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

香港带宽_长沙建设网站公司_学生机

小七 141 0

完美的地理位置和三个史诗般的SystemTap脚本

在最近的一篇博客文章中,我们讨论了导致NGINX工作进程负载不均衡的epoll行为。我们建议了一个解决方法-REUSEPORT套接字选项。它将排队从"组合队列模型"即Waitrose(正式名称:M/M/s)更改为每名员工一个专用的接受队列,即"特易购超市模型"(正式名称:M/M/1)。使用此设置,负载分布更加均匀,但在某些情况下,延迟分布可能会受到影响。读完那篇文章后,我的一位同事John说:"嘿,Marek,别忘了REUSEPORT还有一个额外的优势:它可以改善包的局部性!数据包可以避免在CPU上传递!"约翰说得有道理。让我们一步一步地深入研究。在这篇博文中,我们将解释REUSEPORT套接字选项,它如何帮助实现数据包的局部性及其性能影响。我们将展示三个高级的SystemTap脚本,它们可以帮助我们理解和测量数据包的位置。共享队列标准的BSD套接字API模型相当简单。为了接收新的TCP连接,程序在新的套接字上调用bind()然后调用listen()。这将创建一个接受队列。程序可以在多个进程之间共享文件描述符(指向一个内核数据结构),以分散负载。正如我们在之前的一篇博文中所看到的,连接可能并不完美地分布。不过,这允许程序从有限的单进程、单CPU设计中扩展处理能力。现代网卡将入站数据包分成多个接收队列,允许多个CPU共享中断和数据包处理负载。不幸的是,在标准的bsdapi中,新的连接都将被引导回单个接受队列,从而导致潜在的瓶颈。REUSEPORT简介这个瓶颈是在Google发现的,据报道,该应用程序每秒处理40000个连接。googlekernelhackers通过在linuxkernel3.9中添加了一个TCP支持SO-unreuseport-socket选项来修复这个问题。REUSEPORT允许应用程序在单个TCP侦听端口上设置多个接受队列。这消除了中心瓶颈,使cpu能够并行执行更多的工作。重用报告位置最初没有办法影响负载平衡算法。虽然REUSEPORT允许为每个工作进程设置一个专用的接受队列,但不可能影响哪些数据包将进入这些进程。流入网络堆栈的新连接将仅使用通常的5元组哈希进行分布。来自任何RX队列的数据包命中任何CPU,都可能流入任何accept队列。在Linux内核4.4中,这一点发生了变化,引入了sou INCOMING_CPU setable socket选项。现在,用户空间程序可以添加一个提示,使在特定CPU上接收到的数据包进入特定的接受队列。有了这个改进,接受队列就不需要在多个内核之间共享,从而提高了CPU缓存的局部性并修复了锁争用问题。还有其他好处-通过适当的调整,可以将属于整个连接的数据包的处理保持在本地。这样想一想:如果在某个CPU上接收到一个SYN包,那么这个连接的更多数据包很可能也会被传递到同一个CPU[1]。因此,确保同一个CPU上的worker调用accept()有很强的优势。通过正确的调优,连接的所有处理都可以在单个CPU上执行。这有助于保持CPU缓存的温度,减少跨CPU中断,提高内存分配算法的性能。因此,进入的CPU接口是相当初级的,被认为不适合更复杂的使用。在内核4.6中,它被更强大的SO_ATTACH_REUSEPORT_CBPF选项(以及它的扩展变体:SO_ATTACH_REUSEPORT_EBPF)所取代。这些标志允许程序指定一个功能齐全的BPF程序作为负载平衡算法。请注意,引入的SO_ATTACH_REUSEPORT_U[CE]BPF会损坏SO_传入的_CPU。现在没有选择-你必须使用BPF变体来获得预期的行为。在NGINX上设置CBPFNGINX在"reuseport"模式下不设置高级套接字选项来增加数据包的局部性。John局部性对提高分组性能是有益的。我们必须核实这种大胆的说法!我们想设置一些soattachment_REUSEPORT_CBPF BPF脚本。不过,我们不想入侵NGINX源代码。经过一番修补之后,我们决定编写一个SystemTap脚本来在服务器进程之外设置选项会更容易。原来这是个大错误!在做了大量的工作之后,由于我们有缺陷的脚本(在"guru"模式下运行)导致的大量内核崩溃,我们最终设法使它进入工作状态。使用正确的参数调用"setsockopt"的SystemTap脚本。这是迄今为止我们编写的最复杂的脚本之一。这里是:setcbpf.stp公司我们在内核4.9上进行了测试。它在REUSEPORT套接字组上设置以下CBPF(经典BPF)负载平衡程序。在第n个CPU上接收到的套接字将传递给REUSEPORT组的第n个成员:A=#cpuA=A%返回ASystemTap脚本接受三个参数:pid、文件描述符和REUSEPORT组大小。要计算进程的pid和文件描述符编号,请使用"ss"工具:$ss-4nlp-t'sport=:8181'|排序听0 511*:8181*:*用户:(("nginx",pid=29333,fd=3),。。。听0 511*:8181*:*。。。...在这个清单中,我们看到pid=29333fd=3指向绑定到端口tcp/8181的REUSEPORT描述符。在我们的测试机器上,我们有24个逻辑CPU(包括HT),运行12个NGINX worker—组大小为12。脚本调用示例:$sudo stap-gsetcbpf.stp公司29333 312衡量绩效不幸的是,在Linux上很难验证设置CBPF是否真的起作用。为了理解发生了什么,我们编写了另一个SystemTap脚本。它钩住一个进程并打印accept()函数的所有成功调用,包括将连接传递到内核的CPU,以及运行应用程序的当前CPU。想法很简单-如果他们匹配,我们会有好的位置!剧本:接受.stp在服务器上设置CBPF套接字选项之前,我们看到以下输出:$sudo stap-g接受.stpnginx | grep"cpu=#12"cpu=#12 pid=29333接受(3)->fd=30 rxcpu=#19cpu=#12 pid=29333接受(3)->fd=31 rxcpu=#21cpu=#12 pid=29333接受(3)->fd=32 rxcpu=#16cpu=#12 pid=29333接受(3)->fd=33 rxcpu=#22cpu=#12 pid=29333接受(3)->fd=34 rxcpu=#19cpu=#12 pid=29333接受(3)->fd=35 rxcpu=#21接受cpu=293我们可以看到从CPU 12上的一个工作线程执行的accept()返回其他CPU上接收到的客户端套接字,例如:#19、#21、#16等等。现在,让我们运行CBPF并查看结果:$sudo stap-gsetcbpf.stp公司`pidof nginx-s`3 12[+]Pid=29333 fd=3组_size=12 setsockopt(SO_ATTACH_REUSEPORT_CBPF)=0$sudo stap-g接受.stpnginx | grep"cpu=#12"cpu=#12 pid=29333接受(3)->fd=30 rxcpu=#12cpu=#12 pid=29333接受(3)->fd=31 rxcpu=#12cpu=#12 pid=29333接受(3)->fd=32 rxcpu=#12cpu=#12 pid=29333接受(3)->fd=33 rxcpu=#12cpu=#12 pid=29333接受(3)->fd=34 rxcpu=#12cpu=#12 pid=29333接受(3)->fd=35 rxcpu=#12cpu=#12 pid=29333接受(3)->fd=36 rxcpu=#12现在情况是完美的。从固定在CPU 12上的NGINX worker调用的所有accept()都在同一个CPU上接收到客户机套接字。但这对表演有帮助吗?遗憾的是:没有。我们已经运行了很多测试(使用了之前的博客文章中介绍的设置),但是我们无法记录任何显著的性能差异。与运行高级HTTP服务器所产生的其他成本相比,保持与CPU的本地连接所节省的几微秒似乎并没有产生明显的差别。测量包位置但不,我们没有放弃!由于无法衡量端到端性能的提高,我们决定尝试另一种方法。为什么不尝试测量包的位置本身呢!测量位置是很棘手的。在某些情况下,一个数据包在进入网络堆栈的过程中可以跨越多个CPU。幸运的是我们可以简化问题。让我们将"数据包位置"定义为在同一个CPU上分配和释放一个包(具体地说:LinuxSock_buff数据结构,skb)的概率。为此,我们编写了另一个SystemTap脚本:地点.stp当不使用CBPF选项运行时,脚本将给出以下结果:$sudo stap-g地点.stp12rx=21%29公里/秒tx=9%24公里/秒rx=8%130公里/秒tx=8%131公里/秒rx=11%132公里/秒tx=9%126公里/秒rx=10%128公里/秒tx=8%127公里/秒rx=10%129kps发送=8%126kpsrx=11%132公里/秒tx=9%127公里/秒rx=11%129kps发送=10%128kpsrx=10%130公里/秒tx=9%127公里/秒rx=12%94公里/秒tx=8%90公里/秒在我们的测试中,HTTP服务器每秒接收到大约130000个数据包,传输的数据量也差不多。10-11%的接收数据包和8-10%的传输数据包具有良好的局部性-在同一个CPU上分配和释放。取得好的地理位置并不是那么容易的。在RX端,这意味着数据包必须与将要读取的应用程序在同一个CPU上接收。在传输方面,这更为棘手。在TCP的情况下,一段数据必须全部:由应用程序发送(),被传输,并从另一方接收一个ACK,所有这些都在同一个CPU上。我们进行了一些调整,包括检查:将RSS队列及其中断固定到正确CPU上的数量间接表更正TX路径上的XPS设置NGINX工作人员被固定在右cpu上NGINX使用REUSEPORT绑定选项最后在REUSEPORT套接字上设置CBPF我们几乎可以达到完美的定位!完成所有调整后,脚本输出看起来更好:$sudo stap-g地点.stp12rx=99%18公里/秒tx=100%12公里/秒rx=99%118公里/秒tx=99%115公里/秒rx=99%132公里/秒tx=99%129公里/秒rx=99%138公里/秒tx=99%136公里/秒rx=99%140公里/秒tx=100%134公里/秒rx=99%138公里/秒tx=99%135公里/秒rx=99%139公里/秒tx=100%137公里/秒rx=99%139公里/秒tx=100%135公里/秒rx=99%77公里/秒tx=99%74公里/秒现在测试运行在138