盒子
盒子
文章目录
  1. Guava之RateLimiter速率限制器
    1. 1. RateLimiter方法摘要
    2. 2. 实例

Guava之RateLimiter速率限制器

Guava之RateLimiter速率限制器

在平时开发中,我们有时需要对某些接口的调用进行速率限制,以保护系统的稳定运行。
常用的限流算法有漏桶算法和令牌桶算法,guava的RateLimiter使用的是令牌桶算法,也就是以固定的频率向桶中放入令牌,例如一秒钟10枚令牌,实际业务在每次响应请求之前都从桶中获取令牌,只有取到令牌的请求才会被成功响应,获取的方式有两种:阻塞等待令牌或者取不到立即返回失败

令牌桶算法

1. RateLimiter方法摘要

修饰符和类型 方法和描述
double acquire() 从RateLimiter获取一个许可,该方法会被阻塞直到获取到请求
double acquire(int permits)从RateLimiter获取指定许可数,该方法会被阻塞直到获取到请求
static RateLimiter create(double permitsPerSecond)根据指定的稳定吞吐率创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少查询)
static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit)根据指定的稳定吞吐率和预热期来创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少个请求量),在这段预热时间内,RateLimiter每秒分配的许可数会平稳地增长直到预热期结束时达到其最大速率。(只要存在足够请求数来使其饱和)
double getRate()返回RateLimiter 配置中的稳定速率,该速率单位是每秒多少许可数
void setRate(double permitsPerSecond)更新RateLimite的稳定速率,参数permitsPerSecond 由构造RateLimiter的工厂方法提供。
String toString()返回对象的字符表现形式
boolean tryAcquire()从RateLimiter 获取许可,如果该许可可以在无延迟下的情况下立即获取得到的话
boolean tryAcquire(int permits)从RateLimiter 获取许可数,如果该许可数可以在无延迟下的情况下立即获取得到的话
boolean tryAcquire(int permits, long timeout, TimeUnit unit)从RateLimiter 获取指定许可数如果该许可数可以在不超过timeout的时间内获取得到的话,或者如果无法在timeout 过期之前获取得到许可数的话,那么立即返回false (无需等待)
boolean tryAcquire(long timeout, TimeUnit unit)从RateLimiter 获取许可如果该许可可以在不超过timeout的时间内获取得到的话,或者如果无法在timeout 过期之前获取得到许可的话,那么立即返回false(无需等待)

2. 实例

  • 我们需要处理一个任务列表,但我们不希望每秒的任务提交超过两个:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //速率是每秒两个许可
    final RateLimiter rateLimiter = RateLimiter.create(2.0);

    void submitTasks(List tasks, Executor executor) {
    for (Runnable task : tasks) {
    rateLimiter.acquire(); // 该方法会被阻塞直到获取到请求
    executor.execute(task);
    }
    }
  • 某个Controller请求,限制每秒5个

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    @Controller
    public class HelloController {

    //每秒只发出5个令牌
    private static final RateLimiter rateLimiter = RateLimiter.create(5.0);

    @Autowired
    private AccessLimitService accessLimitService;

    @RequestMapping("/access")
    @ResponseBody
    public String access(){
    //尝试获取令牌
    if(rateLimiter.tryAcquire()){
    //执行业务
    return "aceess success [" + sdf.format(new Date()) + "]";
    }else{
    //请求数超出
    return "aceess limit [" + sdf.format(new Date()) + "]";
    }
    }
    }

  • 一直被调用的接口,每30秒打印一次日志

1
2
3
4
5
6
7
8
9
10
11
private static final RateLimiter rateLimiter = RateLimiter.create(1.0);

void onMessage(String param) {
//执行业务

//打印日志
if (logRateLimiter.tryAcquire(30)) {
logger.info(String.format("onMessage调用正常");
}

}