云服务器价格_云数据库_云主机【优惠】最新活动-搜集站云资讯

香港带宽_阿里云小图标_限量秒杀

小七 141 0

为什么一个NGINX工作人员承担所有的工作?

扩展TCP服务器通常很简单。大多数部署都是使用单个进程设置开始的。当需要时,会添加更多的工作进程。这是许多应用程序的可伸缩性模型,包括诸如Apache、NGINX或Lighttpd之类的HTTP服务器。Paul Townsend的CC BY-SA 2.0图像增加工作进程的数量是克服单个CPU核心瓶颈的一个很好的方法,但是会带来一系列新的问题。在性能方面,通常有三种设计TCP服务器的方法:(a) 单侦听套接字,单工作进程。(b) 单侦听套接字,多个工作进程。(c) 多个工作进程,每个进程都有独立的侦听套接字。(a) 单侦听套接字,单工作进程这是最简单的模型,其中处理仅限于单个CPU。一个工作进程同时执行accept()调用以接收新连接,并处理请求本身。此型号是首选的Lighttpd设置。(b) 单个侦听套接字,多个工作进程新连接位于单个内核数据结构(侦听套接字)中。多个工作进程同时执行accept()调用和处理请求。此模型允许在多个CPU之间扩展入站连接。这是NGINX的标准型号。(c) 通过使用SO\u REUSEPORT socket选项为每个工作进程分离侦听套接字可以为每个工作进程创建专用的内核数据结构(侦听套接字)。这可以避免侦听套接字争用,除非在googlescale上运行,否则这实际上不是一个问题。它还可以帮助更好地平衡负载。稍后再谈。在Cloudflare,我们运行NGINX,我们最熟悉(b)模型。在这篇博文中,我们将描述这个模型的一个具体问题,但是让我们从头开始。传播accept()加载没有多少人意识到有两种不同的方法可以将accept()新连接负载分散到多个进程中。考虑这两个代码片段。让我们调用第一个阻塞accept。最好用这个伪代码来描述:sd=绑定(('127.0.0.1',1024))对于范围(3)中的i:如果os.fork操作系统()==0:如果是真的:cdsd.接受()cd.关闭()打印"worker%d%"(i,)其思想是通过同时从多个worker调用blocking accept(),跨进程共享一个accept队列。第二个模型应称为epoll并接受:(1024.0英尺,标准偏差=127)sd.止动块(错误)对于范围(3)中的i:如果os.fork操作系统()==0:ed=选择.epoll()编辑寄存器(sd,环氧树脂如果是真的:编辑投票()cdsd.接受()cd.关闭()打印"worker%d%"(i,)其目的是在每个工作进程中有一个专用的epoll。只有当epoll报告新的连接时,worker才会调用non-blocking accept()。我们可以通过使用EPOLLEXCLUSIVE标志来避免通常的雷鸣羊群问题。(此处提供完整代码)虽然这些程序看起来很相似,但它们的行为却有细微差别[1][2]。让我们看看,当我们与每个连接建立了几个连接时会发生什么:美元/块-接受.py&$for i in`seq 6`;do nc localhost 1024;完成工人2工人1工作线程0工人2工人1工作线程0美元/欧元-接受.py&$for i in`seq 6`;do nc localhost 1024;完成工作线程0工作线程0工作线程0工作线程0工作线程0工作线程0blocking accept模型跨所有工作线程分布连接-每个工作进程只获得2个连接。另一方面,epoll和accept模型将所有连接转发给第一个worker。剩下的工人没有交通堵塞。这可能会让您大吃一惊,但Linux在这两种情况下都会执行不同的负载平衡。在第一种情况下,Linux将进行适当的FIFO循环负载平衡。等待accept()的每个进程都被添加到一个队列中,它们将按顺序得到连接服务。在epoll和accept中负载平衡算法不同:Linux似乎选择了最后添加的进程,一种类似后进先出的行为。最近添加到等待队列的进程将获得新连接。这种行为会导致最繁忙的进程(刚刚返回事件循环的进程)接收大部分新连接。因此,最忙的工人很可能承担大部分工作。事实上,这就是我们在NGINX中看到的。以下是我们的一个综合测试的垃圾场,其中一个工人承担了大部分负荷,而其他工人则相对未得到充分利用:注意,最后一个工人几乎没有负载,而最忙的是使用30%的CPU。所以你再报告营救Linux支持一个解决这个平衡问题的特性—SO峎REUSEPORT套接字选项。我们在(c)模型中解释了这一点,在该模型中,传入连接被分成多个独立的接受队列。通常每个工作进程都有一个专用队列。由于accept队列不是共享的,而且Linux通过一个简单的哈希逻辑来分散负载,所以每个worker将获得相同数量的传入连接。这样可以更好地平衡负载。每个工作人员处理的流量大致相同:这里所有的工人都在处理工作,最忙的占13.2%,最不忙的占9.3%。负荷分配比以前好得多。这是更好的,但是负载平衡并不是故事的结尾。在某些情况下,分割接受队列会恶化延迟分布!工程师最好的解释是:我把这个问题称为维特罗斯和特易购超市收银员。Waitrose的"组合队列模型"更能减少最大延迟。单个阻塞的收银台不会显著影响整个系统的延迟。剩下的部分将分散到其他不太忙的出纳员身上。共享队列将相对迅速地耗尽。另一方面,特易购超市模式(每个收银台都有单独的队列)将面临延迟大的问题。如果一个收银台被阻塞,那么在其队列中等待的所有流量都将停止。如果任何一个队列被卡住,最大延迟将增加。在负载增加的情况下,(b)单接受队列模型虽然不能均衡负载,但对于延迟来说更好。我们可以通过运行另一个综合基准测试来证明这一点。以下是100k个CPU相对密集型HTTP请求的延迟分布,禁用了HTTP keepalives,并发设置为200,并针对单个队列(b)NGINX运行:美元/台Http-n 100000-c 200-r目标:8181 http://a.a/|切割-d""-f 1|./mmhistogram-t"持续时间(毫秒)(单队列)"最小值:平均3.61:30.39中=30.28最大值:72.65偏差:1.58计数:100000持续时间(毫秒)(单队列):值|--------------------------------------------------计数0 | 01 | 02 | 14 | 168 | 6716 |**********************************************9176032 |****815564 | 1正如您所见,延迟是非常可预测的。中位数几乎等于平均值,标准差很小。下面是针对SO\u REUSEPORT多队列NGINX设置(c)的相同测试运行:美元/台Http-n 100000-c 200-r目标:8181 http://a.a/|切割-d""-f 1|./mmhistogram-t"持续时间(毫秒)(多个队列)"最小值:平均1.49:31.37中=24.67最大值:144.55偏差:25.27秒计数:100000持续时间(毫秒)(多个队列):值|--------------------------------------------------计数0 | 01 |*10232 |*******53214 |*************99868 |****************************1844316 |****************************************2585232 |**********************************************2794964 |*******************11368128 | 58平均值是可比的,中位数下降,最大值显著增加,最重要的是现在的偏差是巨大的!延迟分布到处都是-这不是您希望在生产服务器上拥有的。(复制试验的说明)不过,对这一基准持保留态度。为了证明这一点,我们尝试生成大量的负载。根据您的设置,可能会屏蔽您的服务器,使其不受过多流量的影响,并防止它进入这种延迟降级状态[3]。结论在多个应用程序工作线程之间平衡传入连接远不是一个解决的问题。单队列方法(b)可以很好地扩展并保持对最大延迟的控制,但是由于epoll-LIFO行为,工作进程的负载不会均衡。对于需要平衡的工作负载,使用SO峎u REUSEPORT模式(c)可能会有好处。不幸的是,在高负载情况下,延迟分布可能会降低。最好的通用解决方案似乎是将标准epoll行为从后进先出改为FIFO。过去,Akamai的Jason Baron曾试图解决这一问题(1、2、3),但到目前为止,还没有人登陆主线。处理Linux和NGINX的内部结构听起来很有趣吗?加入我们在伦敦、奥斯汀、旧金山和波兰华沙的精英团队。当然,将blocking accept()与功能齐全的epoll()事件循环进行比较是不公平的。Epoll功能更强大,允许我们创建丰富的事件驱动程序。仅仅使用阻塞是很有用的。不管怎样,阻塞accept程序都需要小心的多线程编程,每个请求都要有一个专用的线程。↩︎另一个