Skip to content

什么是限流?限流算法有哪些?怎么实现的? ​

  • 标签:后端、系统设计、场景题

🏛 业务架构师 ​

限流是主动拒绝超出容量的流量,保住系统活着的手段——过载时拒绝一部分请求,好过全部请求都死。四个基础算法按演进讲:

  • 固定窗口计数:每分钟一个计数器。简单,但窗口边界能放过双倍流量(59 秒和 61 秒各打满一个窗口);
  • 滑动窗口:窗口切成小格滚动统计,抹平边界毛刺——精度与内存的折中,大多数接口限流够用;
  • 漏桶:请求进桶,恒定速率流出——绝对平滑,但无法应对突发(秒杀开抢瞬间的合理突发也被削平);
  • 令牌桶(最常用):恒速放令牌、桶有容量——允许累积的突发(闲时攒的令牌应对短时洪峰),兼顾平滑与弹性。

实现分两级:

  • 单机:Guava RateLimiter(令牌桶);
  • 分布式:Redis + Lua("读计数-判断-自增-设过期"原子化)——全局限流必须原子,否则限流器自己就是竞态源。

业务架构的三条实战原则:

  1. 阈值从压测来,不从拍脑袋来:容量压测得出单机 800 QPS,集群阈值 = 800 × N × 0.8 水位系数——限流值是容量工程的输出;
  2. 分层限流各守一段:网关全局限流(总闸)→ 服务级(按下游容量)→ 接口级(核心/非核心差异阈值)→ 数据库连接池(资源级)——每层保护对应的资源,只在一层限流等于只装一道闸;
  3. 被限的流量要有业务去向:返回友好提示("当前排队人数较多")、降级到静态页、引导错峰——限流是业务动作,不是 throw exception。

一句话:限流是容量与流量的对账机制——知道自己的容量,才谈得上拒绝的艺术。

🏗 系统架构师 ​

限流是过载保护的第一道闸:超过系统容量的请求主动拒绝,保住系统与核心用户体验。常见算法四种,各有适用:

  • 固定窗口计数:每分钟计数器,超阈值拒绝。实现最简单(Redis INCR + EXPIRE),缺陷是临界突刺——第 59 秒与下一分钟第 1 秒可放入双倍流量;
  • 滑动窗口:把窗口切小格(如 6 个 10 秒)滚动统计,或用 ZSet(score=时间戳) 每次删窗口外数据再计数——平滑临界问题,精度与内存随格子数上升,是网关/API 限流主力;
  • 漏桶:请求进桶,恒定速率流出(排队或丢弃),输出绝对平滑——适合保护脆弱下游(如第三方接口只能 100 QPS);Guava RateLimiter 便是此型;
  • 令牌桶:恒速生成令牌入桶(桶可积压),请求拿到令牌才放行——允许突发(积压的令牌一次用完),符合互联网流量特征,Google Guava、Sentinel、Redis+Lua 的主流选择。

分布式实现:Redis + Lua 把"取令牌/查窗口 + 计数"原子化,所有实例共用一套配额;性能敏感时退化为"本地预扣 + 定期同步"(每实例领一段配额自行分配),牺牲一点精度换零网络开销。

工程实践:多级限流(网关粗 → 服务细 → 接口/用户维度);阈值来自压测容量而非拍脑袋;被限请求返回 429 + Retry-After;限流计数打监控——限流阈值是被打出来的,不是算出来的。

💻 全栈开发专家 ​

限流 = 限制单位时间内的请求量,保护系统不被过载打死(过载时延迟重试雪崩,宁可排队/拒绝,保住可用性)。

四种经典算法:

  1. 固定窗口计数:INCR + EXPIRE,简单但有临界突刺(窗口交界处 2 倍流量)。
  2. 滑动窗口:把窗口切小格子滚动统计(Sentinel 默认),平滑了突刺。
  3. 漏桶:请求进桶,恒定速率流出——绝对平滑但无法应对突发。
  4. 令牌桶:恒速放令牌,桶满暂存——允许累积的突发,最常用。

分布式实现:Redis + Lua 令牌桶:

lua
-- KEYS[1]=rate key, ARGV: rate(个/秒) capacity(桶容) now(秒) requested(数量)
local rate, capacity = tonumber(ARGV[1]), tonumber(ARGV[2])
local now, req = tonumber(ARGV[3]), tonumber(ARGV[4])
local d = redis.call('HMGET', KEYS[1], 'tokens', 'ts')
local tokens = math.min(capacity, (d[1] or capacity) + (now - (d[2] or now)) * rate)
if tokens < req then return 0 end
redis.call('HMSET', KEYS[1], 'tokens', tokens - req, 'ts', now)
redis.call('EXPIRE', KEYS[1], 60)
return 1

单机:Guava RateLimiter(令牌桶)/ Resilience4j;集群 + 规则动态:Sentinel(控制台流控规则热更新,QPS/线程数/关联/链路多种模式)。

我的分层落地:网关粗粒度(全局 + 用户级 IP 级)→ 服务层 Sentinel 细粒度(接口/资源级)→ 关键依赖(DB、下游 RPC)信号量兜底。限流阈值靠压测定(容量的 70~80%),过载返回 429 + 友好提示,别让用户看到 500。