代表性面试主题

后端面试:如何设计安全的 Request Hedging?

后端困难
OfferCC 编辑团队发布 更新

题干

某个幂等读 API 的 p99 延迟很高,请设计 Request Hedging。说明触发阈值、候选副本、取消竞速请求、负载保护、监控和何时关闭该策略。

题干与适用场景

某个幂等读 API 的 p99 延迟很高,p50 正常,慢请求主要来自偶发排队或单机抖动。请设计 Request Hedging:先发主请求,经过延迟仍未完成时向另一副本发出副本请求,取先返回的结果并取消另一个。回答必须说明额外负载和故障放大边界。

面试官考察点

是否理解目标

Hedging 针对尾延迟,不会让每个请求都更快。它适合可安全重复的读操作,不能把有副作用的写请求直接复制。

是否能控制代价

如果所有请求同时复制,后端负载会接近翻倍。候选人应使用延迟触发、最大尝试次数、节流和取消,并用原始请求指标评估效果。

是否能处理相关故障

把主请求和副本请求发到同一台过载机器没有收益。跨实例、可用区或故障域路由,以及队列深度和错误率保护,决定方案是否安全。

回答前需要澄清的问题

  • API 是否幂等,结果是否允许多个并发读取?
  • p50、p95、p99 和 p99.9 的基线及 SLO 是什么?
  • 慢请求是随机单机抖动,还是所有副本都在排队?
  • 副本之间如何选择,是否跨实例、可用区或版本?
  • 取消请求能否真正释放下游线程、连接和计算资源?
  • 什么错误率、队列深度或 hedge fire rate 会触发自动关闭?

30 秒回答框架

“我只对幂等读启用 hedging。先发送主请求;按请求类别用动态 p95 或受控分位数作为延迟阈值,超时才向不同故障域的健康副本发送一次 hedge。任一请求成功就取消另一请求,整个链路共享 deadline。最大尝试次数、节流令牌、错误率和队列深度保护后端;监控 p99、触发率、额外请求量、取消成功率和原始延迟,异常时关闭。”

分步骤深入解答

定义请求状态机

状态依次为 primary_sent、hedge_waiting、hedge_sent、winner_selected 和 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。业务还要校验自己的容量预算。

公开来源

同类题目