分布式限流器框架 eval-rate-limiter
分布式限流器框架 eval-rate-limiter前言
基于 redis 实现的分布式限流器,实现如下效果
[*]对每个应用节点进行限流
[*]有需要的可以根据现实业务定制 rateLimiterKey 的生成规则,目前是针对 ip+port 进行限流
[*]控制限流的速率->次数/分钟
源码已上传 github
[*]https://github.com/huajiexiewenfeng/eval-rate-limiter
设计
流程图
https://i-blog.csdnimg.cn/direct/43b6e6ec67364f5786b462758e5d00aa.png
[*] 时间窗口&最大条数可以设置
[*] 接纳 redis 的过期时间+increment 来实现时间窗口和计数功能
[*] synchronized 防止多线程获取 count 产生一致性问题
[*] key 接纳实例的 host+port 来实现各个实例控制本身的发送速率
核心方法
tryAcquire 获取通信证
[*]获取当前的请求次数 currentCount
[*]比较 currentCount 和 MaxCount 的大小
[*]小于,那么 count++,返回 true,表现获取通行证成功
[*]否则,返回 false,表现获取通信证失败
/**
* 尝试获取限流通行证
*
* @return true表示允许通过,false表示被限流
*/
public synchronized boolean tryAcquire() {
if (!properties.getEnable()) {
return true;
}
try {
int currentCount = getCurrentRequestCount();
if (currentCount >= properties.getMaxCount()) {
logger.warn("Rate limit exceeded - window: {} minutes, max: {}, current: {}",
properties.getWindowMinutes(),
properties.getMaxCount(),
currentCount);
return false;
}
incrementRequestCount();
return true;
} catch (Exception e) {
logger.error("Failed to acquire rate limit token for key: {}", rateLimiterKey, e);
// 在限流器异常时默认放行,保证系统可用性
return true;
}
}
增加访问次数 incrementRequestCount
[*]利用 redis increment 计数+1
[*]第一次设置过期时间为时间窗口
private void incrementRequestCount() {
Long count = valueOperations.increment(rateLimiterKey, 1);
if (count != null && count == 1) {
// 首次设置时初始化过期时间
redisTemplate.expire(rateLimiterKey, properties.getWindowMinutes(), TimeUnit.MINUTES);
}
}
生身分布式 key generateRateLimiterKey
private String generateRateLimiterKey() {
String port = environment.getProperty("server.port", "unknown");
String host = getLocalHostAddress();
return String.format("%s:%s:%s", RATE_LIMITER_KEY_PREFIX, host, port);
}
测试
测试代码
@SpringBootApplication
@EnableEvalRateLimiter
@RestController
@RequestMapping("/demo1")
public class Demo1Application {
@Autowired
private RateLimiter rateLimiter;
public static void main(String[] args) {
SpringApplication.run(Demo1Application.class, args);
}
@GetMapping("/test")
public String test(@RequestParam(value = "count", defaultValue = "105") int count) {
for (int i = 0; i < count; i++) {
doTest(i);
}
return "hello";
}
private void doTest(int count) {
System.out.println("当前请求次数:" + count + ",节点限流剩余次数:" + rateLimiter.getRemainingRequests());
if (!rateLimiter.tryAcquire()) {
// 5秒后再发送
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
doTest(count);
}
}
}
application.properties 设置
spring.application.name=${APPLICATION_NAME:demo1}
server.port=${SERVER_PORT:8091}
#redis
eval.rate.limiter.redis.database=${REDIS_DB_INDEX:2}
eval.rate.limiter.redis.host=${REDIS_HOST:127.0.0.1}
eval.rate.limiter.redis.port=${REDIS_PORT:6379}
eval.rate.limiter.redis.password=${REDIS_INFRA_PASSWORD:1234567a}
#1分钟
eval.rate.limiter.redis.windowMinutes=${EVAL_RATE_LIMITER_WINDOW_MINUTES:1}
#100次
eval.rate.limiter.redis.maxCount=${EVAL_RATE_LIMITER_MAX_COUNT:100}
结果
https://i-blog.csdnimg.cn/direct/33adf5a89d394354837d3bce3e4ce28e.png
到 1 分钟时间之后,又重新计数
https://i-blog.csdnimg.cn/direct/44663629e55c47f78b69938bfe369b42.png
Redis 客户端
[*]key:host+ip,限制每个机器+端口
[*]value:每访问一次,value+1,value 到达100(设置)后,不在增加
https://i-blog.csdnimg.cn/direct/4e94f9f2a0ee4c5798dcc208b62979f9.png
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]