题目重点:统一两篇存储团队面经的限流系统题,讲清令牌数学、Lua 原子性、热点和容灾。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 每个限流 key 有容量 C、补充速率 r token/s;一次请求消耗 cost 个 token,允许瞬时突发最多 C。真实 C、r、cost、拒绝响应码与 key 维度题目未明确。
- 训练设计要求单区域内同 key 严格原子扣减;跨区域若要单一全球严格上限,需要共识/集中协调并付额外延迟,题目未指定。
- 按服务端权威时间计算补充,过期 key 再次出现初始化为满桶;TTL 至少覆盖从空桶回满所需时间及缓冲。
解题路线
- 每个 key 存 `(tokens,last_refill_time)`。请求时 `new=min(C,old+r×max(0,now−last))`;若 `new≥cost`,原子写回 `new−cost` 并放行,否则写回 new 并拒绝/返回等待时间。
- 把读取状态、计算补充、判断、扣减和更新时间放在同一个串行化执行单元中;若用 Redis,可写 Lua 脚本在单 key 所属分片上原子运行,并以同一个服务器时间作为 now。
- 对多实例重试分配 request ID 或幂等 key:客户端超时后若盲重试,可能重复扣 token;限流日志应记录决策 ID、key 与规则版本。
- 单区域分片按 key 分散负载;超热 key 可做局部 token leasing,但会引入短时超放上界。多区域选择本地宽松配额、全局协调或区域预算拆分,并写明一致性/延迟取舍。
- 当状态存储不可用时按业务选择 fail-open、fail-closed 或保守本地桶;观测拒绝率、过载率、存储延迟、时钟回退和规则发布。
手动走一遍
设 C=5、r=1/s,t=0 桶满,请求 cost=3 放行后剩 2;t=2 秒补至 4,再来 cost=3 放行后剩 1。若两个实例在 t=2 各自读到 4 再各扣 3,都会误放行;原子脚本使第二个看到首次扣减后的状态并拒绝。
规模与取舍
单 key 判定和状态 O(1),分布式存储一次原子往返;总状态 O(活跃 key 数)。严格全局多区域限流的下界由跨区域协调延迟支配,不能仍宣称本地一次 RTT。
易错点与追问
容易失分的边界
- 先 GET 后 SET 两步更新,竞争下重复放行。
- 不同实例用本机时钟计算 refill,时钟回拨或偏差造成额度错误。
- 设置过短 TTL,使近空桶提前过期并重新满额。
- 把 Token Bucket 说成任意窗口内严格最多 C 次;它允许按补充率持续发送。
- 说‘Lua 原子’但脚本跨多个不在同一分片的 key 时仍假设强事务。
继续深挖
- 若同一请求同时受用户级和全局级限流,怎样原子扣两个 key?
- 热点用户的局部预留如何量化最大超放?
- 可用性要求高时 fail-open 和 fail-closed 的业务风险分别是什么?
- 跨区域强一致额度是否值得额外 RTT?
