• 引擎优化笔记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/

  • 相关阅读:
    Docker Warning : the backing xfs filesystem is formatted without d_type support
    docker 版本变化及说明
    CORS 跨域请求
    nginx 用户登录认证
    PipelineDB On Kafka
    Postgres 主从配置(五)
    exec() has been disabled for security reasons
    invalid PID number "" in "/usr/local/nginx/logs/nginx.pid"
    未连接到互联网
    github管理代码
  • 原文地址:https://www.cnblogs.com/codestack/p/13601665.html
Copyright © 2020-2023  润新知