Em cenários de alta concorrência, temos de dizer três armas: cache, rebaixamento, limitando
Cache: Os dados são armazenados em cache, reduzindo o stress banco de dados, proteção e IO de disco DB
Rebaixamento: proteger o sistema de núcleo / serviço, reduzir sistema de solicitação de serviço de resposta não-núcleo para evitar falhas no sistema causadas por acumulação excessiva de pedidos
Limitando: o limite de pedido de um determinado período de tempo ou tempo de acesso de um sistema de proteção convencional
Aplicações distribuídas Micro Serviços, limitação de corrente, e outra identificação autoridade geral pode ser feita diretamente no gateway, Primavera Nuvem Gateway oferece a RequestRateLimiterGatewayFilterFactory oficial esta classe de script Lua Redis e percebeu a maneira como o token bucket limitar o .nginx pode fazer em nginx limit_req módulo
Este artigo é feito através da limitação da camada de aplicação desta forma Aop
Antes da linha de código e para introduzir um plug-in frame sob o Google a goiaba https://www.yiibai.com/guava/ além da goiaba, há outras soluções limitando quadro correspondentes, tais como: Ali sentinela (sem contacto), da mola - Zuul-Ratelimit-Nuvem ( https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit ), etc.
Goiaba é baseado em open-source biblioteca Java, que contém muitos da biblioteca do núcleo Google está sendo usado por muitos de seus projetos. Esta biblioteca é facilitar a codificação e reduzir os erros de codificação. Esta biblioteca fornece para a recolha, cache, e primitivas método prático, a simultaneidade, notas comuns, processamento de corda, I / O e suporte de validação. Desde o tutorial original] [Ebey, reimpressão comercial entre em contato com o autor autorizado, reimpressão não comercial por favor reter o link original: https: //www.yiibai.com/guava/
Aqui ele será usado LoadingCache e RateLimiter. Algoritmo envolve algum conhecimento (através de token algoritmo / gotejante balde algoritmo), antes de o algoritmo de diferença dois blogueiros e artigos introduziram
LoadingCache ConcurrentMap semelhante, também thread-safe. Mas LoadingCache acrescenta política mais elementos de validade, ConcurrentMap só pode exibir elementos são removidos
Line e cupom:
Ratelimit anotação personalizado
package com.chwl.cn.ann;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value={ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
double limitNum() default 100.0;//请求数
boolean ipRestricted() default true; //是否限制同一IP
double ipLimitNum() default 1.0;//同一IP访问的次数(默认每秒)
}
Controlador: até duas solicitações por segundo
@RequestMapping(value = "/get/{id}", method = RequestMethod.GET)
@RateLimit(limitNum=2.0,ipRestricted=true,ipLimitNum=1.0)
public ProductEntity get(@PathVariable("id") long id) {
return productService.selectById(id);
}
AOP: se um sinal é adquirido dentro de um segundo, a mesma versão. Flexibilidade, se não, apenas um máximo de duas solicitações simultâneas por segundo para o Application Interface
package com.chwl.cn.aspect;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import com.chwl.cn.ann.RateLimit;
import com.chwl.cn.config.cache.ConcurrentHashMapCache;
import com.chwl.cn.utils.result.JsonMsg;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.RateLimiter;
@Component
@Aspect
@Order(0)
public class RateLimitAspect {
private Logger log = LoggerFactory.getLogger(RateLimitAspect.class);
// 用来存放不同接口的RateLimiter(key为接口名称,value为RateLimiter)
private static ConcurrentHashMap<String, RateLimiter> map = new ConcurrentHashMap<>();
private static ObjectMapper objectMapper = new ObjectMapper();
static {
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
@Autowired
private HttpServletRequest request;
@Autowired
private HttpServletResponse response;
@Pointcut("@annotation(com.chwl.cn.ann.RateLimit)")
public void serviceLimit() {
}
@Around("serviceLimit()")
public Object Around(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
Object obj = null;
// 获取拦截的方法名
Signature sig = joinPoint.getSignature();
String methodName = sig.getName();
// 获取拦截的方法名
MethodSignature msig = (MethodSignature) sig;
// 返回被织入增加处理目标对象
Object target = joinPoint.getTarget();
Method method = msig.getMethod();
RateLimit annotation = method.getAnnotation(RateLimit.class);
// RateLimit annotation = target.getClass().getAnnotation(RateLimit.class);
double limitNum = annotation.limitNum();
String requestURI = request.getRequestURI();
// 避免方法名重复导致rateLimiter被覆盖
String key=requestURI+methodName;
// 获取rateLimiter
// ConcurrentHashMapCache cacheManager = ConcurrentHashMapCache.getCacheManagerInstance();
// cacheManager.init();
if (!map.containsKey(key)) {
map.put(key, RateLimiter.create(limitNum));
}
RateLimiter rateLimiter = map.get(key);
try {
// 是否能马上获取到令牌或者在1秒之内能获取到1个令牌
if (rateLimiter.tryAcquire()||rateLimiter.tryAcquire(1,1,TimeUnit.SECONDS)) {
log.info("他们真的来了");
// 放行,执行方法
obj=joinPoint.proceed();
} else {
// 限制访问 拒绝了请求(服务降级)
String result = objectMapper.writeValueAsString(JsonMsg.Error(500, "系统繁忙!"));
log.error("拒绝了请求:" + result);
outErrorResult(result);
}
} catch (Throwable e) {
}
return obj;
}
// 将结果返回
public void outErrorResult(String result) {
response.setContentType("application/json;charset=UTF-8");
try (ServletOutputStream outputStream = response.getOutputStream()) {
outputStream.write(result.getBytes("utf-8"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
O uso concomitante de interface 20 pedidos JMeter
Este é várias vezes solicitações simultâneas é parte limitada
IP continua limitando, exemplos: Interface de dois pedidos por segundo restritor, limitando o acesso a IP uma vez por segundo no negócio real é para limitar o IP atual, e alguns
Ainda assim, o mesmo método de tratamento,
@RequestMapping(value = "/get/{id}", method = RequestMethod.GET)
@RateLimit(limitNum=2.0,ipRestricted=true,ipLimitNum=1.0)
public ProductEntity get(@PathVariable("id") long id) {
return productService.selectById(id);
}
AOP:
package com.chwl.cn.aspect;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import com.chwl.cn.ann.RateLimit;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.RateLimiter;
@Component
@Aspect
@Order(0)
public class RateLimitAspect {
private Logger log = LoggerFactory.getLogger(RateLimitAspect.class);
// 用来存放不同接口的RateLimiter(key为接口名称,value为RateLimiter)
private static ConcurrentHashMap<String, RateLimiter> map = new ConcurrentHashMap<>();
private static ObjectMapper objectMapper = new ObjectMapper();
private RateLimiter rateLimiter;
static {
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
@Autowired
private HttpServletRequest request;
@Autowired
private HttpServletResponse response;
@Pointcut("@annotation(com.chwl.cn.ann.RateLimit)")
public void serviceLimit() {
}
@Around("serviceLimit()")
public Object Around(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
Object obj = null;
// 获取拦截的方法名
Signature sig = joinPoint.getSignature();
String methodName = sig.getName();
// 获取拦截的方法名
MethodSignature msig = (MethodSignature) sig;
// 返回被织入增加处理目标对象
Object target = joinPoint.getTarget();
Method method = msig.getMethod();
RateLimit annotation = method.getAnnotation(RateLimit.class);
// RateLimit annotation = target.getClass().getAnnotation(RateLimit.class);
double limitNum = annotation.limitNum();
String requestURI = request.getRequestURI();
// 避免方法名重复导致rateLimiter被覆盖
String key=requestURI+methodName;
// 获取rateLimiter
// ConcurrentHashMapCache cacheManager = ConcurrentHashMapCache.getCacheManagerInstance();
// cacheManager.init();
String ip = getIpAddress(request);
if (!map.containsKey(key)) {
rateLimiter= RateLimiter.create(limitNum,1,TimeUnit.SECONDS);
map.put(key,rateLimiter);
}
rateLimiter = map.get(key);
try {
if (rateLimiter.tryAcquire()) {
log.info("他们真的来了");
boolean ipRestricted = annotation.ipRestricted();
if(ipRestricted){
double ipLimitNum = annotation.ipLimitNum();
//IP限流总次数总是不大于接口总限流次数
if(ipLimitNum>limitNum){
ipLimitNum=limitNum;
}
//组装ip次数放令牌桶
String loadingCacheKey=key+ip+"&"+ipLimitNum;
RateLimiter limiter = caches.get(loadingCacheKey);
if(limiter.tryAcquire()){
log.error("来了来了来了");
// 放行,执行方法
obj=joinPoint.proceed();
}else {
//根据实际业务处理
log.error("网络连接错误,当前IP请求错误,每个IP每秒最多只能访问"+ipLimitNum+"次");
}
}else {
obj=joinPoint.proceed();
}
} else {
// 限制访问 拒绝了请求(服务降级)
log.error("拒绝了请求");
}
} catch (Throwable e) {
}
return obj;
}
// 根据IP分不同的令牌桶 目的在于对每个IP的令牌桶RateLimiter本地缓存在loadingcach
private static LoadingCache<String, RateLimiter> caches = CacheBuilder.newBuilder().maximumSize(1000)
// 一秒过期
.expireAfterWrite(1, TimeUnit.SECONDS)
//通过CacheLoader构建RateLimiter在loadingchahe本地缓存起来,如果不存在则自动新建并缓存,存在直接取出
.build(new CacheLoader<String, RateLimiter>() {
@Override
public RateLimiter load(String key) throws Exception {
String ipLimitNum = (key.split("&"))[1];
// 新的IP初始化 (限流每秒ipLimitNum个令牌响应)
return RateLimiter.create(Double.valueOf(ipLimitNum));
}
});
public static String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
Jmeter teste 10 segmentos simultâneos
Até 2 vezes por segundo, limitando o IP é apenas uma passagem, a autenticação for bem sucedida
ConcurrentHashMap pode ser substituído LoadingCache é o mesmo
Estes são limitantes aplicação de interface baseada no monómero ou serviço não é fornecido pelo próprio conjunto, se for distribuído micro-serviços projectar, um agrupamento de serviços, o método acima não funcionar, RateLimiter para aglomerados distribuídos, tais como fadiga. A necessidade de outros programas, anexado ao