题干与适用场景
某个幂等读 API 的 p99 延迟很高,p50 正常,慢请求主要来自偶发排队或单机抖动。请设计 Request Hedging:先发主请求,经过延迟仍未完成时向另一副本发出副本请求,取先返回的结果并取消另一个。回答必须说明额外负载和故障放大边界。
面试官考察点
是否理解目标
Hedging 针对尾延迟,不会让每个请求都更快。它适合可安全重复的读操作,不能把有副作用的写请求直接复制。
是否能控制代价
如果所有请求同时复制,后端负载会接近翻倍。候选人应使用延迟触发、最大尝试次数、节流和取消,并用原始请求指标评估效果。
是否能处理相关故障
把主请求和副本请求发到同一台过载机器没有收益。跨实例、可用区或故障域路由,以及队列深度和错误率保护,决定方案是否安全。
回答前需要澄清的问题
- API 是否幂等,结果是否允许多个并发读取?
- p50、p95、p99 和 p99.9 的基线及 SLO 是什么?
- 慢请求是随机单机抖动,还是所有副本都在排队?
- 副本之间如何选择,是否跨实例、可用区或版本?
- 取消请求能否真正释放下游线程、连接和计算资源?
- 什么错误率、队列深度或 hedge fire rate 会触发自动关闭?
30 秒回答框架
“我只对幂等读启用 hedging。先发送主请求;按请求类别用动态 p95 或受控分位数作为延迟阈值,超时才向不同故障域的健康副本发送一次 hedge。任一请求成功就取消另一请求,整个链路共享 deadline。最大尝试次数、节流令牌、错误率和队列深度保护后端;监控 p99、触发率、额外请求量、取消成功率和原始延迟,异常时关闭。”
分步骤深入解答
定义请求状态机
状态依次为 primarysent、hedgewaiting、hedgesent、winnerselected 和 deadline_exceeded。主请求先发送;阈值内返回则结束,超过阈值才创建副本请求。任一成功响应成为 winner,其他请求收到取消信号。
选择阈值与请求范围
按方法、租户、请求大小或 prompt 长度分桶维护延迟直方图,使用 p95 一类的动态阈值,并设置上下限和冷启动回退值。阈值不能只看全站平均值,也不能在所有请求上立即复制。
路由到独立副本
副本选择应避开主请求所在实例、可用区或故障域,优先选择健康且队列较短的节点。若所有候选都处于高负载,hedging 只会增加拥塞,应直接等待、降级或快速失败。
处理取消与 deadline
客户端和代理都要传递取消令牌;下游必须在取消后停止工作并释放连接、线程、GPU 或缓存。主请求和 hedge 共享总 deadline,不能因为复制而把用户等待时间延长。
保护下游负载
设置 maxAttempts、最小 hedge delay、并发预算和按服务的令牌桶。gRPC 的配置把最大尝试次数限制为 5,并提供 retry throttling;业务实现仍需结合自己的容量和错误语义。
处理错误与非幂等操作
只有可重试且幂等的错误才允许继续发送 hedge。认证失败、参数错误等确定性错误应立即返回;写操作要用幂等键或改用单次请求和补偿流程。
伪代码
~~~text send(primary) timer = hedgeThreshold(request_class) if primary unfinished at timer and budget_allows(): send(hedge, differentfailuredomain) winner = firstsuccessbefore_deadline() cancel(allotherattempts) record(primarylatency, hedgefired, winner, cancel_result) ~~~
复杂度、监控与回滚
单次请求的最坏尝试数由 maxAttempts 限制,额外请求量取决于触发率和取消延迟。持续监控 p50/p95/p99、hedge fire rate、额外 QPS、下游队列、错误率、取消成功率和原始主请求延迟;一旦拥塞或错误恶化,按开关或令牌预算关闭。
| 控制项 | 作用 | 失控风险 |
|---|---|---|
| hedge delay | 只复制慢请求 | 太短会放大 QPS |
| maxAttempts | 限制并发副本 | 太高会形成请求风暴 |
| cancellation | 释放输家资源 | 失效会持续消耗下游 |
| throttle budget | 拥塞时收紧策略 | 缺少指标会误判容量 |
高质量示范回答
“我先确认这是幂等读,并用按请求类别分桶的 p95 延迟作为初始 hedge 阈值。主请求发送后,只有超过阈值且下游健康、预算允许时,才把一次副本请求路由到不同实例或可用区。两次请求共享总 deadline;先成功者返回,代理立即传播取消,记录取消是否真正释放资源。用最大尝试数、令牌桶、队列深度和错误率保护后端,确定性错误不再复制。上线前做 shadow 或小流量对照,比较 p99、触发率、额外 QPS、取消延迟和原始主请求延迟;出现拥塞或 hedge storm 就自动关闭。”
常见错误
每个请求都立即复制
这把 hedging 变成无条件复制,常态负载和成本都会显著增加,也无法证明尾延迟问题来自偶发抖动。
复制有副作用的写请求
两个请求可能创建两条记录或扣款两次。除非已有幂等键、去重和事务语义,否则不应使用 hedging。
把两个请求发到同一故障域
同一实例、机架或可用区的共同故障会让副本同时变慢,还会向过载位置继续加压。
只看用户看到的 p99
hedging 可能掩盖真实主请求变慢,导致扩容信号被延迟。应同时记录未 hedge 的主请求延迟和队列深度。
忽略取消实现
仅停止等待响应不等于下游停止计算。必须验证取消传播、资源释放和取消延迟。
没有停止开关
高错误率、队列堆积或 hedge fire rate 异常时,系统需要按服务、租户或区域快速关闭策略。
追问及应对
Hedging 与 retry 有什么区别?
Retry 通常等待失败后再发;hedging 在等待期间按延迟阈值并行发出副本,用于绕过偶发慢请求。两者都需要幂等、deadline 和节流。
阈值为什么常用 p95?
它让大多数正常请求不触发复制,同时覆盖尾部的一小部分请求。实际阈值应按请求类别、预算和实验结果动态调整。
如果所有副本都拥塞怎么办?
停止发送 hedge,使用限流、排队、降级或快速失败。hedging 无法修复容量不足,继续复制会制造更严重的拥塞。
如何验证取消真的生效?
在下游记录取消事件、工作单元完成时间、连接占用和资源释放延迟,并用故障注入确认 winner 产生后 loser 会停止。
流式响应如何处理?
可把首字节或首 token 延迟作为触发条件,但已开始输出后再复制可能造成重复数据。需要定义流合并、取消和客户端可见性。
gRPC 配置有哪些关键限制?
maxAttempts、hedgingDelay 和非致命状态码决定发送时机;gRPC 将最大尝试次数限制为 5,并提供 retry throttling 与 server pushback。业务还要校验自己的容量预算。