
# 云服务器网络优化方案
#### 操作场景
为了提高程序的运行的性能，可以通过把云服务器上运行的某个进程，指定在某个CPU上工作，实现CPU性能调优。
为了获取更高的执行效率，应该保证一个CPU把一个完整的发送或者接收过程处理完，避免CPU切换。最好一个业务进程/线程固定在一个CPU、固定一个网卡发送队列，中断也使用这个CPU。对于跨NUMA的云服务器，应该尽量在一个NUMA上完成网络IO动作，避免NUMA间切换。（可以执行lscpu查看CPU相关信息判断是否跨NUMA。）
本节操作以C7.xlarge.2、CentOS 7.4操作系统的云服务器为例，介绍网络优化的方案。
其中C7.xlarge.2实例的网卡有0、1两个队列，有CPU0、CPU1、CPU2、CPU3四颗CPU。
 #### 场景一：单位CPU获取的带宽（Gbps）或者吞吐量（Mpps）最大
1. 保证一个业务进程或线程固定在一个CPU。 可以通过如下命令把某个进程绑定在特定CPU：
   **taskset -pc cpu_id pid**
   - cpu_id为希望绑定的CPU，取值范围为\[0, CPU最大数-1\]。
   
   - pid为希望绑核的业务进程。
   
   
   例如，本例中使用iperf3作为业务进程，把两个进程分别绑定到CPU2和CPU3：
   1. 执行如下命令，获取iperf3进程状态。 **ps aux \| grep iperf3 \| grep -v grep**
      ```
      [root@localhost ~]# ps aux | grep iperf3 | grep -v grep
        root      3161  9.7  0.0   9712   896 pts/0    R    08:22   0:01 iperf3 -c 192.168.1.61 -p 10000 -t 10000 -i 0
        root      3163  9.0  0.0   9712   892 pts/0    S    08:22   0:01 iperf3 -c 192.168.1.61 -p 10001 -t 10000 -i 0
      ```
      
   
   2. 执行以下命令，指定进程运行在CPU2和CPU3上。 **taskset -pc 2 3161**
      **taskset -pc 3 3163**
      ```
      [root@localhost ~]# taskset -pc 2 3161
        pid 3161's current affinity list: 0-3
        pid 3161's new affinity list: 2
        [root@localhost ~]# taskset -pc 3 3163
        pid 3163's current affinity list: 0-3
        pid 3163's new affinity list: 3
      ```
      如果恢复配置到原来的状态，可以使用如下命令（0-3为初始设置时返回的current affinity list）：
      ```
      [root@localhost ~]# taskset -pc 0-3 3161
       pid 3161's current affinity list: 2
       pid 3161's new affinity list: 0-3
       [root@localhost ~]# taskset -pc 0-3 3163
       pid 3163's current affinity list: 3
       pid 3163's new affinity list: 0-3
      ```
      
    
2. 保证一个CPU固定使用一个网卡队列。 本例中让CPU2使用eth0的0号队列，CPU3使用eth0的1号队列。
   1. 修改前执行如下命令，首先查询原来的配置，便于恢复： **cat /sys/class/net/eth0/queues/tx-0/xps_cpus**
      **cat /sys/class/net/eth0/queues/tx-1/xps_cpus**
      ```
      [root@localhost ~]# cat /sys/class/net/eth0/queues/tx-0/xps_cpus 
        0
       [root@localhost ~]# cat /sys/class/net/eth0/queues/tx-1/xps_cpus 
        0
      ```
      xps_cpus里显示的是CPUbitmask的16进制显示，0、1、2、3四个CPU对应的值分别是1、2、4、8。
      
   
   2. 通过如下的命令让CPU2使用eth0的0号队列，CPU3使用eth0的1号队列。 **echo 4 \> /sys/class/net/eth0/queues/tx-0/xps_cpus**
      **echo 8 \> /sys/class/net/eth0/queues/tx-1/xps_cpus**
      如需恢复原来的配置，使用如下命令：
      **echo 0 \> /sys/class/net/eth0/queues/tx-0/xps_cpus**
      **echo 0 \> /sys/class/net/eth0/queues/tx-1/xps_cpus**
      
    
