Springboot + aop + Lua distributed current limiting principle analysis

1. What is current limit? Why limit current?
I do n’t know if you have ever done the metro of the Imperial City, that is, the kind that you have to queue up when you go to the subway station. Why do you have to go in circles in this way? The answer is to limit current! Because the transportation capacity of a subway is limited, crowding too many people at a time will cause crowding of the platform and overload of the train, and there will be certain safety risks. In the same way, our program is the same, and its ability to process requests is limited. Once the number of requests exceeds its processing limit, it will crash. In order not to have the worst crash situation, we can only delay the time for everyone to enter the station.
Springboot + aop + Lua distributed current limiting principle analysis

Current limiting is an important means to ensure high availability of the system! ! !

Due to the huge traffic of Internet companies, the system will go online to do a peak traffic evaluation, especially like various spike promotion activities. In order to ensure that the system is not overwhelmed by huge traffic, when the system traffic reaches a certain threshold, some of the traffic will be rejected .

The current limit will cause the user to be unavailable for a short period of time (this time period is in the order of milliseconds). In general, the indicator for measuring the processing capacity of the system is QPS or TPS per second. Assuming that the system's flow threshold per second is 1000, theoretical When a 1001 request comes in within one second, the request will be throttled.

Second, the current limit program
1. The counter
Java can also use the atomic counter AtomicInteger, Semaphore semaphore to do simple current limit.

// 限流的个数
        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 algorithm The idea of ​​the leaky bucket algorithm is very simple. We compare the water as a request, and the leaky bucket as the limit of the system's processing capacity. When the rate is less than the inflow rate, due to the limited capacity of the leaking bucket, the subsequent incoming water directly overflows (rejects the request) to achieve the flow limitation.
Springboot + aop + Lua distributed current limiting principle analysis

3.
Token Bucket Algorithm The principle of the token bucket algorithm is also relatively simple. We can understand it as a registered hospital to see a doctor. Only after getting the number can the doctor be diagnosed.
The system maintains a token bucket and puts tokens into the bucket at a constant speed. If a request comes in and wants to be processed, you need to obtain a token from the bucket first. ), When no token is available in the bucket, the request will be denied service. The token bucket algorithm achieves the limit on requests by controlling the capacity of the bucket and the rate of issuing tokens.
Springboot + aop + Lua distributed current limiting principle analysis

4. Redis + Lua
Many students do not know what Lua is? Personally, Lua scripts and MySQL databases have similar stored procedures. They execute a set of commands, and all commands execute either successfully or fail to achieve atomicity. You can also understand Lua script as a piece of code with business logic.

Lua itself is a programming language. Although Redis does not directly provide the corresponding API for current limiting, it supports the function of Lua scripts. It can be used to implement complex token bucket or leak bucket algorithms, which are also implemented in distributed systems. One of the main ways to limit current.

Compared with Redis transactions, the advantages of Lua scripts are:

Reduce network overhead: Use Lua script, do not need to send multiple requests to Redis, execute it once, reduce network transmission
atomic operation: Redis executes the entire Lua script as a command, atomic, without worrying about concurrent
reuse: Once the Lua script is executed, Will be permanently saved in Redis, and other clients can reuse
Lua scripts roughly as follows:

-Get the first key value passed in when calling the script (used as the current limit key)
local key = KEYS [1]-Get
the first parameter value passed in when calling the script (limit current size)
local limit = tonumber (ARGV [1])


-Get the current traffic size 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

Get the incoming key parameter through KEYS [1] Get the incoming
limit parameter through ARGV [1]
redis.call method, get the value related to the key from the cache, if it is null then return 0 and
then judge the record in the cache Whether the value will be larger than the limit size. If it exceeds the limit, it means that the current is limited.
If it does not exceed 0 , then the key's cache value is +1, and the expiration time is set to 1 second later, and the cache value +1
is returned. The solution recommended in this article will be explained in detail later.

5. Gateway layer current limiting and current
limiting are often done at the gateway layer, such as Nginx, Openresty, kong, zuul, Spring Cloud Gateway, etc., and the underlying implementation principle of spring cloud-gateway gateway current limiting is based on Redis + Lua, through Built-in Lua current limiting script.
Springboot + aop + Lua distributed current limiting principle analysis

Third, Redis + Lua current limiting implementation
Below we achieve the current limiting through custom annotations, aop, Redis + Lua, the steps will be more detailed, in order to let Bai Bai get started quickly, the experienced veterans will take more care.

1. The environment preparation
springboot project creation address: https://start.spring.io , a very convenient and practical tool.

Springboot + aop + Lua distributed current limiting principle analysis
2. Introduce the dependency package to the
pom file and add the following dependency packages. The key ones are spring-boot-starter-data-redis and 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. Configure application.properties
to configure the redis service address and port that have been built in advance in the application.properties file.

spring.redis.host=127.0.0.1

spring.redis.port=6379
4、配置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;
}

}
Current limit type enumeration class

/**
 * @author fu
 * @description 限流类型
 * @date 2020/4/8 13:47
 */
public enum LimitType {

        /**
         * 自定义key
         */
        CUSTOMER,

        /**
         * 请求者IP
         */
        IP;
}

5. Custom annotations
We define a @Limit annotation, the annotation type is ElementType.METHOD, which acts on the method.

period is the request limit time period, and count is the number of times the request is allowed to be released during the period. limitType represents the type of current limit. You can customize the key according to the requested IP. If you do not pass the limitType attribute, the method name is used as the default key by default.

/**
 * @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. Aspect code implementation
/ **

}
8. Test
test expectation: three consecutive requests can be successful, the fourth request is rejected. Next, let's see if we expect the effect. The request address:
http://127.0.0.1:8080/limitTest1 . Use postman to test. It is the same whether there is a postman url directly posted to the browser.
Springboot + aop + Lua distributed current limiting principle analysis

It can be seen that when the fourth request was made, the application directly rejected the request, indicating that our Springboot + aop + lua current limiting solution was successfully built.

Springboot + aop + Lua distributed current limiting principle analysis
To sum up the
above springboot + aop + Lua current limit implementation is relatively simple, designed to let everyone know what is current limit? How to do a simple current limit function, the interview must know what this is. Although the above mentioned several solutions to achieve current limiting, but which one to choose must be combined with specific business scenarios, can not be used for use.

Guess you like

Origin blog.51cto.com/14765930/2486702