1。前言 一般来说,我们一个网卡只有一个队列(queue),如果在单队列网卡的网卡的情况下,所有收到的包从这个队列入,内核从这个队列里取数据处理。该队列其实是ringbuffer(环形队列),内核如果取数据不及时,则会存在丢包的情况。一个CPU处理一个队列的数据,这个叫中断。在单队列多核架构的虚拟机当中,默认是0号CPU在处理,这样的话,如果流量特别大,这个CPU负载很高,性能存在瓶颈。因此就有了多队列网卡的这个情况。即一个网卡有多个队列,收到的包根据TCP四元组信息hash后放入其中一个队列,后面该链接的所有包都放入该队列。每个队列对应不同的中断,使用irqbalance将不同的中断绑定到不同的核。充分利用了多核并行处理特性。提高了效率。 2。启用多队列网卡并不是所有的网卡都支持多队列的,如果你的网卡不支持多队列,那你输入以下命令会得到如下提示〔rootccs〕ethtoolleth0 Channelparametersforeth0: Cannotgetdevicechannelparameters :Operationnotsupported 这是因为网卡驱动并没有实现ethtool的getchannels方法。可能的原因包括:该网卡不支持调整RXqueue数量,不支持RSSmultiqueue,或者驱动没有更新来支持此功能。 正常的返回结果应该如下,我的服务器配置是4C8G,如果返回信息中,两个Combined字段取值相同,则表示弹性网卡已开启支持多队列。〔rootccs〕ethtoolleth0 Channelparametersforeth0: Presetmaximums: RX:0 TX:0 Other:0 Combined:4表示最多支持设置4个队列 Currenthardwaresettings: RX:0 TX:0 Other:0 Combined:4表示当前生效的是4个队列 OK,那么这里开始就表示你的网卡支持多队列啦,如果你的生效队列小于支持队列,那么你可以使用以下命令来开启多队列:ethtoolLeth0combinedN strongtoutiaoorigincode这里的eth0是网卡名称,N代表的事你想让网卡生效几个队列strong 3。多队列网卡实现图示(这里使用双队列举例)普通单队列 queue packetpacketCPU0 开启多网卡队列 queue packetpacketCPU0 CPU1 CPU2 queue packetpacketCPU3 4。中断与CPU的关系 第一列是中断号,后面两列是对应CPU处理该中断的次数,virtioinput和virtiooutput为网卡队列的中断,我的服务器由于是云服务器,购买的时候就要求云厂商提供多队列网卡,所以云厂商的操作系统镜像是经过调试的,可以看到网卡的中断是均分给各个CPU的,如果你是刚刚开启的多队列网卡,那么你大部分的中断应该是被CPU0处理了。〔rootccs〕catprocinterruptsegrepCPUvirtio。(inputoutput) CPU0CPU1CPU2CPU3 31:1500117PCIMSIedgevirtio0input。0 32:1000PCIMSIedgevirtio0output。0 33:1101250PCIMSIedgevirtio0input。1 34:1000PCIMSIedgevirtio0output。1 35:1513500PCIMSIedgevirtio0input。2 36:1000PCIMSIedgevirtio0output。2 37:151000PCIMSIedgevirtio0input。3 38:1000PCIMSIedgevirtio0output。3 这个均分你也可以自己设置的,这个就涉及到中断跟CPU的亲和性了,下面就是你的服务器的中断〔rootccs〕lsprocirq 0110111213141522425262728293303132333435363738456789defaultsmpaffinity 如果你要设置亲和性,以0号中断举例〔rootccs〕catprocirq0smpaffinitylist 03 这样表示0号中断是绑定到了前4个CPU(CPU03)上面),你可以将其修改为专属于某个CPU的方式,直接修改值即可 这里遍历几个我的中断当示例展示〔rootccs〕foriin{24。。30};doechonInterruptiisallowedonCPUs;done Interrupt24isallowedonCPUs1 Interrupt25isallowedonCPUs2 Interrupt26isallowedonCPUs03 Interrupt27isallowedonCPUs03 Interrupt28isallowedonCPUs3 Interrupt29isallowedonCPUs0 Interrupt30isallowedonCPUs0 5。软中断负载 top进入交互式界面后,按1显示所有cpu的负载。si是软中断的CPU使用率。如果高比如50,说明该CPU忙于处理中断,通常就是收发网络IOtop10:23:54up24min,1user,loadaverage:1。45,0。84,1。06 Tasks:401total,1running,400sleeping,0stopped,0zombie Cpu0:11。8us,7。7sy,0。0ni,79。1id,0。0wa,0。0hi,1。3si,0。0st Cpu1:10。7us,6。7sy,0。0ni,81。2id,0。0wa,0。0hi,1。3si,0。0st Cpu2:11。1us,7。4sy,0。0ni,80。2id,0。0wa,0。0hi,1。3si,0。0st Cpu3:12。1us,6。4sy,0。0ni,80。2id,0。0wa,0。0hi,1。3si,0。0st KiBMem:8008632total,393060free,4095716used,3519856buffcache KiBSwap:0total,0free,0used。3457996availMem 6。拓展:RSS,RPS,RFS和XPS RSS(receivesidescaling):网卡多队列 RPS(receivepacketSteering):RSS的软件实现 RFS(receiveflwoSteering):基于flow的RPS AcceleratedRFS:RFS的硬件实现 XPS(transmitpacketSteering):应用在发送方向 6。1RSS 现在网卡都有多队列功能,在MSIX中断模式下,每个队列都可以使用单独的中断。在多核系统下,将每个队列绑定到单独的core上,即让单独的core处理队列的中断,可通过下面的命令实现绑定,意思为让core1处理中断27,值得注意的是,cpu列表最好和网卡在同一个numa上。echo0x1procirq27smpaffinity 在单队列模式下,网卡收到的包都会让queue0处理,那多队列模式下,数据包如何分发呢? 网卡内部有Redirectiontable(表大小不同网卡有不同的实现),表中每项包含一个队列id,当网卡收到包后,会先解析报文出五元组等信息,然后根据这些信息(可以设置基于ip,tcp,udp等)计算出hash值(hash算法是固定的,但是使用的key可以在初始化时指定),然后根据hash低7位取模tablesize,得到表的一项,取出此项包含的队列id,此id即是数据包发往的队列。 a。Parsedreceivepacket解析数据包,获取五元组等信息 b。RSShash根据五元组的某些信息计算hash值 c。PacketDescriptor将hash值保存到接收描述符中,最终会保存到skbhash中,后续可以直接使用hash值,比如RPS查找cpu时使用这个hash值 d。7LSbits使用hash值低7位索引redirectiontable的一项,每项包含四位(所以最多支持16个队列) e。RSSoutputindextable的指定项就是接收队列。 那redirectiontable中每一项中的队列id是如何设置的呢?在驱动初始化时,根据使能的队列个数,依次填充到每一项,达到队列最大值后,从0开始循环填充。比如使能了4个队列,则table的0127项依次为:0,1,2,3,0,1,2,3。。。 看下ixgbe中使用到的和redirectiontable相关的寄存器,使用32个IXGBERETA寄存器,每个寄存器的0:3、11:8、19:16和27:24分别表示一个table的entry,而且是4位,所以使能RSS时最多支持16个队列。 6。2RPS 在单队列网卡,多核系统上,或者多队列网卡,但是核数比队列多的情况下,可以使用RPS功能,将数据包分发到多个核上,使多核在软中断中处理数据包。这相当于是RSS的软件实现。 使能RPS也很简单,如下,将cpu列表写入队列对应的rpscpus即可。 配置文件为sysclassnetethqueuesrxrpscpus。默认为0,表示不启动RPS如果要让该队列被CPU0、1处理,则设置echo3sysclassnetethqueuesrxrpscpus,3代表十六进制表示11,即指CPU0和CPU1在开启多网卡队列RSS时,已经起到了均衡的作用。RPS则可以在队列数小于CPU数时,进一步提升性能。因为进一步利用所有CPU 6。3RFS 从RPS选择cpu方法可知,就是使用skb的hash随机选择一个cpu,没有考虑到应用层运行在哪个cpu上,如果执行软中断的cpu和运行应用层的cpu不是同一个cpu,势必会降低cpucache命中率,降低性能。 所以就有了RFS,原理是将运行应用的cpu(为了高性能,一般都会将应用和cpu绑定起来),保存到一个表中,在getrpscpu时,从这个表中获取cpu,即可保证执行软中断的cpu和运行应用层的cpu是同一个cpu。 这个表是procsysnetcorerpssockflowentries。检查当前RPS,RFS开启情况:strongtoutiaooriginstrongclasshighlighttextforstrongistrongtoutiaooriginstrongclasshighlighttextinstrong(ls1sysclassnetethqueuesrxrps);strongtoutiaooriginstrongclasshighlighttextdostrongechon{i}:;cat{i};strongtoutiaooriginstrongclasshighlighttextdonestrong sysclassneteth0queuesrx0rpscpus:3 sysclassneteth0queuesrx0rpsflowcnt:4096 sysclassneteth0queuesrx1rpscpus:3 sysclassneteth0queuesrx1rpsflowcnt:4096 catprocsysnetcorerpssockflowentries 8192hr6。4XPS 前面三个都是接收方向的,而XPS是针对发送方向的。网卡发送出去时,如果有多个发送队列,选择使用哪个队列。 可通过如下命令设置,此命令表示运行在f指定的cpu上的应用调用socket发送的数据会从网卡的txn队列发送出去。echofsysclassnetdevqueuestxnxpscpus 虽然设置的是设备的txqueue对应的cpu列表,但是转换到代码中保存的是每个cpu可使用的queue列表。因为查找xpscpus时,肯定是已知cpuid,寻找从哪个txqueue发送。 选择txqueue时,优先选择xpscpu指定的queue,如果没有指定就使用skbhash计算出来一个。当然也不是每个报文都得经过这个过程,只有socket的第一个报文需要,选择出queue后,将此queue设置到sksktxqueuemapping,后续报文直接获取sktxqueuemapping即可。