分布式限流器框架 eval-rate-limiter

分布式限流器框架 eval-rate-limiter

文章目录

    • [分布式限流器框架 eval-rate-limiter](#分布式限流器框架 eval-rate-limiter)
    • 前言
    • 设计
    • 核心方法
      • [tryAcquire 获取通信证](#tryAcquire 获取通信证)
      • [增加访问次数 incrementRequestCount](#增加访问次数 incrementRequestCount)
      • [生成分布式 key generateRateLimiterKey](#生成分布式 key generateRateLimiterKey)
    • 测试

前言

基于 redis 实现的分布式限流器,实现如下效果

  • 对每个应用节点进行限流
    • 有需要的可以根据实际业务定制 rateLimiterKey 的生成规则,目前是针对 ip+port 进行限流
  • 控制限流的速率->次数/分钟

源码已上传 github

设计

流程图

  • 时间窗口&最大条数可以配置

  • 采用 redis 的过期时间+increment 来实现时间窗口和计数功能

  • synchronized 防止多线程获取 count 产生一致性问题

  • key 采用实例的 host+port 来实现各个实例控制自己的发送速率

核心方法

tryAcquire 获取通信证

  • 获取当前的请求次数 currentCount
  • 比较 currentCount 和 MaxCount 的大小
    • 小于,那么 count++,返回 true,表示获取通行证成功
    • 否则,返回 false,表示获取通信证失败
java 复制代码
    /**
     * 尝试获取限流通行证
     *
     * @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
  • 第一次设置过期时间为时间窗口
java 复制代码
    private void incrementRequestCount() {
        Long count = valueOperations.increment(rateLimiterKey, 1);
        if (count != null && count == 1) {
            // 首次设置时初始化过期时间
            redisTemplate.expire(rateLimiterKey, properties.getWindowMinutes(), TimeUnit.MINUTES);
        }
    }

生成分布式 key generateRateLimiterKey

java 复制代码
    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);
    }

测试

测试代码

java 复制代码
@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 配置

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}

结果

到 1 分钟时间之后,又重新计数

Redis 客户端

  • key:host+ip,限制每个机器+端口
  • value:每访问一次,value+1,value 到达100(配置)后,不在增加
相关推荐
jstart千语4 小时前
【Redisson】锁可重入原理
redis·分布式·redisson
暗离子跃迁7 小时前
达梦数据库单机部署dmhs同步复制(dm8->kafka)
linux·运维·数据库·分布式·学习·kafka·达梦数据库
代码的知行者10 小时前
分布式数据库中间件-Sharding-JDBC
数据库·分布式·中间件
啾啾Fun10 小时前
Java面试题:分布式ID时钟回拨怎么处理?序列号耗尽了怎么办?
java·分布式·分布式id·八股
计算机毕设定制辅导-无忧学长10 小时前
Kafka 可靠性保障:消息确认与事务机制(一)
分布式·kafka·linq
掘金-我是哪吒14 小时前
分布式微服务系统架构第145集:Jeskson文档-微服务分布式系统架构
分布式·微服务·云原生·架构·系统架构
G探险者19 小时前
为什么 Zookeeper 越扩越慢,而 Nacos 却越扩越快?
分布式·后端
Pitayafruit21 小时前
跟着大厂学架构01:如何利用开源方案,复刻B站那套“永不崩溃”的评论系统?
spring boot·分布式·后端
苏格拉没有底_coder1 天前
引入 Kafka 消息队列解耦热点操作
分布式·kafka
顧棟1 天前
Zookeeper 3.8.4 安装部署帮助手册
分布式·zookeeper