3. 保证一个网卡队列的中断固定在一个CPU。这个CPU和业务进程的CPU相同。
   1. 首先查询eth0对应的virtio设备的名字，如下查询结果为virtio0。
      ```
      [root@localhost ~]# ls /sys/class/net/eth0/device/driver/ | grep virtio
        virtio0
      ```
      
   
   2. 然后查询virtio0网卡使用的中断的名字，如下查询结果为24、25、26、27、28。
      ```
      [root@localhost ~]# cat /proc/interrupts | grep virtio0
       24:          0          0          0          0   PCI-MSI-edge      virtio0-config
       25:        105      52766        114      38738   PCI-MSI-edge      virtio0-input.0
       26:         79      49480     593063     403097   PCI-MSI-edge      virtio0-output.0
       27:         55       3594         46      36720   PCI-MSI-edge      virtio0-input.1
       28:      27277    1262879          0   12099829   PCI-MSI-edge      virtio0-output.1
      ```
      
   
   3. 编辑irqbalance配置文件。
      ```
      [root@localhost ~]# vim /etc/sysconfig/irqbalance
      ```
      
   
   4. 修改最后一行IRQBALANCE_ARGS为如下，这样irqbalance服务不再调度这几个中断。
      ```
      IRQBALANCE_ARGS=--banirq=24-28
      ```
      
   
   5. 重启irqbalance服务。
      ```
      [root@localhost ~]# service irqbalance restart
      ```
      
   
   6. 修改中断所在CPU，例如设置25、26号中断到CPU2； 27、28号中断到CPU3。
      ```
      [root@localhost ~]# echo 2 > /proc/irq/25/smp_affinity_list
       [root@localhost ~]# echo 2 > /proc/irq/26/smp_affinity_list
       [root@localhost ~]# echo 3 > /proc/irq/27/smp_affinity_list
       [root@localhost ~]# echo 3 > /proc/irq/28/smp_affinity_list
      ```
      
    
 
#### 场景二：获取的带宽(Gbps)或者吞吐量（Mpps)最高
场景二仅提供优化方案，需要具备相关网络知识，且根据云服务器实际情况和业务场景综合考虑具体操作。
建议首先尝试使用最优效率配置，即[场景一：单位CPU获取的带宽（Gbps）或者吞吐量（Mpps）最大]的操作方法。确认是否可以满足性能要求。当场景一的方法不满足需求的情况下，推荐使用本场景的方法。
- 对于即收又发的业务，可以把接收和发送中断分别绑定一个CPU，例如把[3]的25、26、27、28分别绑定到CPU0、1、2、3。
- 对于接收繁忙的业务，可以打开RPS（Receive Packet Steering），让一个中断的软中断分散到多个CPU。
 
