限流
限流算法在分布式领域是一个经常被提起的话题,当系统的处理能力有限时,如何阻止 计划外的请求继续对系统施压,这是一个需要重视的问题。老钱在这里用 “断尾求生” 形 容限流背后的思想,当然还有很多成语也表达了类似的意思,如弃卒保车、壮士断腕等等。
除了控制流量,限流还有一个应用目的是用于控制用户行为,避免垃圾请求。比如在 UGC 社区,用户的发帖、回复、点赞等行为都要严格受控,一般要严格限定某行为在规定 时间内允许的次数,超过了次数那就是非法行为。对非法行为,业务必须规定适当的惩处策 略。
如何使用 Redis 来实现简单限流策略?
系统要限定用户的某个行为在指定的时间里 只能允许发生 N 次,如何使用 Redis 的数据结构来实现这个限流的功能?
# 指定用户 user_id 的某个行为 action_key 在特定的时间内 period 只允许发生一定的次数
max_count
def is_action_allowed(user_id, action_key, period, max_count):
return True
# 调用这个接口 , 一分钟内只允许最多回复 5 个帖子
can_reply = is_action_allowed("laoqian", "reply", 60, 5)
if can_reply:
do_reply()
else:
raise ActionThresholdOverflow()
这个限流需求中存在一个滑动时间窗口,想想 zset 数据结构的 score 值,是不是可以 通过 score 来圈出这个时间窗口来。而且我们只需要保留这个时间窗口,窗口之外的数据都 可以砍掉。那这个 zset 的 value 填什么比较合适呢?它只需要保证唯一性即可,用 uuid 会 比较浪费空间,那就改用毫秒时间戳吧。
如图所示,用一个 zset 结构记录用户的行为历史,每一个行为都会作为 zset 中的一个 key 保存下来。同一个用户同一种行为用一个 zset 记录。
为节省内存,我们只需要保留时间窗口内的行为记录,同时如果用户是冷用户,滑动时 间窗口内的行为是空记录,那么这个 zset 就可以从内存中移除,不再占用空间。
通过统计滑动窗口内的行为数量与阈值 max_count 进行比较就可以得出当前的行为是否 允许。用代码表示如下:
package com.example.demo.book;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;
/**
* @Author: acton_zhang
* @Date: 2023/4/16 11:56 下午
* @Version 1.0
* 简单限流
*/
public class SimpleRateLimiter {
private Jedis jedis;
public SimpleRateLimiter(Jedis jedis) {
this.jedis = jedis;
}
public boolean isActionAllowed(String userId, String actionKey, int period, int maxCount) {
String key = String.format("hist:%s:%s", userId, actionKey);
//毫秒时间戳
long nowTs = System.currentTimeMillis();
Pipeline pipe = jedis.pipelined();
pipe.multi();
//记录行为
pipe.zadd(key, nowTs, "" + nowTs);
//移除时间窗口之前的行为记录,剩下的都是时间窗口内的
pipe.zremrangeByScore(key, 0, nowTs - period * 1000);
//获取窗口内的行为数量
Response<Long> count = pipe.zcard(key);
//设置zset过期时间,避免冷用户持续占用内存
//过期时间应该等于时间窗口的长度,再多宽限1S
pipe.expire(key, period + 1);
//批量执行
pipe.exec();
pipe.close();
//比较数量是否超标
return count.get() <= maxCount;
}
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.auth("123456");
SimpleRateLimiter limiter = new SimpleRateLimiter(jedis);
for (int i = 0; i < 20; i++) {
System.out.println(limiter.isActionAllowed("laoqian", "reply", 60, 5));
}
}
}
输出:
/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/bin/java "-javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=62021:/Applications/IntelliJ IDEA.app/Contents/bin" -Dfile.encoding=UTF-8 -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/tools.jar:/Users/acton_zhang/J2EE/IdeaWrokSpace/sb_redis_demo/target/classes:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/springframework/boot/spring-boot-starter-web/2.7.4/spring-boot-starter-web-2.7.4.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/springframework/boot/spring-boot-starter/2.7.4/spring-boot-starter-2.7.4.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/springframework/boot/spring-boot/2.7.4/spring-boot-2.7.4.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/springframework/boot/spring-boot-autoconfigure/2.7.4/spring-boot-autoconfigure-2.7.4.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/springframework/boot/spring-boot-starter-logging/2.7.4/spring-boot-starter-logging-2.7.4.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/ch/qos/logback/logback-classic/1.2.11/logback-classic-1.2.11.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/ch/qos/logback/logback-core/1.2.11/logback-core-1.2.11.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/apache/logging/log4j/log4j-to-slf4j/2.17.2/log4j-to-slf4j-2.17.2.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/apache/logging/log4j/log4j-api/2.17.2/log4j-api-2.17.2.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/slf4j/jul-to-slf4j/1.7.36/jul-to-slf4j-1.7.36.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/jakarta/annotation/jakarta.annotation-api/1.3.5/jakarta.annotation-api-1.3.5.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/yaml/snakeyaml/1.30/snakeyaml-1.30.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/springframework/boot/spring-boot-starter-json/2.7.4/spring-boot-starter-json-2.7.4.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/com/fasterxml/jackson/core/jackson-databind/2.13.4/jackson-databind-2.13.4.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/com/fasterxml/jackson/core/jackson-annotations/2.13.4/jackson-annotations-2.13.4.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/com/fasterxml/jackson/core/jackson-core/2.13.4/jackson-core-2.13.4.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.13.4/jackson-datatype-jdk8-2.13.4.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.13.4/jackson-datatype-jsr310-2.13.4.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.13.4/jackson-module-parameter-names-2.13.4.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/springframework/boot/spring-boot-starter-tomcat/2.7.4/spring-boot-starter-tomcat-2.7.4.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/apache/tomcat/embed/tomcat-embed-core/9.0.65/tomcat-embed-core-9.0.65.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/apache/tomcat/embed/tomcat-embed-el/9.0.65/tomcat-embed-el-9.0.65.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/apache/tomcat/embed/tomcat-embed-websocket/9.0.65/tomcat-embed-websocket-9.0.65.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/springframework/spring-web/5.3.23/spring-web-5.3.23.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/springframework/spring-beans/5.3.23/spring-beans-5.3.23.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/springframework/spring-webmvc/5.3.23/spring-webmvc-5.3.23.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/springframework/spring-aop/5.3.23/spring-aop-5.3.23.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/springframework/spring-context/5.3.23/spring-context-5.3.23.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/springframework/spring-expression/5.3.23/spring-expression-5.3.23.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/hamcrest/hamcrest/2.2/hamcrest-2.2.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/springframework/spring-core/5.3.23/spring-core-5.3.23.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/springframework/spring-jcl/5.3.23/spring-jcl-5.3.23.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/springframework/boot/spring-boot-starter-data-redis/2.7.4/spring-boot-starter-data-redis-2.7.4.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/springframework/data/spring-data-redis/2.7.3/spring-data-redis-2.7.3.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/springframework/data/spring-data-keyvalue/2.7.3/spring-data-keyvalue-2.7.3.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/springframework/data/spring-data-commons/2.7.3/spring-data-commons-2.7.3.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/springframework/spring-tx/5.3.23/spring-tx-5.3.23.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/springframework/spring-oxm/5.3.23/spring-oxm-5.3.23.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/springframework/spring-context-support/5.3.23/spring-context-support-5.3.23.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/redis/clients/jedis/3.8.0/jedis-3.8.0.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/apache/commons/commons-pool2/2.11.1/commons-pool2-2.11.1.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/com/alibaba/fastjson/1.2.66/fastjson-1.2.66.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/com/redislabs/jrebloom/1.2.0/jrebloom-1.2.0.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/com/google/code/gson/gson/2.9.1/gson-2.9.1.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/junit/junit/4.13.2/junit-4.13.2.jar:/Users/acton_zhang/software/apache-maven-3.6.3/repository/org/hamcrest/hamcrest-core/2.2/hamcrest-core-2.2.jar com.example.demo.book.SimpleRateLimiter
true
true
true
true
true
true
true
true
true
true
true
false
false
false
false
false
false
false
false
false
这段代码还是略显复杂,需要读者花一定的时间好好啃。它的整体思路就是:每一个行 为到来时,都维护一次时间窗口。将时间窗口外的记录全部清理掉,只保留窗口内的记录。
zset 集合中只有 score 值非常重要,value 值没有特别的意义,只需要保证它是唯一的就可 以了。
因为这几个连续的 Redis 操作都是针对同一个 key 的,使用 pipeline 可以显著提升 Redis 存取效率。但这种方案也有缺点,因为它要记录时间窗口内所有的行为记录,如果这 个量很大,比如限定 60s 内操作不得超过 100w 次这样的参数,它是不适合做这样的限流 的,因为会消耗大量的存储空间。
漏斗限流
漏斗限流是最常用的限流方法之一,顾名思义,这个算法的灵感源于漏斗(funnel)的结
构。
漏洞的容量是有限的,如果将漏嘴堵住,然后一直往里面灌水,它就会变满,直至再也 装不进去。如果将漏嘴放开,水就会往下流,流走一部分之后,就又可以继续往里面灌水。 如果漏嘴流水的速率大于灌水的速率,那么漏斗永远都装不满。如果漏嘴流水速率小于灌水 的速率,那么一旦漏斗满了,灌水就需要暂停并等待漏斗腾空。
所以,漏斗的剩余空间就代表着当前行为可以持续进行的数量,漏嘴的流水速率代表着 系统允许该行为的最大频率。下面我们使用代码来描述单机漏斗算法。
package com.example.demo.book;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: acton_zhang
* @Date: 2023/4/17 12:53 上午
* @Version 1.0
* 漏斗限流
*/
public class FunnelRateLimiter {
static class Funnel {
int capacity; //漏斗容量
float leakingRate; //漏斗流水速率
int leftQuota; //漏斗剩余空间
long leakingTs; //上一次漏水时间
public Funnel(int capacity, float leakingRate) {
this.capacity = capacity;
this.leakingRate = leakingRate;
this.leftQuota = capacity;
this.leakingTs = System.currentTimeMillis();
}
void makeSpace() {
long nowTs = System.currentTimeMillis();
//距离上一次漏水过去了多久
long deltaTs = nowTs - leakingTs;
//上一次漏水后腾出的空间
int deltaQuota = (int)(deltaTs * leakingRate);
//间隔时间太长,整数数字过大溢出,整个漏斗空了
if (deltaQuota < 0) {
this.leftQuota = capacity;
this.leakingTs = nowTs;
return;
}
//腾出空间太小,最小单位是1
if (deltaQuota < 1) {
return;
}
//增加空间
this.leftQuota += deltaQuota;
//记录漏水时间
this.leakingTs = nowTs;
//剩余空间不得高于容量
if (this.leftQuota > this.capacity) {
this.leftQuota = this.capacity;
}
}
boolean watering(int quota) {
makeSpace();
//判断剩余空间是否足够
if (this.leftQuota >= quota) {
this.leftQuota -= quota;
return true;
}
return false;
}
}
private Map<String, Funnel> funnels = new HashMap<>();//所有的漏斗
/**
*
* @param userId
* @param actionKey
* @param capacity 漏斗容量
* @param leakingRate 漏嘴流水速率 quota/s
* @return
*/
public boolean isActonAllowed(String userId, String actionKey, int capacity, float leakingRate) {
String key = String.format("%s:%s", userId, actionKey);
Funnel funnel = funnels.get(key);
if (funnel == null) {
funnel = new Funnel(capacity, leakingRate);
funnels.put(key, funnel);
}
//需要一个quota
return funnel.watering(1);
}
}
Funnel 对象的 make_space 方法是漏斗算法的核心,其在每次灌水前都会被调用以触发 漏水,给漏斗腾出空间来。能腾出多少空间取决于过去了多久以及流水的速率。Funnel 对象 占据的空间大小不再和行为的频率成正比,它的空间占用是一个常量。
问题来了,分布式的漏斗算法该如何实现?能不能使用 Redis 的基础数据结构来搞定?
我们观察 Funnel 对象的几个字段,我们发现可以将 Funnel 对象的内容按字段存储到一 个 hash 结构中,灌水的时候将 hash 结构的字段取出来进行逻辑运算后,再将新值回填到 hash 结构中就完成了一次行为频度的检测。
但是有个问题,我们无法保证整个过程的原子性。从 hash 结构中取值,然后在内存里 运算,再回填到 hash 结构,这三个过程无法原子化,意味着需要进行适当的加锁控制。而 一旦加锁,就意味着会有加锁失败,加锁失败就需要选择重试或者放弃。
如果重试的话,就会导致性能下降。如果放弃的话,就会影响用户体验。同时,代码的 复杂度也跟着升高很多。这真是个艰难的选择,我们该如何解决这个问题呢?Redis-Cell 救 星来了!
Redis-Cell
Redis 4.0 提供了一个限流 Redis 模块,它叫 redis-cell。该模块也使用了漏斗算法,并 提供了原子的限流指令。有了这个模块,限流问题就非常简单了。
安装:
linux下载地址:https://github.com/brandur/redis-cell/releases/download/v0.2.5/redis-cell-v0.2.5-x86_64-unknown-linux-gnu.tar.gz
解压后获取libredis_cell.so或libredis_cell.dylib文件。
添加redis.conf配置,启动redis即可
loadmodule ../etc/libredis_cell.dylib
基本使用:
该模块只有 1 条指令 cl.throttle,它的参数和返回值都略显复杂,接下来让我们来看看这 个指令具体该如何使用。
#语法
cl.throttle key 令牌桶容量-1 令牌产生个数 令牌产生时间 本次取走的令牌数(默认1,负数为放入令牌)
#返回
0成功,1失败
令牌桶容量
当前桶内剩余的令牌数
成功时该值为-1,失败时表示还需要等待多少秒可以有足够的令牌
表示预计多少秒后令牌桶会满
>c6.throttle user:reply 15 30 60 1
1) (integer) 0 # 0 表示允许,1 表示拒绝
2) (integer) 16 # 漏斗容量 capacity
3) (integer) 15 # 漏斗剩余空间 left_quota
4) (integer) -1 # 如果拒绝了,需要多长时间后再试(漏斗有空间了,单位秒)
5) (integer) 2 # 多长时间后,漏斗完全空出来(left_quota==capacity,单位秒)
上面这个指令的意思是允许「用户」的频率为每 60s 最多 30 次(漏水速 率),漏斗的初始容量为 15,也就是说一开始可以连续回复 15 个帖子,然后才开始受漏水 速率的影响。
也可以理解为:
桶容量为16,每60秒产生30个令牌。本次取走一个,还剩15个令牌,2秒后令牌将桶装满(0.5s产生一个令牌)。
在执行限流指令时,如果被拒绝了,就需要丢弃或重试。cl.throttle 指令考虑的非常周 到,连重试时间都帮你算好了,直接取返回结果数组的第四个值进行 sleep 即可,如果不想 阻塞线程,也可以异步定时任务来重试
Java实现:
package com.example.demo.book;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.commands.JedisCommands;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @Author: acton_zhang
* @Date: 2023/4/17 2:27 上午
* @Version 1.0
*/
public class RedisCellDistributedRateLimiter {
//jedis命令客户端
protected JedisCommands jedis;
//自定义限流key
protected String key;
//时间窗口 单位秒
protected int period;
//最大令牌容量
protected int maxCapacity;
public RedisCellDistributedRateLimiter(JedisCommands jedis, String key, int period, int maxCapacity) {
this.jedis = jedis;
this.key = key;
this.period = period;
this.maxCapacity = maxCapacity;
}
public boolean tryAcquire(int quote) throws IOException {
List<String> keys = new ArrayList<>();
keys.add(key);
List<String> argvs = new ArrayList<>();
argvs.add(String.valueOf(maxCapacity));
argvs.add(String.valueOf(maxCapacity));
argvs.add(String.valueOf(period));
argvs.add(String.valueOf(quote));
Object result = null;
if (jedis instanceof Jedis) {
result = ((Jedis) this.jedis).eval(LUA_SCRIPT, keys, argvs);
} else if (jedis instanceof JedisCluster) {
result = ((JedisCluster) this.jedis).eval(LUA_SCRIPT, keys, argvs);
} else {
throw new RuntimeException("redis instance is error") ;
}
return ((List<Long>)result).get(0) == 0;
}
//lua脚本
public static final String LUA_SCRIPT = "local key = KEYS[1]\n"+
"local init_burst = tonumber(ARGV[1])\n"+
"local max_burst = tonumber(ARGV[2])\n"+
"local period = tonumber(ARGV[3])\n"+
"local quota = ARGV[4]\n"+
"return redis.call('CL.THROTTLE',key,init_burst,max_burst,period,quota)";
public static void main(String[] args) throws Exception{
//构造Jedis集群
// Set<HostAndPort> nodeList = new HashSet<>();
// nodeList.add( new HostAndPort("127.0.0.1", 6237));
// JedisCluster jedisCluster =
// new JedisCluster(nodeList,10000,10000,10,"****", new JedisPoolConfig());
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.auth("123456");
String key = "redis-cell-test";
int period = 5;
int maxCapacity = 1;
//构造限流器
RedisCellDistributedRateLimiter limiter =
new RedisCellDistributedRateLimiter(jedis, key, period, maxCapacity);
//计数器
int pass = 0;
int nopass = 0;
for (;;){
Thread.sleep(1000);
if (limiter.tryAcquire(1)){
pass++;
System.out.println("第" + pass + "次通过限流");
}else {
nopass++;
System.out.println("第" + nopass + "次无法通过");
}
}
}
}
第1次通过限流
第2次通过限流
第1次无法通过
第2次无法通过
第3次无法通过
第3次通过限流
第4次无法通过
第5次无法通过
第6次无法通过
第7次无法通过
第4次通过限流
第8次无法通过
第9次无法通过
第10次无法通过
第11次无法通过
第5次通过限流
第12次无法通过
第13次无法通过
第14次无法通过
平均4秒通过一次。