分布式限流方案:基于 Redis 的令牌桶算法实现
- 前言
- 一、原理介绍:令牌桶算法
- 二、分布式限流的设计思路
- 三、代码实现
- 四、方案优缺点
- [五、 适用场景](#五、 适用场景)
- 总结
前言
在分布式场景下,接口限流变得更加复杂。传统的单机限流方式难以满足跨节点的限流需求,因此需要一种分布式限流方案。
这里介绍一种基于 Redis 和 Redisson 实现的令牌桶算法分布式限流方案。
一、原理介绍:令牌桶算法
令牌桶算法是一种用于控制流量的经典算法,其基本原理如下:
-
生成令牌:按照固定的速率向令牌桶中放入令牌。
-
消耗令牌:每个请求到来时需要消耗一个令牌才能执行。
-
桶满时丢弃令牌:如果令牌桶已满,额外生成的令牌会被丢弃。
-
拒绝无令牌请求:当令牌桶为空且有请求到达时,拒绝该请求。
示意图
lua
+--------------------------+
| 请求到达 |
+--------------------------+
|
V
+----------------------------+
| 令牌桶中是否有令牌? |
+----------------------------+
/ \
是 否
/ \
+--------------------+ +----------------------+
| 消耗令牌,放行 | | 拒绝请求,限流 |
+--------------------+ +----------------------+
二、分布式限流的设计思路
在分布式环境中,多个节点需要共享限流状态。为了解决这个问题,我们采用 Redis 作为分布式存储,并通过 Redisson 的 RRateLimiter
组件实现分布式的令牌桶限流:
- Redis 统一存储令牌桶状态:
- 使用 Redis 的
RRateLimiter
对象存储令牌桶的容量和剩余令牌数。
- 多节点共享限流状态:
- 各个服务节点通过 Redis 读取和更新令牌桶状态,实现跨节点的流量控制。
- 动态配置更新:
- 支持从 Redis 中动态获取限流配置,实现限流规则的热更新。
- 基于 IP + 接口路径的粒度限流:
- 使用
api_limit:ip:apiPath
作为 Redis 的 Key,针对不同接口和 IP 进行精细化限流。
三、代码实现
-
初始化令牌桶
java/** * 获取指定接口的令牌桶(每个接口独立一个) * * @param apiKey 接口唯一标识 * @param rate 允许的请求数 * @param interval 时间窗口(秒) * @return RRateLimiter 令牌桶实例 */ public RRateLimiter getRateLimiter(String apiKey, int rate, int interval) { return rateLimiterCache.compute(apiKey, (key, existingLimiter) -> { String redisKey = String.format("%s:%s", RedisKeyConstant.DOC_RATE_LIMIT_PRE, key); RRateLimiter rateLimiter = redissonClient.getRateLimiter(redisKey); // 获取 Redis 中的当前限流配置 List<Integer> config = getRateLimiterConfig(redisKey); Integer currentRate = config.get(0); Integer currentInterval = config.get(1); // 检查是否需要重新初始化 if (existingLimiter != null && existingLimiter.isExists() && Objects.equals(currentRate, rate) && Objects.equals(currentInterval, interval)) { return existingLimiter; } log.warn("检测到限流配置变化或 RateLimiter 失效,重新初始化令牌桶 [{}]", redisKey); // 重新初始化令牌桶 rateLimiter.delete(); if (!rateLimiter.trySetRate(RateType.OVERALL, rate, interval, RateIntervalUnit.SECONDS)) { log.error("令牌桶 [{}] 初始化失败", redisKey); return null; } log.info("创建令牌桶 [{}],QPS: {}, 时间窗口: {} 秒", apiKey, rate, interval); return rateLimiter; }); }
-
申请令牌
java/** * 申请一个令牌 * * @param ip 接口唯一标识 * @param apiPath 接口唯一标识 * @param rate 允许的请求数 * @param interval 时间窗口(秒) * @return 是否成功获取令牌 */ public boolean tryAcquire(String ip, String apiPath, int rate, int interval) { String apiKey = "api_limit:" + ip + ":" + apiPath; RRateLimiter rateLimiter = getRateLimiter(apiKey, rate, interval); if (rateLimiter == null) { return false; } boolean acquired = rateLimiter.tryAcquire(); // Redis Key 可能被删除,需要重新初始化 if (!acquired && !rateLimiter.isExists()) { log.warn("检测到 RateLimiter [{}] 失效,重新初始化", apiKey); rateLimiterCache.remove(apiKey); rateLimiter = getRateLimiter(apiKey, rate, interval); if (rateLimiter != null) { acquired = rateLimiter.tryAcquire(); } } if (!acquired) { log.warn("接口 [{}] 触发限流,QPS: {}, 时间窗口: {} 秒", apiKey, rate, interval); } return acquired; }
-
统一限流校验方法
java/** * 令牌限流检查(对外暴露的方法) * @param rate 允许的请求数 * @param interval 时间窗口(秒) * @throws ServiceException 如果触发限流 */ public void checkRateLimit(int rate, int interval) { String clientIp = WebTool.getRealIpAddress(); // 获取真实 IP String apiPath = WebTool.getApiPath(); // 获取接口路径 boolean allowed = tryAcquire(clientIp, apiPath, rate, interval); if (!allowed) { log.warn("ip: {}, 接口 [{}] 触发限流,QPS: {}, 时间窗口: {} 秒", clientIp, apiPath, rate, interval); String msg = String.format("访问过于频繁,请稍后再试。IP: %s", clientIp); throw new ServiceException(msg); } }
✅ 方案解析
1. 分布式环境下的限流
-
使用 Redis 作为中心存储,每个接口的令牌桶都存储在 Redis 中,便于多个节点共享限流状态。
-
使用 Redisson 提供的
RRateLimiter
对象,它基于 Redis 提供了令牌桶算法的封装,自动管理令牌生成和消费的过程。
2. 令牌桶的管理
-
通过
getRateLimiter
方法动态创建和管理令牌桶。 -
使用
rateLimiter.trySetRate()
设置令牌桶的容量和生成速率。 -
每次请求前调用
rateLimiter.tryAcquire()
尝试获取一个令牌,如果成功则执行请求,否则拒绝。
3. 动态配置管理
-
使用 Redis hget 命令读取当前限流配置(
rate
和interval
),确保分布式环境下的限流配置保持一致。 -
如果发现 Redis 中的配置与本地配置不一致或令牌桶失效,则重新初始化令牌桶。
4. IP + 接口路径粒度限流
- 每个接口的限流是基于
api_limit:ip:apiPath
作为 Redis 的 Key,实现了IP + 接口级别的限流,可有效防止单个 IP 的恶意请求。
四、方案优缺点
优点 | 缺点 |
---|---|
支持分布式环境,多节点共享限流状态 | 依赖 Redis,如果 Redis 异常会影响限流功能 |
支持突发流量,平滑处理请求 | 需要额外维护 Redis 的资源占用 |
支持动态限流配置,实时生效 | 需要额外监控 Redis 的健康状态 |
提供接口级和 IP 级别的精细化限流 | 配置不当可能导致限流过于宽松或过于严格 |
五、 适用场景
-
API 网关限流:在 API 网关层通过该方案对外部流量进行限流,保护后端服务。
-
防止恶意攻击:防止某个 IP 针对特定接口的恶意请求。
-
限流突发流量:在秒杀、促销等场景中平滑处理流量峰值。
-
支付接口保护:确保支付接口在高并发情况下依旧可用。
总结
基于 Redis 的分布式令牌桶限流方案是一个可靠且高效的限流策略。它不仅能够有效应对突发流量,还能在分布式环境下保持限流配置一致性。
通过合理的配置和监控,可以保障系统的稳定性,提升用户体验。😊