环境要求
基于redis 4.0 的单点或者集群
GLIBC_2.18
redis-cell使用说明
命令示例:CL.THROTTLE key_test 100 400 60 3
CL.THROTTLE: redis命令
key_test : redis key
100: 官方叫
max_burst
,初始值,不能为0200: 在指定时间窗口内允许访问的次数
60: 指定的时间窗口,单位:秒
3: 表示本次要申请的令牌数,不写则默认为 1
响应如下:
127.0.0.1:6379> CL.THROTTLE test 100 400 60 3
1) (integer) 0
2) (integer) 101
3) (integer) 98
4) (integer) -1
5) (integer) 0
解释:
1): 是否成功,0:成功,1:拒绝
2): 令牌桶的容量,大小为初始值+1
3): 当前令牌桶中可用的令牌
4): 若请求被拒绝,这个值表示多久后才令牌桶中会重新添加令牌,单位:秒,可以作为重试时间
5): 表示多久后令牌桶中的令牌会存满
redis-cell安装
# 下载
wget 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
# 解压
tar -zxvf redis-cell/releases/download/v0.2.5/redis-cell-v0.2.5-x86_64-unknown-linux-gnu.tar.gz
# 修改redis.conf文件配置,新增配置
loadmodule ${libredis_cell.so路径}
比如:
如果是redis集群,则所有node均需要如上安装。
重启redis后可执行 CL.THROTTLE key_test 100 400 60 3,如果正确输出则说明redis-cell安装成功。
备注:可能在配置redis-cell后redis重启失败,查看redis日志如下图示:
可知需要安装GLIBC 2.18,详情参考:
https://blog.csdn.net/qq_39295044/article/details/86685789
JAVA代码实现
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisCommands;
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;
}
/**
* 尝试申请资源
* @param quote 目标资源数
* @return 是否申请成功
*/
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;
}
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("192.168.181.1", 6237));
JedisCluster jedisCluster =
new JedisCluster(nodeList,10000,10000,10,"****", new JedisPoolConfig());
String key = "redis-cell-test";
int period = 5;
int maxCapacity = 1;
//构造限流器
RedisCellDistributedRateLimiter limiter =
new RedisCellDistributedRateLimiter(jedisCluster, key, period, maxCapacity);
for (;;){
CommonMethod.sleep(1000);
if (limiter.tryAcquire(1)){
System.out.println(("通过限流"));
}else {
System.out.println(("无法通过"));
}
}
}
}
控制台输出:
> 通过限流
> 通过限流
> 无法通过
> 无法通过
> 无法通过
> 通过限流
> 无法通过
> 无法通过
> 无法通过
> 无法通过
> 通过限流
> 无法通过
> 无法通过
> 无法通过
> 无法通过
> 通过限流
> 无法通过
> 无法通过
> 无法通过
> 无法通过
> 通过限流
> 无法通过
> 无法通过
> 无法通过
> 无法通过
> 通过限流
> 无法通过
每隔4秒通过1次。