Java compilado algunos aspectos de la arquitectura, los datos de la entrevista (micro-servicios, la agrupación, distribuido, middleware, etc.), hay poca necesidad de preocuparse por el socio público en el número programador [algo], no recibirá ningún auto rutina
En primer lugar, ¿cuál es el límite de corriente? ¿Por qué limitar?
No sé si usted no ha hecho el imperio del metro, es decir, en la clase de estaciones de metro tienen que hacer cola, así que ¿por qué poner la larga cola dando vueltas en círculos? La respuesta está en orden 限流
! Debido a que la capacidad de viaje de metro está limitado, mira a apretar demasiado mucha gente va a provocar una sobrecarga de la plataforma llena de tren, hay un cierto riesgo para la seguridad. Del mismo modo, nuestro programa es el mismo, su capacidad para manejar la solicitud se limita, una vez más para el procesamiento de solicitudes más allá de su límite se colapsará. Con el fin de bloquear el peor de los casos no aparece, sólo puede retrasar el tiempo se detiene.
Debido al sistema de tráfico de la gran compañía de Internet en línea hará evaluar un pico de tráfico, especialmente como diversas actividades de promoción de pico, con el fin de asegurar que el sistema no está abrumado por la enorme tráfico será cuando el caudal del sistema alcanza un cierto umbral, se negó a parte de intercambio del flujo .
sistema de límite de corriente conducirá al usuario en un breve periodo de tiempo (esta vez es milisegundo) no está disponible, por lo general medida de la capacidad del sistema es por segundo QPS
o TPS
fluir umbral, suponiendo que el sistema es 1.000 por segundo, Teóricamente segundo hay 1001 cuando la primera petición llega, entonces la solicitud será limitante.
En segundo lugar, el esquema de limitación de corriente
1, el contador
contador interno basado en Java puede también átomo AtomicInteger
, Semaphore
semáforos hacer restrictor de flujo simple.
// 限流的个数
private int maxCount = 10;
// 指定的时间内
private long interval = 60;
// 原子类计数器
private AtomicInteger atomicInteger = new AtomicInteger(0);
// 起始时间
private long startTime = System.currentTimeMillis();
public boolean limit(int maxCount, int interval) {
atomicInteger.addAndGet(1);
if (atomicInteger.get() == 1) {
startTime = System.currentTimeMillis();
atomicInteger.addAndGet(1);
return true;
}
// 超过了间隔时间,直接重新开始计数
if (System.currentTimeMillis() - startTime > interval * 1000) {
startTime = System.currentTimeMillis();
atomicInteger.set(1);
return true;
}
// 还在间隔时间内,check有没有超过限流的个数
if (atomicInteger.get() > maxCount) {
return false;
}
return true;
}
复制代码
2, leaky bucket algoritmo
Cubo que pierde la idea del algoritmo es muy simple, que comparó al agua 请求
, cubo agujereado comparado con 系统处理能力极限
el agua drene en el cubo, drenar el agua de la cubeta a una cierta velocidad, cuando la tasa de flujo de salida es menor que la tasa de entrada, debido al cubo agujereado capacidad limitada, sigue el agua directamente en el rebosadero (rechazar la petición), a fin de lograr de limitación de corriente.
3, el token bucket algoritmo
Principio algoritmo de cubetas de fichas relativamente simple, que puede ser entendido como un hospital a ver a un médico, sólo se puede obtener el número después de la consulta médica.
El sistema mantiene un contador ( token
cubo), a una velocidad constante en un token en el cubo ( token
), a continuación, si hay una petición llega querer ser procesado, la necesidad de adquirir una primera de cubetas de fichas ( token
), cuando sin cubetas de fichas ( token
) es deseable, la solicitud será denegada servicio. Token algoritmo de cubeta con control de la capacidad del tambor, tasa de tokens emitidos, para lograr la solicitud límite.
4 Redis + Take
Muchos estudiantes no saben Lua
es qué? Entender, Lua
guiones y MySQL
el procedimiento de base de datos almacenada es bastante similar, llevan a cabo un conjunto de comandos, ejecutar todos los comandos o bien todos tienen éxito o fracasan, a fin de lograr la atomicidad. También puede Lua
escritura entendida como tener algún bloque de código de lógica de negocio.
Y Lua
sí es un lenguaje de programación, aunque redis
el funcionario no proporcionó directamente el límite de corriente adecuada API
, pero es compatible con la Lua
función de la secuencia de comandos, se puede utilizar para implementar muestra cubo complejo o cubo que pierde algoritmo es un sistema distribuido para lograr la limitación principal una de las maneras.
En comparación Redis
asuntos, Lua脚本
ventajas:
- Reducir la sobrecarga de la red: El uso de la
Lua
secuencia de comandos sin laRedis
petición varias veces, una vez ejecutado, reducir el tráfico de red - operaciones atómicas:
Redis
toda laLua
secuencia de comandos como un comando, atómica, sin tener que preocuparse por la concurrencia - Multiplexación:
Lua
guión, una vez ejecutado, serán almacenados de forma permanenteRedis
en otro cliente ,, reutilizable
Lua
Escritura Lógica sustancialmente como sigue:
-- 获取调用脚本时传入的第一个key值(用作限流的 key)
local key = KEYS[1]
-- 获取调用脚本时传入的第一个参数值(限流大小)
local limit = tonumber(ARGV[1])
-- 获取当前流量大小
local curentLimit = tonumber(redis.call('get', key) or "0")
-- 是否超出限流
if curentLimit + 1 > limit then
-- 返回(拒绝)
return 0
else
-- 没有超出 value + 1
redis.call("INCRBY", key, 1)
-- 设置过期时间
redis.call("EXPIRE", key, 2)
-- 返回(放行)
return 1
end
复制代码
- Por
KEYS[1]
la adquisición de los parámetros clave entrantes - Por
ARGV[1]
la adquisición de los entranteslimit
parámetros redis.call
Método, desde el cachéget
ykey
el valor asociado que, si senull
devuelve 0- a continuación, se determina si el valor registrado en la memoria caché será mayor que el límite de tamaño, si se sobrepasa, indica que el flujo está limitado, devuelve 0
- Si no se excede, entonces la caché de los valores clave de 1, y caduca después una segunda vez, y devuelve el valor almacenado en caché 1
Este enfoque se recomienda en este programa de trabajo, vamos a hacer elaborada realización concreta en la espalda.
5, la capa de puerta de enlace que limita
La limitación de frecuencia hacer en esta capa puertas de enlace, tales como Nginx
, Openresty
, kong
, zuul
, Spring Cloud Gateway
, etc., y como spring cloud - gateway
la puerta de entrada que limita principio implementación subyacente se basa Redis + Lua
, por el incorporado en la Lua
manera de limitar la escritura.
Tres, Redis + Lua limitar lograr
A nuestros pies 自定义注解
, aop
, Redis + Lua
para limitar la corriente, los pasos serían más detallada, con el fin de hacer blancos comenzar a trabajar rápidamente aquí largo aliento un poco más veteranos y experimentados que se ven y tolerancia.
1, la preparación entorno
springboot
Proyecto de creación de una dirección: start.spring.io , una herramienta muy cómodo y práctico.
2, la introducción de dependencias
pom archivo, añadir las siguientes dependencias, es más crítica spring-boot-starter-data-redis
y spring-boot-starter-aop
.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>21.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
复制代码
3, application.properties de configuración
En application.properties
el archivo de configuración de antemano para construir una buena redis
dirección de servicio y el puerto.
spring.redis.host=127.0.0.1
spring.redis.port=6379
复制代码
4, los ejemplos de configuración RedisTemplate
@Configuration
public class RedisLimiterHelper {
@Bean
public RedisTemplate<String, Serializable> limitRedisTemplate(LettuceConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Serializable> template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
复制代码
Limitar el tipo de clase de enumeración
/**
* @author fu
* @description 限流类型
* @date 2020/4/8 13:47
*/
public enum LimitType {
/**
* 自定义key
*/
CUSTOMER,
/**
* 请求者IP
*/
IP;
}
复制代码
5, anotación personalizada
Personalizamos un @Limit
comentario al tipo ElementType.METHOD
que está actuando en el método.
period
Indica que el plazo límite de solicitud, count
muestra period
que el período de tiempo de liberación permitida número de peticiones. limitType
Los tipos representativos de limitación de corriente, de acuerdo con 请求的IP
, 自定义key
si no pasa limitType
el nombre del atributo como el método predeterminado de la clave por defecto.
/**
* @author fu
* @description 自定义限流注解
* @date 2020/4/8 13:15
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Limit {
/**
* 名字
*/
String name() default "";
/**
* key
*/
String key() default "";
/**
* Key的前缀
*/
String prefix() default "";
/**
* 给定的时间范围 单位(秒)
*/
int period();
/**
* 一定时间内最多访问次数
*/
int count();
/**
* 限流的类型(用户自定义key 或者 请求ip)
*/
LimitType limitType() default LimitType.CUSTOMER;
}
复制代码
6, el código de corte para lograr
/**
* @author fu
* @description 限流切面实现
* @date 2020/4/8 13:04
*/
@Aspect
@Configuration
public class LimitInterceptor {
private static final Logger logger = LoggerFactory.getLogger(LimitInterceptor.class);
private static final String UNKNOWN = "unknown";
private final RedisTemplate<String, Serializable> limitRedisTemplate;
@Autowired
public LimitInterceptor(RedisTemplate<String, Serializable> limitRedisTemplate) {
this.limitRedisTemplate = limitRedisTemplate;
}
/**
* @param pjp
* @author fu
* @description 切面
* @date 2020/4/8 13:04
*/
@Around("execution(public * *(..)) && @annotation(com.xiaofu.limit.api.Limit)")
public Object interceptor(ProceedingJoinPoint pjp) {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
Limit limitAnnotation = method.getAnnotation(Limit.class);
LimitType limitType = limitAnnotation.limitType();
String name = limitAnnotation.name();
String key;
int limitPeriod = limitAnnotation.period();
int limitCount = limitAnnotation.count();
/**
* 根据限流类型获取不同的key ,如果不传我们会以方法名作为key
*/
switch (limitType) {
case IP:
key = getIpAddress();
break;
case CUSTOMER:
key = limitAnnotation.key();
break;
default:
key = StringUtils.upperCase(method.getName());
}
ImmutableList<String> keys = ImmutableList.of(StringUtils.join(limitAnnotation.prefix(), key));
try {
String luaScript = buildLuaScript();
RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class);
Number count = limitRedisTemplate.execute(redisScript, keys, limitCount, limitPeriod);
logger.info("Access try count is {} for name={} and key = {}", count, name, key);
if (count != null && count.intValue() <= limitCount) {
return pjp.proceed();
} else {
throw new RuntimeException("You have been dragged into the blacklist");
}
} catch (Throwable e) {
if (e instanceof RuntimeException) {
throw new RuntimeException(e.getLocalizedMessage());
}
throw new RuntimeException("server exception");
}
}
/**
* @author fu
* @description 编写 redis Lua 限流脚本
* @date 2020/4/8 13:24
*/
public String buildLuaScript() {
StringBuilder lua = new StringBuilder();
lua.append("local c");
lua.append("\nc = redis.call('get',KEYS[1])");
// 调用不超过最大值,则直接返回
lua.append("\nif c and tonumber(c) > tonumber(ARGV[1]) then");
lua.append("\nreturn c;");
lua.append("\nend");
// 执行计算器自加
lua.append("\nc = redis.call('incr',KEYS[1])");
lua.append("\nif tonumber(c) == 1 then");
// 从第一次调用开始限流,设置对应键值的过期
lua.append("\nredis.call('expire',KEYS[1],ARGV[2])");
lua.append("\nend");
lua.append("\nreturn c;");
return lua.toString();
}
/**
* @author fu
* @description 获取id地址
* @date 2020/4/8 13:24
*/
public String getIpAddress() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
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.getRemoteAddr();
}
return ip;
}
}
复制代码
7, la capa de control para lograr
Vamos a @Limit
comentar sobre el papel de la interfaz de métodos necesitan ser limitante, los métodos indicados a continuación nos dan @Limit
anotaciones en 10秒
la única publicación 3个
petición, aquí es un poco visual AtomicInteger
conteo.
/**
* @Author: fu
* @Description:
*/
@RestController
public class LimiterController {
private static final AtomicInteger ATOMIC_INTEGER_1 = new AtomicInteger();
private static final AtomicInteger ATOMIC_INTEGER_2 = new AtomicInteger();
private static final AtomicInteger ATOMIC_INTEGER_3 = new AtomicInteger();
/**
* @author fu
* @description
* @date 2020/4/8 13:42
*/
@Limit(key = "limitTest", period = 10, count = 3)
@GetMapping("/limitTest1")
public int testLimiter1() {
return ATOMIC_INTEGER_1.incrementAndGet();
}
/**
* @author fu
* @description
* @date 2020/4/8 13:42
*/
@Limit(key = "customer_limit_test", period = 10, count = 3, limitType = LimitType.CUSTOMER)
@GetMapping("/limitTest2")
public int testLimiter2() {
return ATOMIC_INTEGER_2.incrementAndGet();
}
/**
* @author fu
* @description
* @date 2020/4/8 13:42
*/
@Limit(key = "ip_limit_test", period = 10, count = 3, limitType = LimitType.IP)
@GetMapping("/limitTest3")
public int testLimiter3() {
return ATOMIC_INTEGER_3.incrementAndGet();
}
}
复制代码
8, test
Prueba de lo esperado : tres solicitudes sucesivas pueden tener éxito, cuarto solicitud es denegada. A continuación nos fijamos en que no es el efecto deseado, solicitud Dirección: http://127.0.0.1:8080/limitTest1
, con una postman
prueba, no hay una postman
URL directamente unido al navegador también.
resumen
Más springboot + aop + Lua
limitar la aplicación es relativamente simple, diseñado para que todos sepan lo que está limitando? Cómo hacer que una corriente sencilla limitar entrevistas para saber que esto es algo. Aunque varios de los programas anteriormente para la limitación de corriente, pero ¿qué tipo de elección, sino también con los escenarios de negocio específicos, no por el bien de utilizar.
beneficios pequeños:
Algunos frikis llegar a pagar por el curso, Boo ~, libre para el pequeño. Si no responde [preocupación pública friki ] autorrecolección