限流方案设计
问题
如何设计一个高可用的限流系统?
答案
四种限流算法
| 算法 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 固定窗口 | 时间窗口内计数 | 实现简单 | 窗口边界有突刺 |
| 滑动窗口 | 细分子窗口滑动 | 更平滑 | 内存稍高 |
| 漏桶 | 固定速率处理 | 流量平整 | 无法应对突发 |
| 令牌桶 | 固定速率放令牌 | 允许突发 | 实现稍复杂 |
令牌桶算法实现
Guava RateLimiter 使用
// 每秒 100 个令牌
RateLimiter rateLimiter = RateLimiter.create(100);
public Response handleRequest(Request request) {
if (!rateLimiter.tryAcquire(1, 500, TimeUnit.MILLISECONDS)) {
throw new RateLimitException("请求过于频繁");
}
return process(request);
}
滑动窗口 —— Redis 实现
Redis ZSet 滑动窗口限流
public boolean isAllowed(String key, int maxCount, int windowSeconds) {
long now = System.currentTimeMillis();
String redisKey = "rate:" + key;
// Lua 脚本保证原子性
String luaScript = """
-- 移除窗口外的记录
redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, ARGV[1])
-- 当前窗口内的请求数
local count = redis.call('ZCARD', KEYS[1])
if count < tonumber(ARGV[2]) then
redis.call('ZADD', KEYS[1], ARGV[3], ARGV[3])
redis.call('EXPIRE', KEYS[1], ARGV[4])
return 1
end
return 0
""";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(luaScript, Long.class),
List.of(redisKey),
String.valueOf(now - windowSeconds * 1000L),
String.valueOf(maxCount),
String.valueOf(now),
String.valueOf(windowSeconds)
);
return result != null && result == 1;
}
分布式限流架构
| 层级 | 工具 | 维度 |
|---|---|---|
| 网关层 | Nginx limit_req / Spring Cloud Gateway | IP、路由级别 |
| 应用层 | Guava RateLimiter / Sentinel | 接口、用户级别 |
| 分布式 | Redis Lua | 集群统一计数 |
Sentinel 限流配置
Sentinel 注解方式
@SentinelResource(
value = "createOrder",
blockHandler = "handleBlock",
fallback = "handleFallback"
)
public Order createOrder(OrderRequest request) {
return orderService.create(request);
}
// 限流触发时的降级处理
public Order handleBlock(OrderRequest request, BlockException e) {
throw new ServiceException("系统繁忙,请稍后重试");
}
常见面试问题
Q1: 令牌桶和漏桶的区别?
答案:
- 漏桶:以固定速率处理请求,多余的排队或丢弃 → 流量绝对平滑
- 令牌桶:以固定速率放令牌,桶满时停止放 → 允许突发流量(桶内有多余令牌时可一次取多个)
生产中令牌桶更常用,因为实际业务需要一定突发能力。
Q2: 固定窗口的临界问题是什么?
答案:
假设限制 1 分钟 100 次。在第 1 分钟的最后 1 秒发 100 次,第 2 分钟的第 1 秒又发 100 次 → 2 秒内实际 200 次,超过预期。滑动窗口把大窗口细分为多个小窗口解决此问题。
Q3: 单机限流 vs 分布式限流?
答案:
| 对比 | 单机限流 | 分布式限流 |
|---|---|---|
| 工具 | Guava RateLimiter | Redis + Lua |
| 精度 | 单实例精确 | 集群级精确 |
| 性能 | 无网络开销 | Redis 网络延迟 |
| 场景 | 单体应用 | 微服务集群 |
Q4: 限流后怎么处理?
答案:
- 快速失败:直接返回 429 Too Many Requests
- 排队等待:放入队列慢慢处理
- 降级返回:返回缓存数据或默认值
- 生产建议:对用户请求快速失败 + 友好提示,对内部调用可排队等待