Home  >  Article  >  Database  >  Three ways to implement a current limiter in Redis (summary sharing)

Three ways to implement a current limiter in Redis (summary sharing)

WBOY
WBOYforward
2022-09-08 17:50:322392browse

Recommended learning: Redis video tutorial

Method 1: Redis-based setnx operation

We are here When using Redis's distributed lock, everyone knows that it relies on the setnx instruction. During the CAS (Compare and swap) operation, the expiration practice (expire) is set for the specified key. We are mainly limiting the current The purpose is to allow only N number of requests to access my code program within unit time. So relying on setnx can easily achieve this function.

For example, if we need to limit 20 requests within 10 seconds, then we can set the expiration time to 10 during setnx. When the number of requested setnx reaches 20, the current limiting effect will be achieved. The code is relatively simple and will not be shown.

Of course, there are many disadvantages to this approach. For example, when counting 1-10 seconds, it is impossible to count 2-11 seconds. If you need to count M requests within N seconds, then our Redis Need to maintain N keys and other issues.

In the specific implementation, you can consider using the interceptor HandlerInterceptor:

public class RequestCountInterceptor implements HandlerInterceptor {

    private LimitPolicy limitPolicy;

    public RequestCountInterceptor(LimitPolicy limitPolicy) {
        this.limitPolicy = limitPolicy;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!limitPolicy.canDo()) {
            return false;
        }
        return true;
    }
}

At the same time add a configuration LimitConfiguration:

@Configuration
public class LimitConfiguration implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new RequestCountInterceptor(new RedisLimit1())).addPathPatterns("/my/increase");
    }
}

This way every time a /my/increase request reaches the Controller Previously, the current limit was based on the policy RedisLimit1. The code in the original Controller does not need to be modified:

@RestController
@RequestMapping("my")
public class MyController {
    int i = 0;
    @RequestMapping("/increase")
    public int increase() {
        return i++;
    }
}

The specific current limit logic code is in the RedisLimit1 class:

/**
* 方法一:基于Redis的setnx的操作
*/
public class RedisLimit1 extends LimitPolicy {

    static {
        setNxExpire();
    }

    private static boolean setNxExpire() {
        SetParams setParams = new SetParams();
        setParams.nx();
        setParams.px(TIME);
        String result = jedis.set(KEY, COUNT + "", setParams);


        if (SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

    @Override
    public boolean canDo() {

        if (setNxExpire()) {
            //设置成功,说明原先不存在,成功设置为COUNT
            return true;
        } else {
            //设置失败,说明已经存在,直接减1,并且返回
            return jedis.decrBy(KEY, 1) > 0;
        }
    }
}

public abstract class LimitPolicy {
    public static final int COUNT = 10; //10 request
    public static final int TIME= 10*1000 ; // 10s
    public static final String SUCCESS = "OK";
    static Jedis jedis = new Jedis();
    abstract boolean canDo();
}

One effect achieved in this way is Maximum of 10 requests per second.

Method 2: Redis-based data structure zset

In fact, the most important thing involved in current limiting is the sliding window. It is also mentioned above how 1-10 becomes 2-11. In fact, the starting value and the end value are both 1.
If we use Redis's list data structure, we can easily implement this function
We can create the request into a zset array. When each request comes in, the value remains unique and can be generated using UUID, and score It can be represented by the current timestamp, because score can be used to calculate the number of requests within the current timestamp. The zset data structure also provides the zrange method so that we can easily get how many requests there are within 2 timestamps

/**
* 方法二:基于Redis的数据结构zset
*/
public class RedisLimit2 extends LimitPolicy {
    public static final String KEY2 = "LIMIT2";

    @Override
    public boolean canDo() {
        Long currentTime = new Date().getTime();
        System.out.println(currentTime);
        if (jedis.zcard(KEY2) > 0) { // 这里不能用get判断,会报错:WRONGTYPE Operation against a key holding the wrong kind of value
            Integer count = jedis.zrangeByScore(KEY2, currentTime - TIME, currentTime).size(); // 注意这里使用zrangeByScore,以时间作为score。zrange key start stop 命令的start和stop是序号。
            System.out.println(count);
            if (count != null && count > COUNT) {
                return false;
            }
        }
        jedis.zadd(KEY2, Double.valueOf(currentTime), UUID.randomUUID().toString());
        return true;

    }
}

The above code can achieve the effect of a sliding window and ensure that every N seconds At most M requests, the disadvantage is that the data structure of zset will become larger and larger. The implementation method is relatively simple.

Method 3: Redis-based token bucket algorithm

When it comes to current limiting, we have to mention the token bucket algorithm. The token bucket algorithm mentions input rate and output rate. When the output rate is greater than the input rate, the traffic limit is exceeded. That is to say, every time we access a request, we can get a token from Redis. If we get the token, it means that the limit has not been exceeded. If we cannot get it, the result will be the opposite.
Relying on the above ideas, we can easily implement such code by combining the List data structure of Redis. It is just a simple implementation that relies on the leftPop of List to obtain the token.

First configure a scheduled task and insert a token every second through the rpush method of the redis list:

@Configuration      //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling   // 2.开启定时任务
public class SaticScheduleTask {
    //3.添加定时任务
    @Scheduled(fixedRate = 1000)
    private void configureTasks() {
        LimitPolicy.jedis.rpush("LIMIT3", UUID.randomUUID().toString());
    }
}

When the current is limited, obtain the corresponding token from redis through the lpop method of the list , if the acquisition is successful, the request can be executed:

/**
* 方法三:令牌桶
*/
public class RedisLimit3 extends LimitPolicy {
    public static final String KEY3 = "LIMIT3";

    @Override
    public boolean canDo() {

        Object result = jedis.lpop(KEY3);
        if (result == null) {
            return false;
        }
        return true;
    }
}

Recommended learning: Redis video tutorial

The above is the detailed content of Three ways to implement a current limiter in Redis (summary sharing). For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:jb51.net. If there is any infringement, please contact admin@php.cn delete