Redis限流插件-redis-cell
redis限流插件作者:
redis-cell 是一个用rust语言编写的基于令牌桶算法的的限流模块,提供原子性的限流功能,并允许突发流量,可以很方便的应用于分布式环境中。
下载redis-cell插件
访问Releases · brandur/redis-cell (github.com)
上传redis-cell插件到linux服务器
![image-20231126171232661](https://img-blog.csdnimg.cn/img_convert/3ddb89237a63e15f942cd0de491da5f9.png)
解压插件
[root@localhost plugin]# tar -zxvf redis-cell-v0.3.1-x86_64-unknown-linux-gnu.tar.gz
libredis_cell.d
libredis_cell.so
拷贝libredis_cell.so到redis容器中
docker cp libredis_cell.so redis_6390:/usr/local/etc/redis
修改后redis.conf文件
在redis.conf配置文件中引用插件
38 ################################## MODULES #####################################
39
40 # Load modules at startup. If the server is not able to load modules
41 # it will abort. It is possible to use multiple loadmodule directives.
42 #
43 # loadmodule /path/to/my_module.so
44 loadmodule /usr/local/etc/redis/libredis_cell.so
![image-20231126171836524](https://img-blog.csdnimg.cn/img_convert/70c20357dd919443b88652e575264f86.png)
插件使用
查询插件是否已启用
输入命令: module list
127.0.0.1:6379> module list
1) 1) "name"
2) "redis-cell"
3) "ver"
4) (integer) 1
![image-20231126173521851](https://img-blog.csdnimg.cn/img_convert/bbc61b9a65765ca45005d67cefd1a013.png)
语法
![image-20231126173228028](https://img-blog.csdnimg.cn/img_convert/5db3fc062b17b0628d7972caa30fc5e6.png)
127.0.0.1:6379> cl.throttle mytest 99 5 100 2
1) (integer) 0 #0 表示成功, 1表示失败
2) (integer) 100 # 令牌桶的容量
3) (integer) 98 # 当前令牌桶的令牌数
4) (integer) -1 # 成功时该值为-1,失败时表还需要等待多少秒可以有足够的令牌
5) (integer) 41 # 预计多少秒后令牌桶会满
测试
多次从令牌桶取出数据
127.0.0.1:6379> cl.throttle mytest 99 5 100 40
1) (integer) 0
2) (integer) 100
3) (integer) 54
4) (integer) -1
5) (integer) 911
127.0.0.1:6379> cl.throttle mytest 99 5 100 40
1) (integer) 0
2) (integer) 100
3) (integer) 14
4) (integer) -1
5) (integer) 1708
127.0.0.1:6379> cl.throttle mytest 99 5 100 40
1) (integer) 1 #失败,拒绝取出
2) (integer) 100
3) (integer) 14
4) (integer) 505 # 取出失败,令牌桶还有14个令牌,还需505秒才能够取出
5) (integer) 1705
springboot使用令牌
引入redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
yml配置
server:
port: 10022
spring:
redis:
host: 192.168.198.128
port: 6390
redis配置
package com.wnhz.redis.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
}
测试
@PostMapping("rush")
public HttpResp rush(){
//对接口进行限流操作
//检查令牌桶,返回的第一值是否为 0: 0-流量够,1-限流中
String script = "return redis.call('cl.throttle',KEYS[1],ARGV[1],ARGV[2],ARGV[3],ARGV[4])";
List<String> keys = new ArrayList<>();
keys.add("redbag");
String maxBurst = "99"; //漏洞容量
String countPerPeriod = "10";
String period = "100";
String quantity = "10";
List<Long> list = stringRedisTemplate.execute(
new DefaultRedisScript<>(script,List.class) ,
keys,
maxBurst, countPerPeriod, period,
quantity
);
log.debug("限流产假返回结果:{}",list);
if(!list.isEmpty() && list.get(0)==0){
return HttpResp.success("抢红包成功,剩余红包:"+(--num));
}else{
return HttpResp.failed("当前抢红包人数过多,请2分钟之后再来");
}
}
运行结果 [0, 100, 90, -1, 101]
redis-cell封装
/**
* @param key redis key
* @param maxBurst 令牌桶最大容量
* @param countPerPeriod 通过的令牌桶数(countPerPeriod/period 速率)
* @param period 时间
* @param quantity 取出数量
* @return List 返回集合中5个Long值
* 第0个位置: 0 表示成功, 1表示失败
* 第1个位置: 令牌桶的容量
* 第2个位置: 当前令牌桶的令牌数
* 第3个位置: 成功时该值为-1,失败时表还需要等待多少秒可以有足够的令牌
* 第4个位置: 预计多少秒后令牌桶会满
*/
public List<Long> redisCell(String key,
String maxBurst,
String countPerPeriod,
String period,
String quantity) {
final String script = "return redis.call('cl.throttle',KEYS[1],ARGV[1],ARGV[2],ARGV[3],ARGV[4])";
connect();
List<Long> eval = this.connection.sync()
.eval(script, ScriptOutputType.MULTI,
new String[]{key},
maxBurst,
countPerPeriod,
period,
quantity);
returnPool();
return eval;
}
使用AOP应用限流插件
使用限流插件+aop对接口进行限流案例
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
@Aspect
public class FlowLimitAop {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
/**
* 使用aop前切对接口进行限流
*/
@Before("execution(* com.wnhz.ssc.actitivity.controller.*.*(..))")
public void flowLimitAdvice(){
System.out.println("我准备切接口了....");
String script = "return redis.call('CL.THROTTLE',KEYS[1], ARGV[1], ARGV[2], ARGV[3], ARGV[4])";
List list = redisTemplate.execute(
new DefaultRedisScript<>(script, List.class),
new ArrayList<String>(){{
add("older:activity");
}},
99,10,100,10);
System.out.println(list.get(0));
if(0 != (Long)list.get(0)){ //令牌桶有令牌,可以放行
throw new ActivityException("当前祈福人数过多,请等一会儿再来....");
}
}
}