1. 概述

限流,无论在系统层面,还是在业务层面,使用都非常广泛。例如说:

  • 【业务】为了避免恶意的灌水机或者用户,限制每分钟至允许回复 10 个帖子。
  • 【系统】为了避免服务系统被大规模调用,超过极限,限制每个调用方只允许每秒调用 100 次。

限流算法,常用的分成四种:

每一种的概念,推荐看看 《计数器、滑动窗口、漏桶、令牌算法比较和伪代码实现》 文章。

  • 计数器

    比较简单,每固定单位一个计数器即可实现。

  • 滑动窗口

    Redisson 提供的是基于滑动窗口 RateLimiter 的实现。相比计数器的实现,它的起点不是固定的,而是以开始计数的那个时刻开始为一个窗口。

    所以,我们可以把计数器理解成一个滑动窗口的特例,以固定单位为一个窗口。

  • 令牌桶算法

    《Eureka 源码解析 —— 基于令牌桶算法的 RateLimiter》 ,单机并发场景下的 RateLimiter 实现。

    《Spring-Cloud-Gateway 源码解析 —— 过滤器 (4.10) 之 RequestRateLimiterGatewayFilterFactory 请求限流》 ,基于 Redis 实现的令牌桶算法的 RateLimiter 实现。

  • 漏桶算法

    漏桶算法,一直没搞明白和令牌桶算法的区别。现在的理解是:

    • 令牌桶算法,桶里装的是令牌。每次能拿取到令牌,就可以进行访问。并且,令牌会按照速率不断恢复放到令牌桶中直到桶满。
    • 漏桶算法,桶里装的是请求。当桶满了,请求就进不来。例如说,Hystrix 使用线程池或者 Semaphore 信号量,只有在请求未满的时候,才可以进行执行。

上面哔哔了非常多的字,只看本文的话,就那一句话:“Redisson 提供的是基于滑动窗口 RateLimiter 的实现。”。

2. 整体一览

在 Redisson 中,提供了四个 RateLimiter 相关的接口,如下图:

正在上传…重新上传取消RateLimiter 接口

  • org.redisson.api.RRateLimiterAsync ,定义了异步操作的接口。
  • org.redisson.api.RRateLimiter ,继承 RRateLimiterAsync 的基础上,定义了同步操作的接口。
  • org.redisson.api.RRateLimiterReactive ,定义基于 Reactor 操作的接口。
  • org.redisson.api.RRateLimiterReactive ,定义基于 RxJava 操作的接口。

目前,Redisson 暂时只实现了 RRateLimiterAsync 和 RRateLimiter 接口的方法,即 org.redisson.RedissonRateLimiter 。

RRateLimiterAsync 和 RRateLimiter 定义的接口,差别就在于同步和异步,所以我们就只看看 RRateLimiter 接口。代码如下:

boolean trySetRate(RateType mode, long rate, long rateInterval, RateIntervalUnit rateIntervalUnit);
RateLimiterConfig getConfig();boolean tryAcquire();
boolean tryAcquire(long permits);
boolean tryAcquire(long timeout, TimeUnit unit);
boolean tryAcquire(long permits, long timeout, TimeUnit unit);void acquire();
void acquire(long permits);
  • #trySetRate(RateType mode, long rate, long rateInterval, RateIntervalUnit rateIntervalUnit) 方法,设置限流器的配置。
  • #getConfig() 方法,获得限流器的配置。
  • #tryAcquire(...) 方法,尝试在指定时间内,获得指定数量的令牌,并返回是否成功。
  • #acquire(...) 方法,在指定时间内,获得指定数量的令牌,直到成功。

总的来说,一共两类方法,一类是设置或获取配置,一类是获取令牌。下面,我们来逐个方法的源码,来瞅瞅。

3. trySetRate

在 《精尽 Redisson 源码分析 —— 调试环境搭建》 中,我们搭建了一个限流器的示例。在示例的开始,我们会调用 RRateLimiter#trySetRateAsync(RateType type, long rate, long rateInterval, RateIntervalUnit unit) 方法,设置限流器的配置。代码如下:

// RedissonRateLimiter.java@Override
public boolean trySetRate(RateType type, long rate, long rateInterval, RateIntervalUnit unit) {return get(trySetRateAsync(type, rate, rateInterval, unit));
}@Override
public RFuture<Boolean> trySetRateAsync(RateType type, long rate, long rateInterval, RateIntervalUnit unit) {return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,"redis.call('hsetnx', KEYS[1], 'rate', ARGV[1]);"+ "redis.call('hsetnx', KEYS[1], 'interval', ARGV[2]);"+ "return redis.call('hsetnx', KEYS[1], 'type', ARGV[3]);",Collections.<Object>singletonList(getName()), // keys [分布锁名]rate, unit.toMillis(rateInterval), type.ordinal()); // values [速度、速度单位、限流类型]
}