常见的提高数据包处理能力的方法

这里列举了几个常见的提高数据包处理能力的方法。主要包括:

1. 多队列/RSS/RPS/RFS
2. LSO/LRO/GSO/GRO/TSO/USO
3. DPDK

现在来分别说明下:

1. 多队列/RSS/RPS/RFS

在前面的博文中小秦讲过了一个数据包的收包、发包路径。其实如果大家按照之前文章的内容来分析的话,会发现一个网卡的数据包基本上都是在一个核上进行收发的。比方说一个数据包到达了网卡,网卡触发中断通知某个CPU,然后接下来的事情就基本上都是这个CPU来处理了。我们知道对于一个物理机来说CPU的核数可能能有二三十个,但网卡的个数一般只有四块。那按照这个逻辑一个物理机上最多也就四个CPU能处理网络的包咯?对于Neutron的网络节点来说这个是不能接受的,因为这会让CPU严重成为性能提高的瓶颈。所以在这里我们来讲下一个最常见的针对这个问题的优化方法。

这个方法的核心思想很简单:上面说的瓶颈之所以产生的原因我们知道是因为一个网卡对应一个中断向量号,而一个中断只能被一个CPU响应。那么能不能让这个网卡产生的中断分散到各个CPU上呢?一种方法就是多队列。这里的多队列指的是网卡本身硬件支持多队列,每个队列我们可以简单的看成就是一个能起到收发包作用的网卡,一个数据包到达网卡时,根据某个hash算法这个数据包会放到某个队列上,然后这个队列负责发送一个中断给CPU。假设一个网卡有24个队列,那么就可以绑定最多24个CPU,这样就能充分发挥CPU的使用率了。

我们来看下这种多队列的配置方法。内核开发者Robert Olsson写了一个脚本帮助大家简单的配置多队列,大家可以直接使用这个脚本进行配置:

# setting up irq affinity according to /proc/interrupts  
# 2008-11-25 Robert Olsson  
# 2009-02-19 updated by Jesse Brandeburg  
#  
# > Dave Miller:  
# (To get consistent naming in /proc/interrups)  
# I would suggest that people use something like:  
#       char buf[IFNAMSIZ+6];  
#  
#       sprintf(buf, "%s-%s-%d",  
#               netdev->name,  
#               (RX_INTERRUPT ? "rx" : "tx"),  
#               queue->index);  
#  
#  Assuming a device with two RX and TX queues.  
#  This script will assign:   
#  
#       eth0-rx-0  CPU0  
#       eth0-rx-1  CPU1  
#       eth0-tx-0  CPU0  
#       eth0-tx-1  CPU1  
#  
  
set_affinity()  
{  
    MASK=$((1<<$VEC))  
    printf "%s mask=%X for /proc/irq/%d/smp_affinity\n" $DEV $MASK $IRQ  
    printf "%X" $MASK > /proc/irq/$IRQ/smp_affinity  
    #echo $DEV mask=$MASK for /proc/irq/$IRQ/smp_affinity  
    #echo $MASK > /proc/irq/$IRQ/smp_affinity  
}  
  
if [ "$1" = "" ] ; then  
        echo "Description:"  
        echo "    This script attempts to bind each queue of a multi-queue NIC"  
        echo "    to the same numbered core, ie tx0|rx0 --> cpu0, tx1|rx1 --> cpu1"  
        echo "usage:"  
        echo "    $0 eth0 [eth1 eth2 eth3]"  
fi  
  
  
# check for irqbalance running  
IRQBALANCE_ON=`ps ax | grep -v grep | grep -q irqbalance; echo $?`  
if [ "$IRQBALANCE_ON" == "0" ] ; then  
        echo " WARNING: irqbalance is running and will"  
        echo "          likely override this script's affinitization."  
        echo "          Please stop the irqbalance service and/or execute"  
        echo "          'killall irqbalance'"  
fi  
  
#  
# Set up the desired devices.  
#  
  
for DEV in $*  
do  
  for DIR in rx tx TxRx  
  do  
     MAX=`grep $DEV-$DIR /proc/interrupts | wc -l`  
     if [ "$MAX" == "0" ] ; then  
       MAX=`egrep -i "$DEV:.*$DIR" /proc/interrupts | wc -l`  
     fi  
     if [ "$MAX" == "0" ] ; then  
       echo no $DIR vectors found on $DEV  
       continue  
       #exit 1  
     fi  
     for VEC in `seq 0 1 $MAX`  
     do  
        IRQ=`cat /proc/interrupts | grep -i $DEV-$DIR-$VEC"$"  | cut  -d:  -f1 | sed "s/ //g"`  
        if [ -n  "$IRQ" ]; then  
          set_affinity  
        else  
           IRQ=`cat /proc/interrupts | egrep -i $DEV:v$VEC-$DIR"$"  | cut  -d:  -f1 | sed "s/ //g"`  
           if [ -n  "$IRQ" ]; then  
             set_affinity  
           fi  
        fi  
     done  
  done  
done  

我们来看下关键的代码。关键的代码是:

IRQ=`cat /proc/interrupts | grep -i $DEV-$DIR-$VEC"$"  | cut  -d:  -f1 | sed "s/ //g"`  

以及:

printf "%X" $MASK > /proc/irq/$IRQ/smp_affinity  

