springboot + + AOP mejores prácticas Lua distribuye límite de corriente

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.

Aquí Insertar imagen Descripción
Limitación de corriente es un medio importante para asegurar una alta disponibilidad del sistema! ! !

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 QPSo TPSfluir 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, Semaphoresemá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.

Aquí Insertar imagen Descripción

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 ( tokencubo), 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.

Aquí Insertar imagen Descripción

4 Redis + Take

Muchos estudiantes no saben Luaes qué? Entender, Luaguiones y MySQLel 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 Luaescritura entendida como tener algún bloque de código de lógica de negocio.

Y Luasí es un lenguaje de programación, aunque redisel funcionario no proporcionó directamente el límite de corriente adecuada API, pero es compatible con la Luafunció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 Redisasuntos, Lua脚本ventajas:

  • Reducir la sobrecarga de la red: El uso de la Luasecuencia de comandos sin la Redispetición varias veces, una vez ejecutado, reducir el tráfico de red
  • operaciones atómicas: Redistoda la Luasecuencia de comandos como un comando, atómica, sin tener que preocuparse por la concurrencia
  • Multiplexación: Luaguión, una vez ejecutado, serán almacenados de forma permanente Redisen otro cliente ,, reutilizable

LuaEscritura 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 entrantes limitparámetros
  • redis.callMétodo, desde el caché gety keyel valor asociado que, si se nulldevuelve 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 - gatewayla puerta de entrada que limita principio implementación subyacente se basa Redis + Lua, por el incorporado en la Luamanera de limitar la escritura.

Aquí Insertar imagen Descripción

Tres, Redis + Lua limitar lograr

A nuestros pies 自定义注解, aop, Redis + Luapara 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

springbootProyecto de creación de una dirección: start.spring.io , una herramienta muy cómodo y práctico.

Aquí Insertar imagen Descripción

2, la introducción de dependencias

pom archivo, añadir las siguientes dependencias, es más crítica spring-boot-starter-data-redisy 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.propertiesel archivo de configuración de antemano para construir una buena redisdirecció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 @Limitcomentario al tipo ElementType.METHODque está actuando en el método.

periodIndica que el plazo límite de solicitud, countmuestra periodque el período de tiempo de liberación permitida número de peticiones. limitTypeLos tipos representativos de limitación de corriente, de acuerdo con 请求的IP, 自定义keysi no pasa limitTypeel 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 @Limitcomentar sobre el papel de la interfaz de métodos necesitan ser limitante, los métodos indicados a continuación nos dan @Limitanotaciones en 10秒la única publicación 3个petición, aquí es un poco visual AtomicIntegerconteo.

/**
 * @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 postmanprueba, no hay una postmanURL directamente unido al navegador también.

Aquí Insertar imagen Descripción
Cuando se puede ver la cuarta solicitud, la aplicación directa rechazó la solicitud, muestra que nuestra Springboot + AOP + lua limitar el programa para el éxito de construcción.
Aquí Insertar imagen Descripción

resumen

Más springboot + aop + Lualimitar 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

Supongo que te gusta

Origin juejin.im/post/5e8da655f265da47f9674cbb
Recomendado
Clasificación