> ## Content Index
> Fetch the complete content index at: https://huizhou92.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# 使用对冲模式降低长尾请求
- URL: https://huizhou92.com/e4-bd-bf-e7-94-a8-e5-af-b9-e5-86-b2-e6-a8-a1-e5-bc-8f-e9-99-8d-e4-bd-8e-e9-95-bf-e5-b0-be-e8-af-b7-e6-b1-82/
- Published: 2025-01-16T06:44:41.000Z
- Updated: 2026-09-06T11:38:41.000Z
- Description: 使用对冲模式降低长尾请求 Google的论文《The Tail At Scale》提出了一种解决微服务长尾效应的方法：对冲请求模式, 通过将同一个请求发送到不同的节点,降低一个返回的作为有效请求，并且将其他请求抛弃
- Author: huizhou92
- Tags: tech, golang, 设计模式, #lang-zh-cn

对冲请求模式出现在论文[The Tail At Scale](https://research.google/pubs/pub40801/?ref=huizhou92.com)中，是Google 解决微服务长尾效应的一个办法.也是[gRPC](https://grpc.io/docs/guides/request-hedging/?ref=huizhou92.com)中两种重试模式之一。  
![source: https://grpc.io/img/basic_hedge.svg](https://images.hxzhouh.com/blog-images/2025/01/bc5c635fafbf4cdf41447c0469eb67a7.svg)  
对冲请求客户端将同一个请求发送到不同的节点，一旦收到第一个结果，客户端就会取消剩余的未处理请求。  
这种模式主要作用是为了实现可以预测的延迟。假设我们的服务的一个调用链路是20个节点，每个节点的P99是1s，从概率上讲，一定有 18.2% 的请求时间大于1s。  
![](https://images.hxzhouh.com/blog-images/2025/01/8a3dde08bd12375f9b9a8dc12ece10f6.png)

通过对冲模式，我们每次都是从最快的节点那里得到结果，所以不会存在不可预测的长尾延迟(服务故障不在考虑范围之内) 。  
在Golang中，我们可以使用context很方便的实现对冲请求，比如在下面的例子中：对于同一个后端服务，我们发起五次请求，只取最先返回的那次。

```go
func hedgedRequest() string {
	ch := make(chan string) // chan used to abort other requests
	ctx, cancel := context.WithCancel(context.Background())

	for i := 0; i < 5; i++ {
		go func(ctx *context.Context, ch chan string, i int) {
			log.Println("in goroutine: ", i)
			if request(ctx, "http://localhost:8090", i) {
				ch <- fmt.Sprintf("finsh [from %v]", i)
				log.Println("completed goroutine: ", i)
			}
		}(&ctx, ch, i)
	}

	select {
	case s := <-ch:
		cancel()
		log.Println("cancelled all inflight requests")
		return s
	case <-time.After(5 * time.Second):
		cancel()
		return "all requests timeout after 5 secs"
	}
}

```

![](https://images.hxzhouh.com/blog-images/2025/01/057e494e9b15eb730d465905c66b75ad.png)

完整的代码 请访问：[https://go.dev/play/p/fY9Lj\_M7ZYE](https://go.dev/play/p/fY9Lj%5FM7ZYE?ref=huizhou92.com)  
这样做的好处就是，我们可以规避服务的长尾延迟，使服务的之间的延迟控制在可控的范围内。不过直接这么实现会造成额外的多倍负载。需要仔细设计。

### 为什么会出现长尾延迟？

出现长尾延迟的原因有很多，比如

1. 现在混合部署已经成为主流，意味着一台物理机上有很多人跟你抢夺关键资源，所以可能会因为关键资源调度，导致长尾效应
2. GC，这个不需要过多解释，Golang的 `STW`会放大长尾延迟
3. 排队, 包括 消息队列、 网络等。
4. ....

有什么办法可以避免对冲请求模式造成的 请求放大嘛？[Go High-Performance Programming EP7: Use singleflight To Merge The Same Request](https://pub.huizhou92.com/go-high-performance-programming-ep7-use-singleflight-to-merge-the-same-request-692bafe0940b?ref=huizhou92.com) 中详细介绍了如何使用 `SingleFlight` 来合并相同的请求。这个场景下面，使用`SingleFlight` 能够一定程度的缓解重复请求。

还有一种做法是只发送一个请求, 到P95的时候，如果还没有收到返回，那么就立即向第二个节点发送请求。这样做的好处就是将重复请求缩小到5%。并且大大缩短了长尾请求。

在这篇[论文](https://research.google/pubs/pub40801/?ref=huizhou92.com)中，还有一些方法可以用来解决，长尾请求

1. **服务分级 && 优先级队列(Differentiating service classes and**  
**higher-level queuing)**。差异化服务类别可以用来优先调度用户正在等待的请求，而不是非交互式请求。保持低级队列较短，以便更高级别的策略更快生效。
2. **减少队头阻塞** ，将耗费时间比较多的请求，转换成比较小的请求。Web性能优化的时候有时候也会使用这种方式。
3. **微分区(Micro-partition)** 以细粒度来调整负载便可以尽量降低负载不均导致的延迟影响。
4. 对于性能比较差的机器，采用熔断。
5. ......

你还有其他处理长尾请求的好方法吗？

- [本文长期连接](https://huizhou92.com/e4-bd-bf-e7-94-a8-e5-af-b9-e5-86-b2-e6-a8-a1-e5-bc-8f-e9-99-8d-e4-bd-8e-e9-95-bf-e5-b0-be-e8-af-b7-e6-b1-82)
- 如果您觉得我的博客对你有帮助，请通过 [RSS](https://huizhou92.com/sitemap.xml)订阅我。
- 或者在[X](https://x.com/@piaopiaopig?ref=huizhou92.com)上关注我。
- 如果您有[Medium](https://medium.huizhou92.com/?ref=huizhou92.com)账号，能给我个关注嘛？我的文章第一时间都会发布在Medium。