ByFomo

架构实战|分布式锁 vs 令牌桶 vs 排队:限流、公平性与“别把秒杀当成抽奖”(连载第 4 篇)

2026/02/04
1
0

evalsha(LUA_SHA, keys, args);

return allowed != null && allowed == 1L;

}

```

工程要点:

- **多维度限流**:活动维度 + 用户维度 + IP/设备维度(防羊毛)

- **错误策略**:Redis 超时/抖动时,宁可拒绝也不要“放行全部”(否则下游暴毙)

- **观测**:把 `allowed/blocked` 打到 metrics(后面第 7 篇会展开)

### 4.2 “强公平”要另外做:令牌桶只保证速率,不保证排队

我们做过一个反直觉实验:

- 令牌桶 rate=2000/s

- 10 台应用实例

- 1000 个用户同时开始请求

理论上每个人都有机会。

现实:

- RTT 更小、重试更激进的客户端更容易拿到令牌

- 一些用户在 3 秒内连续拿到多个令牌

所以结论是:

> 令牌桶解决“系统能力边界”,不负责“用户体验公平”。

公平,要靠“排队”。

### 4.3 细粒度限流:别只按活动控流,还要按用户/渠道控流

活动维度限流解决的是“系统整体别被打穿”;但秒杀现场真正把你拖死的,往往是两种请求:

- **机器人/脚本的高频重试**:一秒几十次,拿不到也不走

- **边缘网络抖动导致的重复提交**:客户端以为失败,实际上你已经在处理

所以我们会叠加两层“人类友好”的限流:

1) **用户维度**:`rl:user:{userId}:{activityId}`,例如 1 秒最多 2 次(拿不到就提示排队,别鼓励狂点)

2) **渠道维度**:对特定渠道(小程序/APP/H5)设置不同的桶容量,避免某个渠道异常把所有人拖下水

如果你在面试里想讲得更工程一点,可以补一句:

> “活动维度是保护系统能力,用户维度是保护用户体验,渠道维度是保护运营策略。”

### 4.4 漏桶 vs 令牌桶:我们为什么更偏爱令牌桶

- 漏桶更像“匀速出水”,**突发几乎不允许**,延迟更可控,但用户峰值体验更差

- 令牌桶允许“存量令牌”吸收小突发,**更适合秒杀这种‘到点起跑’的业务**

但注意:令牌桶的“突发”不是免费的,它只是把压力从“那一秒”挪到“接下来的几秒”。

如果你桶设得太大,下游还是会被冲击,所以我们通常会把容量设为 `rate * 1~2s` 的量级。

### 4.5 限流不是拍脑袋:至少把三个数算清楚

落地时我会逼自己(也逼产品)回答三个问题:

- **下游硬能力**:库存服务、订单落库、MQ 投递,各自的稳定吞吐是多少?(别拿