Infra / SWE · Onsite Coding / System Design

xAI 每用户 Token Bucket:惰性补桶、差异化限额与原子扣减

Onsite 给现有分布式 cache 与 tokenbucket 类,要求补桶和按 user-id 判定;结合每用户不同速率说明并发超发与失效策略。

题目重点:Onsite 给现有分布式 cache 与 tokenbucket 类,要求补桶和按 user-id 判定;结合每用户不同速率说明并发超发与失效策略。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 训练桶状态为 (tokens,lastRefillTime,capacity,refillRate,configVersion),tokens 可为小数但一次请求成本为 1;单调时钟,时间单位秒。
  • 每个 user-id 独立配置 capacity 与 tokens/s;同一用户所有服务实例共享一致的桶状态。配置更新时先限制旧 tokens 不超过新 capacity,并用新速率续算。
  • 训练版 cache 提供可原子更新单键状态的事务/CAS/脚本;若原模板只有 get 和 set,必须解释并发超发风险或添加后端同步能力,不能用本地锁冒充分布式原子性。

解题路线

  1. 惰性 refill:收到用户请求时取 now≥lastRefillTime,算 newTokens=min(capacity,oldTokens+elapsed×refillRate),并把 lastRefillTime 更新为 now;不需要为每个用户跑后台计时器。
  2. 在同一个原子临界区检查 newTokens≥cost。若足够,扣 cost、写回并允许;不足,保存补桶后的状态并拒绝。首次见用户按明确默认配置创建满桶或空桶,须与面试官确认。
  3. 用 user-id 作为隔离键;配置表/版本与桶状态分开,配置变更带版本以避免旧请求覆盖新限额。热点用户需考虑单键串行瓶颈,可在严格限额与本地预借 token 之间明确误差预算。
  4. 分布式存储不可用时按业务风险选 fail closed、短时本地保守配额或队列削峰,并记录误放/误拒;不能含糊地说‘自动降级’。
  5. 若 take-home 同时有 API 100 req/s 与 consumer 10 req/s,入口和下游各设独立桶/队列,避免消费者慢速导致无限积压;不能把两个数合并成单一 100/s 配额。

手动走一遍

训练例:普通用户 capacity=100、rate=100/s,t=0 有 100 token,一次请求后 99;t=0.1 时惰性补 10 但上限为 100,扣后 99。并发两实例都读取 tokens=1 时,分离的 get/check/set 可能两者均允许;单键 CAS/事务只能有一个成功。高级用户 rate=10000/s 使用自己的配置,不改变普通用户状态。

规模与取舍

单用户单次更新 O(1) 计算与 O(1) 状态;U 个活跃用户存储 O(U)。分布式原子操作的延迟取决于缓存网络往返与竞争,热点用户无法仅靠水平扩容消除同键串行。

易错点与追问

容易失分的边界

  • get 与 set 分开执行,即使单进程加锁,跨实例仍可能双重扣减。
  • 每次 refill 不设 capacity 上限,使闲置用户积累无穷突发额度。
  • 用墙上时钟直接相减而不处理时间回拨或时钟漂移。
  • 把每用户 10000/s 与 100/s 当成同一全局桶,造成互相抢额。
  • 把 take-home 的 API/consumer 两级限流误写成同一个速率。

继续深挖

  • 配置从 100/s 降到 10/s 时如何处理已有 token?
  • 限流缓存不可用如何在安全与可用性之间选?
  • 若请求成本不等于 1,怎样修改判断?
  • 如何测试两实例同时只剩 1 token 时恰好一次通过?
✓已复制微信号!