第一个关键代码的目的是获取网卡的某个队列其绑定的中断向量号,此时由于没有做别的什么操作,所以任何一个队列产生的数据包都只会发送到开机启动的时候系统所默认的CPU上,一般也就是CPU0。因此此时虽然有多队列但是所有队列依旧只和CPU0相关,这里我们要做的就是将某个队列从CPU0绑定到其他CPU上。这个队列可以通过其IRQ识别出,然后这个IRQ通过向/proc/irq/$IRQ/smp_affinity 写入一个掩码来将其和某个CPU绑定。掩码类似00000001这种,每一位表示一个CPU。比如00000001就表示CPU0。

RSS(Receive Side Scaling),指的就是上面这种通过硬件多队列来提升CPU使用率进而优化性能的一种方法。

小秦所接触过的一些托管云用户有时候会使用一些老机器来做网络节点,这些机器的网卡很老,不支持硬件多队列,此时该如何优化呢?这就得用上RPS(Receive Packet Steering)了。什么是RPS呢?其实很简单,RPS就是软件实现的一种RSS。本来一个数据包到达网卡后走哪个队列是硬件决定的,现在由于硬件没有多队列所以硬件中断都是发生在一个CPU上。但是通过本书前面的学习我们知道真正的开销其实是softirq这个下半区中进行的,如果此时什么都不做的话,所有的开销都会发生在这个响应硬件中断的CPU上,此时这个CPU(一般是CPU0)就会不堪重负。但是通过RPS技术,硬件中断响应后内核会将这个sk_buff平衡的放到别的CPU的softnet_data的input_queue上,并且设置相应的softirq的标记,此时最耗时的softirq的工作就能由别的CPU负担了。这就是RPS技术的基本思想。

最后我们来看个RFS(Receive Flow Steering)。我们都知道内存在计算机中是一种金字塔结构,CPU的cache最快,然后是内存,然后是磁盘。如果一个数据流都能在一个CPU上进行处理那么就能充分利用cache。但是按照我们上面讲的RSS或者RPS,一个数据包走哪个CPU核是由一个hash算法决定的,这个hash算法一般只会通过源、目的IP或者mac来判断走哪个CPU。所以这个hash算法是看不到数据流(flow)层面的东西的。RFS可以看成是一个使用了高级点的hash算法的RSS,其保证同一个数据流的包都走同一个CPU。

最后如果大家对这些多队列的技术感兴趣的话可以看下https://www.kernel.org/doc/Documentation/networking/scaling.txt。链接中的文档包含了这些技术的说明、使用方法及相关原理。

2. LSO/LRO/GSO/GRO/TSO/USO

首先先来讲一个术语:offload。比如offload vxlan等。什么叫offload呢?很简单,就是将一个本来由软件实现的功能现在放到硬件上来实现。这里的offload vxlan的意思就是本来我vxlan的封包解包是由ovs来做的,offload后就由网卡或交换机帮我做了。

LSO/LRO/GSO/GRO/TSO/USO等其实就是一种offload技术。先来说下全称:

* LSO:Large Segment Offload
* LRO:Large Receive Offload
* GSO:Generic Segmentation Offload
* GRO:Generic Receive Offload
* TSO:TCP Segmentation Offload
* USO:UDP Fragmentation offload

我们知道当数据包在传输的时候按照标准是必须分割成一个一个小于MTU的小包进行传输的,然后传输到另一头后另一头还得将这些数据包拼装回去。这些分割、拼装的事情都是软件实现的,于是就有人想将这些事情offload到硬件上去实现,接着就产生了上面的这些技术。

* LSO:协议栈直接传递打包给网卡,由网卡负责分割
* LRO:网卡对零散的小包进行拼装,返回给协议栈一个大包
* GSO:LSO需要用户区分网卡是否支持该功能,GSO则会自动判断,如果支持则启用LSO,否则不启用
* GRO:LRO需要用户区分网卡是否支持该功能,GRO则会自动判断,如果支持则启用LRO,否则不启用
* TSO:针对TCP的分片的offload。类似LSO、GSO,但这里明确是针对TCP
* USO:正对UDP的offload,一般是IP层面的分片处理

实际大家在使用中,必须做好充分的测试。

3. DPDK

传统上说的收发包走的都是Linux内核提供的一套流程。而DPDK则不是。DPDK是一套Intel委托6wind开发的开源的数据收发库。当一个数据包进入网卡产生中断后,响应这个中断的驱动是DPDK安装的驱动。这个驱动会通过UIO机制直接让用户态可以直接操作这个数据包。在用户态用户可以写一个程序通过DPDK提供的API处理这个数据包,比如直接在用户态写一个二层转发实现,或者在用户态直接实现一个vRouter等。用户态的好处是其可以充分利用CPU。内核态的协议栈存在一个问题:处理包的性能和CPU核数不成正比。当CPU的核数增加到一定个数后,再增加CPU核数对于数据包的收发作用就不大了。因此大的公司可以基于DPDK直接在用户态实现一个全新的协议栈程序,只实现自己需要的功能,显而易见这个协议栈程序肯定会是一个多线程的程序用于充分挖掘多核CPU的潜力。

对于存在性能瓶颈且使用了很多方法优化都没法提高性能但又不希望使用硬件方案的用户来说,使用基于DPDK或类似技术实现的协议栈是一个可以考虑的选择。

1 Response

  1. MatheMatrix 2015年11月22日 / 下午2:11

    我们已经把 VxLan 吞吐达到 8Gbps 了 😀 无硬件 offload 下

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*