跳到主要内容

限流方案设计

问题

如何设计一个高可用的限流系统?

答案

四种限流算法

算法原理优点缺点
固定窗口时间窗口内计数实现简单窗口边界有突刺
滑动窗口细分子窗口滑动更平滑内存稍高
漏桶固定速率处理流量平整无法应对突发
令牌桶固定速率放令牌允许突发实现稍复杂

令牌桶算法实现

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 GatewayIP、路由级别
应用层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 RateLimiterRedis + Lua
精度单实例精确集群级精确
性能无网络开销Redis 网络延迟
场景单体应用微服务集群

Q4: 限流后怎么处理?

答案

  • 快速失败:直接返回 429 Too Many Requests
  • 排队等待:放入队列慢慢处理
  • 降级返回:返回缓存数据或默认值
  • 生产建议:对用户请求快速失败 + 友好提示,对内部调用可排队等待

相关链接