引擎优化笔记1

 根据perf 工具可以看到目前引擎问题主要是:

 

 

内核协议栈以及软中断问题;细分的话 就是 自旋锁、cache-misses、cs 进程上下文切换

1、应用层目前预计只能从数据结构优化; 比如使用haproxy的ebtree经行优化。但是 我们使用了fdtable ,所以无用。ebtree(见https://blog.csdn.net/xiefangjin/article/details/50932201)

2、内核协议栈方面:

 

怎么优化测试呢??

1、

可以看到 function call interrupts 数据一直都在增高。

 LOC-local timer interrupt:

SPU- spurious interrupt:

RES-Rescheduling interrupt:

CAL-Function call interrupt:

TLB-TLB shootdowns:

 对于接收数据调优:

1、中断合并(Interrupt coalescing)-中断合并会将多个中断事件放到一起,到达一定的阈值之后才向 CPU 发起中断请求;防止中断风暴,提升吞吐。减少中断数量能使吞吐更高,但延迟也变大,CPU 使用量下降;这个参数 为InterruptThrottleRate 部分网卡支持。

 同时有的网卡支持“自适应 RX/TX 硬中断合并“   见“adaptive-rx”参数

2、在多 CPU 的环境中,还有一个中断平衡的问题,比如,网卡中断会教给哪个 CPU 处理,这个参数控制哪些 CPU 可以绑定 IRQ 中断。其中的 {number} 是对应设备的中断编号,可以用下面的命令找出:cat /proc/interrupt

比如,一般 eth1 的 IRQ 编号是 17,所以控制 eth1 中断绑定的 /proc 文件名是 /proc/irq/17/smp_affinity。上面这个命令还可以看到某些中断对应的CPU处理的次数,缺省的时候肯定是不平衡的。

设置其值的方法很简单,smp_affinity 自身是一个位掩码(bitmask),特定的位对应特定的 CPU,这样,01 就意味着只有第一个 CPU 可以处理对应的中断,而 0f(0x1111)意味着四个 CPU 都会参与中断处理。

echo 1 > /proc/irq/8/smp_affinity
----//Set the IRQ affinity for IRQ 8 to CPU 0

3、网络数据处理

3.1一旦软中断代码判断出有 softirq 处于 pending 状态,就会开始处理,执行net_rx_action

net_rx_action 从包所在的内存开始处理,包是被设备通过 DMA 直接送到内存的。
函数遍历本 CPU 队列的 NAPI 变量列表,依次出队并操作之。

处理逻辑考虑任务量(work budget)和执行时间两个因素;也就是防止处理数据包过程耗光整个 CPU

 budget 是该 CPU 的所有 NAPI 变量的总预算。这也是多队列网卡应该精心调整 IRQ Affinity 的原因。网卡数据包文到达时,触发硬件中断,处理硬中断的 CPU接下来会处理相应的软中断,进而执行上面包含 budget 的这段逻辑。

多网卡多队列可能会出现这样的情况:多个 NAPI 变量注册到同一个 CPU 上。每个 CPU 上的所有 NAPI 变量共享一份 budget。

如果没有足够的 CPU 来分散网卡硬中断,可以考虑增加 net_rx_action 允许每个 CPU处理更多包。增加 budget 可以增加 CPU 使用量

但是top的 si可能标高可以减少延迟,毕竟数据处理更加及时

3.2 NAPI 以及设备驱动

  • 如果驱动的 poll 方法用完了它的全部 weight(默认 hardcode 64/300),那它不要更改 NAPI 状态。接下来 软中断net_rx_action 来loop处理
  • 如果驱动的 poll 方法没有用完全部 weight,那它关闭 NAPI。下次有硬件中断触发,驱动的硬件处理函数调用 napi_schedule 时,NAPI 会被重新打开
  • 这也就是 中断+LOOP

之前讲过:

netif_napi_add   --驱动通知使用napi的机制,初始化响应参数,注册poll的回调函数

napi_schedule    --驱动通知kernel开始调度napi的机制,稍后poll回调函数会被调用(本次sd也会加入list)

napi_complete    --驱动告诉内核其工作不饱满即中断不多,数据量不大,改变napi的状态机,后续将采用纯中断方式响应数据

net_rx_action      --收包软中断,注册poll的回调函数会被其调用

3.3  net_rx_action 退出循环:

  • 这个 CPU 上注册的 poll 列表已经没有 NAPI 变量需要处理(!list_empty(&sd->poll_list))
  • 剩余的 budget <= 0
  • 已经满足 2 个 jiffies 的时间限制
/* If softirq window is exhausted then punt.
         * Allow this to run for 2 jiffies since which will allow
         * an average latency of 1.5/HZ.
         */
        if (unlikely(budget <= 0 ||
                 time_after_eq(jiffies, time_limit))) {
            sd->time_squeeze++;

time_squeeze 字段记录的是满足如下条件的次数:net_rx_action 有很多 work 要做但是 budget 用完了,或者 work 还没做完但时间限制到了。

调整 net_rx_action budget

net_rx_action budget 表示一个 CPU 单次轮询(poll)所允许的最大收包数量。单次 poll 收包时,所有注册到这个 CPU 的 NAPI 变量收包数量之和不能大于这个阈值。 调整:

sudo sysctl -w net.core.netdev_budget=600

如果要保证重启仍然生效,需要将这个配置写到/etc/sysctl.conf

网络处理的瓶颈体现之处

/**
 * ixgbe_poll - NAPI polling RX/TX cleanup routine
 * @napi: napi struct with our devices info in it
 * @budget: amount of work driver is allowed to do this pass, in packets
 *
 * This function will clean all queues associated with a q_vector.
 **/
int ixgbe_poll(struct napi_struct *napi, int budget)
{
    struct ixgbe_q_vector *q_vector =
                   container_of(napi, struct ixgbe_q_vector, napi);
    struct ixgbe_adapter *adapter = q_vector->adapter;
    struct ixgbe_ring *ring;
    int per_ring_budget;
    bool clean_complete = true;


    ixgbe_for_each_ring(ring, q_vector->tx)
        clean_complete &= ixgbe_clean_tx_irq(q_vector, ring);

    /* attempt to distribute budget to each queue fairly, but don't allow
     * the budget to go below 1 because we'll exit polling */
    if (q_vector->rx.count > 1)
        per_ring_budget = max(budget/q_vector->rx.count, 1);
    else
        per_ring_budget = budget;

    ixgbe_for_each_ring(ring, q_vector->rx)
        clean_complete &= ixgbe_clean_rx_irq(q_vector, ring,
                             per_ring_budget);


    /* If all work not completed, return budget and keep polling */
    if (!clean_complete)
        return budget;

    /* all work done, exit the polling mode */
    napi_complete(napi);
    if (adapter->rx_itr_setting == 1)
        ixgbe_set_itr(q_vector);
    if (!test_bit(__IXGBE_DOWN, &adapter->state))
        ixgbe_irq_enable_queues(adapter, ((u64)1 << q_vector->v_idx));

    return 0;
}
  • 然后执行 igb_clean_rx/tx_irq,---收发包具体看前面讲的驱动哪一章
  • 然后执行 clean_complete,判断是否仍然有 work 可以做。如果有,就返回 budget(回忆,这里是 hardcode 64)。在之前我们已经看到,net_rx_action 会将这个 NAPI变量移动到 poll 列表的末尾
  • 如果所有 work 都已经完成,驱动通过调用 napi_complete 关闭 NAPI,并通过调用igb_ring_irq_enable 重新进入可中断状态。下次中断到来的时候回重新打开 NAPI
igb_clean_rx_irq:主要处理如下:
  • 分配额外的 buffer 用于接收数据,因为已经用过的 buffer 被 clean out 了。一次分配 IGB_RX_BUFFER_WRITE (16)个。
  • 从 RX 队列取一个 buffer,保存到一个 skb 类型的变量中
  • 判断这个 buffer 是不是一个包的最后一个 buffer。如果是,继续处理;如果不是,继续从 buffer 列表中拿出下一个 buffer,加到 skb。当数据帧的大小比一个 buffer 大的时候,会出现这种情况
  • 验证数据的 layout 和头信息是正确的
  • 更新 skb->len,表示这个包已经处理的字节数
  • 设置 skb 的 hash, checksum, timestamp, VLAN id, protocol 字段。hash,checksum,timestamp,VLAN ID 信息是硬件提供的,如果硬件报告 checksum error,csum_error 统计就会增加。如果 checksum 通过了,数据是 UDP 或者 TCP 数据,skb 就会被标记成 CHECKSUM_UNNECESSARY
  • 构建的 skb 经 napi_gro_receive()进入协议栈
  • 更新处理过的包的统计信息
  • 循环直至处理的包数量达到 budget
  • 循环结束的时候,这个函数设置收包的数量和字节数统计信息

3.4 监控网络数据处理

/proc/net/softnet_stat 
static int softnet_seq_show(struct seq_file *seq, void *v)
{
    struct softnet_data *sd = v;
    unsigned int flow_limit_count = 0;

#ifdef CONFIG_NET_FLOW_LIMIT
    struct sd_flow_limit *fl;

    rcu_read_lock();
    fl = rcu_dereference(sd->flow_limit);
    if (fl)
        flow_limit_count = fl->count;
    rcu_read_unlock();
#endif

    seq_printf(seq,
           "%08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x
",
           sd->processed, sd->dropped, sd->time_squeeze, 0,
           0, 0, 0, 0, /* was fastroute */
           sd->cpu_collision,    /* was cpu_collision */
           sd->received_rps, flow_limit_count);
    return 0;
}

    • 每一行代表一个 struct softnet_data 变量。因为每个 CPU 只有一个该变量,所以每行
      其实代表一个 CPU
    • 每列用空格隔开,数值用 16 进制表示
    • 第一列 sd->processed,是处理的网络帧的数量。如果你使用了 ethernet bonding,
      那这个值会大于总的网络帧的数量,因为 ethernet bonding 驱动有时会触发网络数据被
      重新处理(re-processed)
    • 第二列,sd->dropped,是因为处理不过来而 drop 的网络帧数量。后面会展开这一话题
    • 第三列,sd->time_squeeze,前面介绍过了,由于 budget 或 time limit 用完而退出net_rx_action 循环的次数
    • 接下来的 5 列全是 0
    • 第九列,sd->cpu_collision,是为了发送包而获取锁的时候有冲突的次数
    • 第十列,sd->received_rps,是这个 CPU 被其他 CPU 唤醒去收包的次数
    • 最后一列,flow_limit_count,是达到 flow limit 的次数。flow limit 是 RPS 的特性
 

所以主要是要有合理的 budget

GRO(Generic Receive Offloading)

Large Receive Offloading (LRO) 是一个硬件优化,GRO 是 LRO 的一种软件实现。

两种方案的主要思想都是:通过合并“足够类似”的包来减少传送给网络栈的包数,这有助于减少 CPU 的使用量。例如,考虑大文件传输的场景,包的数量非常多,大部分包都是一段文件数据。相比于每次都将小包送到网络栈,可以将收到的小包合并成一个很大的包再送到网络栈。这可以使得协议层只需要处理一个 header,而将包含大量数据的整个大包送到用户程序。

这类优化方式的缺点就是:信息丢失。如果一个包有一些重要的 option 或者 flag,那将这个包的数据合并到其他包时,这些信息就会丢失。这也是为什么大部分人不使用或不推荐使用LRO 的原因。

LRO 的实现,一般来说,对合并包的规则非常宽松。GRO 是 LRO 的软件实现,但是对于包合并的规则更严苛。

tcpdump 的抓包点(捕获包的 tap)在整个栈的更后面一些,在GRO 之后,可能抓到非常大的包

RPS (Receive Packet Steering)

RPS 并不会减少 CPU 处理硬件中断和 NAPI poll(软中断最重要的一部分)的时间,但是可以在 packet 到达内存后,将 packet 分到其他 CPU,从其他 CPU 进入协议栈。

但是分发cpu的时候可能导致cpu 间中断变高,可能性能会下降;

RPS 的工作原理是对个 packet 做 hash,决定哪个 CPU 处理。然后 packet 放到每个 CPU独占的接收后备队列(backlog)等待处理。

这个 CPU 会触发一个进程间中断(IPI,Inter-processor Interrupt)向对端 CPU。如果当时对端 CPU 没有在处理 backlog 队列收包,这个进程间中断会触发它开始从 backlog 收包。

/proc/net/softnet_stat 其中有一列是记录 softnet_data变量(也即这个 CPU)收到了多少 IPI(received_rps 列)。

同时cat /proc/interrupts 里面的CAL 就是

对于RPS/RFS 的调优

1、RPS 在不同 CPU 之间分发 packet,但是,如果一个 flow 特别大,会出现单个 CPU 被打爆,而其他 CPU 无事可做(饥饿)的状态。因此引入了 flow limit 特性,放到一个 backlog 队列的属
于同一个 flow 的包的数量不能超过一个阈值。这可以保证即使有一个很大的 flow 在大量收包,小 flow 也能得到及时的处理。

if(qlen <= netdev_max_backlog && !skb_flow_limit(skb, qlen))

由于 input_pkt_queue 打满或 flow limit 导致的丢包可以在/proc/net/softnet_stat 里面的 dropped 列计数看到体现出来

所以可以 增大 netdev_max_backlog来 Adjusting netdev_max_backlog to prevent drops

2、net.core.dev_weight 决定了 backlog poll loop 可以消耗的整体 budget

3、Enabling flow limits and tuning flow limit hash table size  

通过设置 net.core.flow_limit_table_len的值

打开 flow limit 功能的方式是,在/proc/sys/net/core/flow_limit_cpu_bitmap 中指定一个 bitmap

再就打开或关闭 IP 协议的 early demux 选项,据说可以提高吞吐

再就是Socket receive queue memory

4、打时间戳 (timestamping)--测试收包处理延时

root@fp:~# ethtool -T ens33
Time stamping parameters for ens33:
Capabilities:
    software-transmit     (SOF_TIMESTAMPING_TX_SOFTWARE)
    software-receive      (SOF_TIMESTAMPING_RX_SOFTWARE)
    software-system-clock (SOF_TIMESTAMPING_SOFTWARE)
PTP Hardware Clock: none
Hardware Transmit Timestamp Modes: none
Hardware Receive Filter Modes: none

可以查看网卡是否支持硬件打时间戳

5、Socket 低延迟选项:busy polling

6、SO_INCOMING_CPU 使用 getsockopt 带 SO_INCOMING_CPU 选项,可以判断当前哪个 CPU 在处理这个 socket 的网络包。你的应用程序可以据此将 socket 交给在期望的 CPU 上运行的线程,增加数据本地性(data locality)和 CPU 缓存命中率;在内核邮件里面有个example

https://patchwork.ozlabs.org/project/netdev/patch/1415393472.13896.119.camel@edumazet-glaptop2.roam.corp.google.com/

 

看下 软中断debug数据: 

TIMER(定时中断),NET_RX(网络接收),SCHED(内核调度),RCU(RCU锁)等都在变化,而NET_RX TIMER变化最多

sar -n DEV 1

 

 eth2每秒接收的数据帧为4242,而发送的有8485,但是接收总大小才656KB,发送数据为556KB

 656*1024/4242=158字节  报文大小还行   没有都是 tcp的syn ack 报文 这些报文小于64

 也就是在跑转发 ;此时只能tcpdump看了 

控制TCP三次握手参数

  • SYN_SENT状态

    net.ipv4.tcp_syn_retries

  • SYN_RCVD状态

    net.ipv4.tcp_max_syn_backlog  SYN_RCVD状态连接的个数

   net.ipv4.tcp_synack_retries    被动建立连接的时候发送SYN/ACK重试的次数 

  流程是SYN分组收到的数据插入到tcp_max_syn_backlog的队列并发送回复 ACK分组收到数据从tcp_max_syn_backlog的队列中取出放入ACCEPT队列 server 从ACCEPT队列获取套接字 如果应用程序请求过慢,就会导致accept队列满 net.core.somaxconnACCEPT队列

建立TCP连接的优化

应对SYN攻击

net.core.netdev_max_backlog接收自网卡但是没有被内核协议栈处理的报文队列长度
net.ipv4.tcp_max_syn_backlog SYN_RCVD状态连接的个数
net.ipv4.tcp_abort_on_overflow 超出处理能力对新来的SYN包直接返回RST并丢弃连接
当SYN队列满了,新的SYN不进入队列,而是计算出cookie以SYN+ACK回复返回client,正常client发送报文的时候,server根据报文中的cookie恢复连接,

但是cookies会占用序列号空间(32位),会使扩充窗口或者时间戳失效

文件句柄上限

操作系统全局 :fs.file-max,可以用sysctl -a | grep file-max查看,使用是file-nr

用户级别 :/etc/security/limits.conf的nofile

参考来自:http://arthurchiao.art/blog/monitoring-network-stack/

原文地址:https://www.cnblogs.com/codestack/p/13601665.html