#### 场景三：获取最优的不丢包TCP带宽
场景三仅提供优化方案，需要具备相关网络知识，且根据云服务器实际情况和业务场景综合考虑具体操作。
为了获取稳定的TCP不丢包带宽，推荐从小到大修改，设置一个满足业务带宽要求的发送socket缓冲区大小。业务带宽要求应该小于云服务器规格的带宽大小，否则请更换更大规格的实例。
例如推荐从64K缓冲区开始，尝试64K、128K...socket缓冲区大小。
socket缓冲区大小设置示例：
```
int opt = 64 * 1024;
        if (setsockopt(sk, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)) < 0
            || setsockopt(sk, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt)) < 0) {
            log_err("socket buffer size set failed.");
            close(sk);
            return -1;
        }
```
使用小的发送缓冲区大小，可以避免由于瞬时发送速率太高，超过flavor带宽限速而引起的丢包，从而获得更稳定的带宽。
- 对于iperf3程序，可以使用-w参数指定缓冲区大小。
- 对于4.9及以上内核版本，推荐把操作系统的TCP拥塞控制算法修改为BBR算法，这样可以在不修改socket缓冲区大小的情况下，获得一个稳定的TCP不丢包带宽。
  ![](https://support.huaweicloud.com/ecs_faq/public_sys-resources/note_3.0-zh-cn.png)
  BBR（Bottleneck Bandwidth and Round-trip propagation time）是一种拥塞控制算法，能够在传输最大带宽的同时保证最小的时延。Linux 4.9及以上内核版本自带了该算法，关于把操作系统内核协议栈拥塞控制算法修改为BBR的具体方法可以查询相关网站。
  
 
#### 场景四：处理UDP接收丢包场景
场景四仅提供优化方案，需要具备相关网络知识，且根据云服务器实际情况和业务场景综合考虑具体操作。
1. 执行以下命令，检查是否触发软中断丢包。 **cat /proc/net/softnet_stat**
   如果第二列计数值在增长时，则会判断为"软中断丢包"。当您的实例触发了软中断丢包时，可通过以下步骤进行排查及处理：
   1. 执行以下命令，查看是否开启 RPS。 **cat /sys/class/net/*eth0*/queues/rx-\*/rps_cpus**
      **eth0**需改为对应网卡名。如果回显为非全0，则是开启了RPS。
      
   
   2. 在开启场景下，内核参数**net.core.netdev_max_backlog** 偏小时会引发软中断丢包，需执行以下命令，调大该值。
      **sysctl -w net.core.netdev_max_backlog=65535**
      此处为示例数值，具体数值与业务场景相关，可逐步增大尝试。
      
    
2. 检查是否UDP接受缓冲区满导致丢包。 若您的实例因UDP接收缓冲区不足而导致丢包时，可通过以下步骤进行排查处理：
   1. 执行以下命令，检查UDP中receive buffer errors字段。 **nstat -su**
      该字段表示由于接收缓冲区满而不能接收的UDP数据包数量。如果该值较高或者持续增加，可能是因为UDP接收缓冲区满而导致的丢包。
      您可以尝试通过调大内核参数net.core.rmem_max和net.core.rmem_default来增加接收缓冲区的大小，并重启UDP程序以生效。
      
   
   2. 执行以下命令，临时调整内核参数。 查看原始值：
      **sysctl -a \| grep net.core.rmem_max**
      **sysctl -a \| grep net.core.rmem_default**
      修改：
      **sysctl -w net.core.rmem_max=2129920**
      **sysctl -w net.core.rmem_default=2129920**
      具体数值与业务场景有关，可逐步增大尝试后再观测缓冲区丢包计数是否增长。
      
    
3. 检查是否为CPU单核软中断高导致丢包。 通过执行**top**命令后按1查看每个CPU核的si列占比，确认是否为CPU单核软中断高导致未能及时接收数据。若是，您可以通过以下步骤进行排查处理：
   1. 选择开启RPS，使软中断分配更为均衡。 RPS采用软件模拟的方式，实现了多队列网卡所提供的功能，分散了在多CPU系统上数据接收时的负载，把软中断分到各个CPU处理，而不需要硬件支持，一般与RFS配合使用。RFS则是尽力保证同一会话仍然由"上次"的CPU处理，这样可以保证cache的热度，提高cache的命中率。
      1. 获取RPS/RFS默认设置。 执行以下命令，获取接收队列0所设置的CPU掩码，表示将接收队列0散列到指定CPU上，数值是按照16进制表示，如0、1、2、3四个CPU对应的值分别是1、2、4、8。
         **cat /sys/class/net/eth0/queues/rx-0/rps_cpus**
         执行以下命令命令获取接收队列0的最大栈/流程数。
         **cat /sys/class/net/eth0/queues/rx-0/rps_flow_cnt**
         执行以下命令，获取内核可以操控的任意指定CPU可控制的最大栈/流程数，是一个系统参数。
         **cat /proc/sys/net/core/rps_sock_flow_entries**
         以上三个参数共同构成RPS/RFS功能。
         
      
      
      
      2. 开启RPS。 以下示例是将eth0网卡接收队列0散列到CPU2核，CPU3核使用eth0的1号接收队列，设置每个接收队列最大栈数为4096，CPU可控制的最大栈为65536：
         **echo 4 \> /sys/class/net/eth0/queues/rx-0/rps_cpus**
         **echo 8 \> /sys/class/net/eth0/queues/rx-1/rps_cpus**
         **echo 4096 \> /sys/class/net/eth0/queues/rx-0/rps_flow_cnt**
         **echo 4096 \> /sys/class/net/eth0/queues/rx-1/rps_flow_cnt**
         **echo 65536 \> /proc/sys/net/core/rps_sock_flow_entries**
         一般单网卡队列多CPU场景建议开启RPS以获得更优性能。
         
      
      3. 关闭RPS。 在多网卡队列场景，根据业务场景的不同，关闭RPS有时能提高性能，以下为临时关闭RPS的操作：
         **for rps_file in /sys/class/net/eth0/queues/rx-\*/rps_cpus; do echo 0 \> "$rps_file"; done**
         **for rps_flow in /sys/class/net/eth0/queues/rx-\*/rps_flow_cnt; do echo 0 \> "$rps_flow"; done**
         **echo 0 \> /proc/sys/net/core/rps_sock_flow_entries**
         
       
   
   2. 检查业务程序是否会引发软中断分配不均匀。 如果是业务程序引发的软中断分配不均匀，可通过[场景一：单位CPU获取的带宽（Gbps）或者吞吐量（Mpps）最大]中保证一个业务进程或线程固定在一个CPU的操作方法进行业务绑核以缓解。
      
    
 
