面试考察点
- 是否先从下游容量和业务优先级确定阈值。
- 能否比较常见限流算法的流量形态。
- 是否设计网关、服务、用户和资源多层配额。
核心答案
限流用于把进入系统的工作量控制在可承受范围。令牌桶允许按速率补充并容纳一定突发,漏桶把输出整形成稳定速率,滑动窗口比固定窗口更平滑。算法只是基础,关键是限流维度、阈值来源、分布式一致性和拒绝后的响应策略。
阈值应来自压测容量、依赖瓶颈和 SLO,并预留故障期间的容量余量,不能凭机器配置猜测。
分层设计
边缘层拦截非法与总体洪峰,网关按租户、接口和用户配额,服务内部按线程池、数据库或第三方依赖做并发限制。关键交易和普通查询使用不同优先级,防止低价值流量耗尽资源。
四种算法的形状
| 算法 | 流量形状 | 突发能力 | 典型实现 |
|---|---|---|---|
| 固定窗口 | 窗口边界可能突刺 | 窗口内可突发 | 简单计数器 |
| 滑动窗口 | 比固定窗口平滑 | 取决于窗口 | 分桶计数、时间序列 |
| 令牌桶 | 平均速率 + 可控突发 | bucket 容量 | 令牌按速率补充 |
| 漏桶 | 稳定输出速率 | 通常不保留大突发 | 队列按固定速率流出 |
令牌桶适合 API 访问既有平均配额又允许短突发;漏桶适合保护下游,让输出平滑;滑动窗口实现直观但存储和分布式计数成本更高。算法名称不是需求,先定义每秒、每分钟、并发数和队列长度的限制维度。
从容量反推阈值
假设数据库在 P99 目标下能承受 2000 QPS,当前有 4 个应用实例,每实例预留 20% 故障余量,则应用层安全配额可从约 1600 QPS 起步,再按接口成本分配。一个复杂报表请求不能和一个简单健康查询共用相同权重,最好使用 cost/weight 或独立资源池。
阈值要随扩缩容、下游降级和业务高峰调整。静态全局阈值在实例数变化后可能过严或过松,可使用中心配置、按实例权重分配并在中心故障时回退到保守本地限流。
分布式计数与误差
Redis Lua、网关插件或集中式令牌服务能提供统一视图,但每次请求多一次网络依赖,中心故障会影响入口。本地限流延迟低但多个实例会产生配额误差。严格金融额度要用原子存储和明确一致性;普通 API 防洪峰可接受近似和本地快速拒绝。
不要使用客户端时间直接计算分布式窗口,时钟漂移会造成边界错误;使用服务端时间或令牌服务的单调时间语义。
分布式实现
本地限流延迟低但只能约束单实例,可按实例权重分配配额并定期调整;集中式 Redis 或网关可统一计数但引入网络和可用性成本。极端流量下本地快速拒绝应作为兜底。
常见误区
限流只返回 429 而没有重试建议、降级内容和监控,会把压力转成客户端重试风暴。阈值固定不变也无法适应扩缩容、依赖降级和昼夜流量。
限流后的用户体验
429 响应应包含稳定错误码、Retry-After 或可行动提示。对可异步任务,把请求转为排队受理并返回 operation_id;对实时查询,返回缓存、降级字段或明确失败。客户端 SDK 要有随机退避,不能收到 429 后立即无脑重试。
与削峰、熔断的组合
限流在入口控制速率,队列削峰保存可异步任务,熔断阻止已故障依赖继续被调用。三者都需要有界容量:无限队列只是延迟的 OOM,熔断没有恢复探测会永久失败。监控拒绝率、排队时长、令牌耗尽、下游利用率和用户错误率。
高频追问与参考回答
追问:限流和削峰有什么区别?
限流拒绝或推迟超出容量的请求,削峰常通过队列把瞬时任务摊到更长时间;能异步的任务可排队,强实时且过期无价值的请求应快速拒绝。
追问:为什么固定窗口会有边界突刺?
窗口末尾用满配额,下一窗口开始又立刻用满,短时间内实际请求数可能接近两个窗口总量。滑动窗口或令牌桶可以缓和。
追问:限流阈值应该按 QPS 还是并发数?
短请求常按 QPS,慢 I/O 和连接池保护常按并发数;两者可以同时设置,配合超时和队列上限。
追问:分布式限流中心挂了怎么办?
入口应回退到保守本地限流或快速拒绝,避免无限放行;恢复后逐步同步配额,不能因中心故障让所有实例解除保护。
总结
限流是容量保护体系,需把算法、维度、优先级、分布式误差和客户端行为一起设计并持续校准。
机制全景图
下面把「高并发系统如何设计限流?」从输入到结果压缩成一条可复述的主链路。面试时先用图建立全局坐标,再进入局部实现,能避免只背零散结论。
flowchart LR
A["请求携带身份与资源维度"]
A --> B["选择本地/集中限流器"]
B --> C["原子消耗令牌或配额"]
C --> D["超限快速失败/排队"]
D --> E["动态调整并观测拒绝"]
完整链路:从输入到结果
沿着「请求携带身份与资源维度 → 选择本地/集中限流器 → 原子消耗令牌或配额 → 超限快速失败/排队 → 动态调整并观测拒绝」观察输入、状态与输出,下面每个阶段都对应一个可以在源码、日志或系统表中验证的位置。
1. 请求携带身份与资源维度
限流维度应包含用户、IP、租户、接口和下游资源,单维度容易误伤或被绕过。
2. 选择本地/集中限流器
本地限流延迟低但总量不精确,集中限流全局一致但增加网络和热点。
3. 原子消耗令牌或配额
令牌桶允许受控突发,漏桶平滑速率,滑动窗口更接近统计上限,各自语义不同。
4. 超限快速失败/排队
超限应返回明确错误、Retry-After 或降级结果;无界排队只是把拒绝变成超时。
5. 动态调整并观测拒绝
配额根据下游水位、错误率和业务优先级动态调整,发布和故障时需有保守默认值。
源码与实现定位
| 入口 | 阅读重点 |
|---|---|
| 网关限流决策日志 | 维度、规则、结果 |
| 下游连接/线程水位 | 真实保护对象 |
源码或系统表应按上表顺序追踪:先确认入口实际走到哪条路径,再用运行时数据验证,而不是仅凭类名或配置推测。
参数配置与可复现实验
rate=5000/s; burst=1000; max_concurrency=300
阶跃流量与慢请求分别压测 QPS、并发、加权成本三种限制。
验证步骤与预期结果
1. 固定输入和基线
先在没有故障注入的环境执行上述配置,固定数据规模、并发度、运行时版本和预热时间。以「放行/拒绝」为主基线,记录值应满足「记录活动/稳态基线」;同时保存 放行/拒绝率、在途并发,使后续变化能够回到同一时间轴比较。
2. 从实现入口确认路径
在「网关限流决策日志」确认请求确实进入「维度、规则、结果」对应的实现,再沿「下游连接/线程水位」观察「真实保护对象」。如果入口路径都未命中,就不应继续调整下游参数,而应先检查调用条件、版本或路由是否与假设一致。
3. 注入本文特有的失败模式
优先复现「只限 QPS 不限并发和成本」,并把单一变量逐级放大,直到「放行/拒绝」越过「超过容量或 SLO」。随后再分别验证「限流器故障时默认全放行」和「排队无上限导致超时雪崩」,三类故障分开执行,避免多个变量同时变化而无法归因。
4. 执行止损和根因修复
第一轮只应用「按下游可持续能力设阈值」,确认它能控制影响范围;第二轮应用「限 QPS 同时限并发」,验证核心链路恢复;最后落实「限流器故障保守降级」,消除同类问题再次出现的条件。每一步都保留变更前后数据,不用“感觉变快了”替代测量。
5. 通过退出条件
实验只有同时满足三项才算通过:「放行/拒绝」回到「记录活动/稳态基线」、「端到端 P99」回到「小于预算」、「状态差异」回到「0」,并且业务结果差异为零。若性能恢复但结果不一致,仍应视为失败;若指标恢复后很快再次越线,则说明只完成了临时止损,没有消除根因。
量化基线
| 指标 | 样例基线/口径 | 风险线 | 结论 |
|---|---|---|---|
| 放行/拒绝 | 记录活动/稳态基线 | 超过容量或 SLO | 触发降级 |
| 端到端 P99 | 小于预算 | 突破预算 | 停止扩量 |
| 状态差异 | 0 | 任意非零 | 补偿并对账 |
这些数值是实验口径或示例告警线,不是可复制到所有系统的固定答案;上线阈值应由本系统稳态、峰值和故障演练共同确定。
事故复盘:全局限流配置正确但数据库仍被打满
总 QPS 未超阈值,但一个昂贵查询占用远多于普通请求。引入按接口成本加权的并发许可,并以数据库连接与慢查询为反馈后,限流才对准真实瓶颈。
| 失败模式 | 首要证据 | 第一处置动作 |
|---|---|---|
| 只限 QPS 不限并发和成本 | 放行/拒绝率 | 按下游可持续能力设阈值 |
| 限流器故障时默认全放行 | 在途并发 | 限 QPS 同时限并发 |
| 排队无上限导致超时雪崩 | 下游饱和水位 | 限流器故障保守降级 |
发布与回滚检查点
- 发布前:确认「网关限流决策日志」对应实现和上述配置在目标版本仍然有效,并保存「放行/拒绝」基线。
- 灰度中:同时观察 放行/拒绝率、在途并发、下游饱和水位;任一指标越过表中风险线,就停止继续扩量。
- 回滚时:先执行「按下游可持续能力设阈值」控制影响,再回退代码或参数;涉及持久状态时必须额外核对结果差异。
- 发布后:至少覆盖一个完整峰值周期,确认「只限 QPS 不限并发和成本」没有再次出现,才关闭变更观察窗口。
方案对比与选型
| 方案 | 更适合的场景 | 主要收益 | 代价与边界 |
|---|---|---|---|
| 固定窗口 | 简单计数和粗粒度配额 | 实现低成本 | 边界双倍突发 |
| 滑动窗口 | 需要更精确速率 | 边界平滑 | 存储与计算更高 |
| 令牌桶 | 允许短时突发的 API | 吞吐与突发可配置 | 不直接限制在途并发 |
选型至少带上 峰值流量、数据规模、依赖容量、一致性目标和故障预算,并用上面的量化基线验证;未知数据应明确为待测假设。
设计边界与工程取舍
限流阈值应来自被保护资源的可持续能力;对慢请求,限制并发往往比只限制每秒请求数更有效。
工程落地遵循:先定义 SLO 与不变量,再通过限流、隔离、异步和补偿保护核心链路。回答时直接引用「网关限流决策日志」、配置实验和事故数据,比复述固定模板更有说服力。