vai*_*hav 1 performance latency http go server
我正在编写一个事件收集器http服务器,它将负载很重.因此,在http处理程序中,我只是对事件进行反序列化,然后在goroutine中的http请求 - 响应周期之外运行实际处理.
有了这个,我看到如果我以每秒400个请求命中服务器,那么99百分位的延迟低于20毫秒.但是一旦我将请求率提高到每秒500,延迟就会超过800毫秒.
任何人都可以帮我解释一下原因是什么,以便我可以探索更多.
package controller
import (
"net/http"
"encoding/json"
"event-server/service"
"time"
)
func CollectEvent() http.Handler {
handleFunc := func(w http.ResponseWriter, r *http.Request) {
startTime := time.Now()
stats.Incr("TotalHttpRequests", nil, 1)
decoder := json.NewDecoder(r.Body)
var event service.Event
err := decoder.Decode(&event)
if err != nil {
http.Error(w, "Invalid json: " + err.Error(), http.StatusBadRequest)
return
}
go service.Collect(&event)
w.Write([]byte("Accepted"))
stats.Timing("HttpResponseDuration", time.Since(startTime), nil, 1)
}
return http.HandlerFunc(handleFunc)
}
Run Code Online (Sandbox Code Playgroud)
我以每秒1000个请求运行测试并对其进行了分析.以下是结果.
(pprof) top20
Showing nodes accounting for 3.97s, 90.85% of 4.37s total
Dropped 89 nodes (cum <= 0.02s)
Showing top 20 nodes out of 162
flat flat% sum% cum cum%
0.72s 16.48% 16.48% 0.72s 16.48% runtime.mach_semaphore_signal
0.65s 14.87% 31.35% 0.66s 15.10% syscall.Syscall
0.54s 12.36% 43.71% 0.54s 12.36% runtime.usleep
0.46s 10.53% 54.23% 0.46s 10.53% runtime.cgocall
0.34s 7.78% 62.01% 0.34s 7.78% runtime.mach_semaphore_wait
0.33s 7.55% 69.57% 0.33s 7.55% runtime.kevent
0.30s 6.86% 76.43% 0.30s 6.86% syscall.RawSyscall
0.10s 2.29% 78.72% 0.10s 2.29% runtime.mach_semaphore_timedwait
0.07s 1.60% 80.32% 1.25s 28.60% net.dialSingle
0.06s 1.37% 81.69% 0.11s 2.52% runtime.notetsleep
0.06s 1.37% 83.07% 0.06s 1.37% runtime.scanobject
0.06s 1.37% 84.44% 0.06s 1.37% syscall.Syscall6
0.05s 1.14% 85.58% 0.05s 1.14% internal/poll.convertErr
0.05s 1.14% 86.73% 0.05s 1.14% runtime.memmove
0.05s 1.14% 87.87% 0.05s 1.14% runtime.step
0.04s 0.92% 88.79% 0.09s 2.06% runtime.mallocgc
0.03s 0.69% 89.47% 0.58s 13.27% net.(*netFD).connect
0.02s 0.46% 89.93% 0.40s 9.15% net.sysSocket
0.02s 0.46% 90.39% 0.03s 0.69% net/http.(*Transport).getIdleConn
0.02s 0.46% 90.85% 0.13s 2.97% runtime.gentraceback
(pprof) top --cum
Showing nodes accounting for 70ms, 1.60% of 4370ms total
Dropped 89 nodes (cum <= 21.85ms)
Showing top 10 nodes out of 162
flat flat% sum% cum cum%
0 0% 0% 1320ms 30.21% net/http.(*Transport).getConn.func4
0 0% 0% 1310ms 29.98% net.(*Dialer).Dial
0 0% 0% 1310ms 29.98% net.(*Dialer).Dial-fm
0 0% 0% 1310ms 29.98% net.(*Dialer).DialContext
0 0% 0% 1310ms 29.98% net/http.(*Transport).dial
0 0% 0% 1310ms 29.98% net/http.(*Transport).dialConn
0 0% 0% 1250ms 28.60% net.dialSerial
70ms 1.60% 1.60% 1250ms 28.60% net.dialSingle
0 0% 1.60% 1170ms 26.77% net.dialTCP
0 0% 1.60% 1170ms 26.77% net.doDialTCP
(pprof)
Run Code Online (Sandbox Code Playgroud)
我正在使用另一个goroutine,因为我不希望在http请求 - 响应周期中进行处理.
这是一个常见的谬误(因此陷阱).推理线似乎是合理的:您试图在"其他地方"处理请求,以尽可能快地处理入口HTTP请求.
问题是"其他地方"仍然是一些代码,
它与你的请求处理流失的其余部分同时运行.因此,如果该代码运行速度低于入口请求的速率,那么您的处理goroutines将堆积起来,从根本上耗尽一个或多个资源.具体取决于实际处理:如果它受CPU限制,它将在所有这些GOMAXPROCS硬件执行线程之间创建CPU的自然争用; 如果它绑定到网络I/O,它将在Go运行时scheruler上创建加载,它必须在它想要执行的所有goroutine之间划分它拥有的可用执行量; 如果它绑定到磁盘I/O或其他系统调用,您将创建OS线程的扩散,依此类推......
实际上,您正在对从入口HTTP请求转换的工作单元进行排队,但队列不会修复过载. 它们可能用于吸收过载的短尖峰,但这仅在这些尖峰被负载周期"包围"时才起作用,至少略低于系统提供的最大容量.您排队的事实并不是直接在您的情况下看到的,但它就在那里,并且通过按下您的系统超过其自然容量来展示它 - 您的"队列"开始无限增长.
请仔细阅读这篇经典论文,了解为什么您的方法无法在现实生产环境中发挥作用.密切关注厨房水槽的照片.
不幸的是,提供简单的解决方案几乎是不可能的,因为我们在您的工作负载设置中没有使用您的代码.不过,这里有几个方向可供探讨.
在最广泛的范围内,试着看看你的系统中是否有一些你现在看不到的容易辨别的瓶颈.举例来说,如果所有这些并发工作够程最终跟一个RDBMS实例,它的磁盘I/O可以很容易地连载 所有这些够程,这将只是等待轮到他们有他们的数据被接受.瓶颈可能比较简单 - 比方说,在每个工作人员中,你不小心执行一些长时间运行的操作,同时拿着所有那些goroutines争论的锁; 这显然将它们全部序列化.
下一步是实际衡量(我的意思是,通过编写基准)一个工人完成其工作单位需要多少时间.然后,您需要在增加并发因子时测量此数字的变化方式.收集这些数据后,您将能够对系统能够处理请求的实际速率进行有根据的预测.
下一步是考虑您的策略,使您的系统满足这些计算的期望.通常这意味着限制入口请求的速率.有不同的方法来实现这一目标.查看golang.org/x/time/rate
基于时间的速率限制器,但可以从低技术方法开始,例如使用缓冲通道作为计数信号量.会溢出您的容量的请求可能会被拒绝(通常使用HTTP状态代码429,请参阅此内容).您可能还会考虑对它们进行简短排队,但我会尝试将其作为馅饼中的樱桃 - 也就是说,当您将其余部分完全整理出来时.
如何处理被拒绝的请求的问题取决于您的设置.通常,您尝试通过部署多个服务来"横向扩展"以处理您的请求并教您的客户切换可用服务.(我想强调的是,这意味着一些 独立的服务,如果他们都共享它收集的数据的一些目标信宿,他们可能会通过水槽的最终容量的限制,并增加更多的系统不会得到你任何东西.)
让我再说一遍,一般的问题有没有神奇的解决方案:如果您的完整制度的几点(与你写的是这个HTTP服务仅仅是它的前端,网关,一部分)是唯一能够处理N负载的RPS,无散射的量go processRequest()是将它以更高的速度处理请求.容易并发的Go并不是一个银弹,它是一挺机枪.
| 归档时间: |
|
| 查看次数: |
750 次 |
| 最近记录: |