对冲请求模式出现在论文The Tail At Scale中,是Google 解决微服务长尾效应的一个办法.也是gRPC中两种重试模式之一。
对冲请求客户端将同一个请求发送到不同的节点,一旦收到第一个结果,客户端就会取消剩余的未处理请求。
这种模式主要作用是为了实现可以预测的延迟。假设我们的服务的一个调用链路是20个节点,每个节点的P99是1s,从概率上讲,一定有 18.2% 的请求时间大于1s。
通过对冲模式,我们每次都是从最快的节点那里得到结果,所以不会存在不可预测的长尾延迟(服务故障不在考虑范围之内) 。
在Golang中,我们可以使用context很方便的实现对冲请求,比如在下面的例子中:对于同一个后端服务,我们发起五次请求,只取最先返回的那次。
|
|
完整的代码 请访问:https://go.dev/play/p/fY9Lj_M7ZYE
这样做的好处就是,我们可以规避服务的长尾延迟,使服务的之间的延迟控制在可控的范围内。不过直接这么实现会造成额外的多倍负载。需要仔细设计。
为什么会出现长尾延迟?
出现长尾延迟的原因有很多,比如
- 现在混合部署已经成为主流,意味着一台物理机上有很多人跟你抢夺关键资源,所以可能会因为关键资源调度,导致长尾效应
- GC,这个不需要过多解释,Golang的
STW
会放大长尾延迟 - 排队, 包括 消息队列、 网络等。
- ….
有什么办法可以避免对冲请求模式造成的 请求放大嘛?Go High-Performance Programming EP7: Use singleflight To Merge The Same Request 中详细介绍了如何使用 SingleFlight
来合并相同的请求。这个场景下面,使用SingleFlight
能够一定程度的缓解重复请求。
还有一种做法是只发送一个请求, 到P95的时候,如果还没有收到返回,那么就立即向第二个节点发送请求。这样做的好处就是将重复请求缩小到5%。并且大大缩短了长尾请求。
在这篇论文中,还有一些方法可以用来解决,长尾请求
- 服务分级 && 优先级队列(Differentiating service classes and
higher-level queuing)。差异化服务类别可以用来优先调度用户正在等待的请求,而不是非交互式请求。保持低级队列较短,以便更高级别的策略更快生效。 - 减少队头阻塞 ,将耗费时间比较多的请求,转换成比较小的请求。Web性能优化的时候有时候也会使用这种方式。
- 微分区(Micro-partition) 以细粒度来调整负载便可以尽量降低负载不均导致的延迟影响。
- 对于性能比较差的机器,采用熔断。
- ……
你还有其他处理长尾请求的好方法吗?