一句话定义:对冲请求(Hedged Requests)是指把一份完全相同的请求同时发给多个后端副本,谁先返回就采用谁的结果,其余请求随即取消或丢弃。
打个比方
你在一座陌生城市赶时间,同时叫两辆网约车,谁先到就上谁的车,另一辆取消。多花一点取消费,换来的不是“更快”,而是“不用赌运气”——不用赌你恰好叫到的那辆车会不会堵在路上。对冲请求做的事情一模一样:用一点额外成本,把“万一遇到慢请求”的运气成分去掉。
尾延迟为什么值得针对
在分布式系统里,延迟的平均值意义不大,p95、p99 这类尾延迟才是用户真实感受到的东西。原因在于调用链:一个页面请求往往要串行调用十几个后端,每个后端都有一定概率慢一拍。只要其中一环慢,整条链路就慢。单机上很小比例的慢请求,经过十几层串联放大后,就变成肉眼可见的“偶尔卡一下”。对冲请求的思路就是:接受慢请求存在的概率,但不接受让它影响到用户,因此用冗余把这条慢尾巴剪掉。
和几个相邻概念的区别
| 手段 | 第二份请求何时发出 | 主要解决 | 主要代价 |
|---|---|---|---|
| 重试(Retry) | 等失败或超时之后 | 失败 | 串行等待,救不了“慢但成功” |
| 备份请求(Backup Request) | 等到 p95 还没回再补发 | 慢 | 有一定概率白等 |
| 对冲请求(Hedged Requests) | 一开始就同时发多份 | 慢 | 成倍的瞬时 QPS |
重试是最常见的误解:它处理的是“请求失败”,而对冲处理的是“请求很慢但最终会成功”。对慢请求来说,重试只会在超时之后再加一份负担,反而把尾延迟推得更高。备份请求可以看作对冲的温和版本,对冲请求则是它的统称,也包括一上来就发两份的极端形式。至于负载均衡,它是在多个副本里挑一个;对冲是挑多个。冗余存储保证数据不丢,对冲保证的是快,两者目标不同。
实际应用时需要注意的几个问题
- 幂等:只适合读、查询、无副作用的推理这类请求。写操作必须带去重键,否则可能重复下单、重复扣费。
- 取消:要真的能取消掉输掉的那一份,否则省下的延迟会变成后端双份负载。
- 流式输出:大模型逐 token 返回,两份流不能拼接,通常等首个 token 或一小段出来后就锁定其中一份。
- 成本:多出来的 QPS 是实打实的账单和后端压力,一般只在延迟敏感的关键路径上开启,按比例采样而非全量。
对普通人的意义
你感觉到的“这产品偶尔卡一下”,往往不是因为平均慢,而是因为尾部慢。对冲请求是用一点冗余换稳定的手感——代价是厂商多花一点机器,收益是你少遇见几次转圈。各家具体策略与触发阈值不同,以官方文档为准。
