Linux性能优化

性能工具图谱

基准测试工具图谱

CPU

系统层面的一些优化手段:

一些分析诊断工具

平均负载

平均负载统计了这两种情况的进程:

  1. Linux 进程调度器中可运行队列(Running Queue)一段时间(1 分钟,5 分钟,15 分钟)的进程平均数
  2. Linux 进程调度器中休眠队列(Sleeping Queue)里的一段时间的 TASK_UNINTERRUPTIBLE 状态下的进程平均数

从上面两点可知,平均负载跟CPU 使用率没有直接关系

Load Average = 可运行队列进程平均数 + 休眠队列中不可打断的进程平均数

处于 D 状态(主要集中在 disk I/O 的访问和信号量(Semaphore)锁的访问)的进程即休眠队列中不可打断的进程过多就会增加负载,这是性能下降的表现

在只有 2 个 CPU 的系统上,平均负载为 2 代表所有的 CPU 都刚好被完全占用,大量 IO 会导致平均负载变高,但 CPU 使用率却不一定高

top 命令展示的三个值,分别为 1 分钟、5 分钟、15 分钟内的平均负载

上下文切换

vmstat 可以查看系统整体上下文切换情况:

pidstat 则可以查看单个进程的上下文切换情况,cswch ,表示每秒自愿上下文切换(voluntary context switches)的次数,另一个则是 nvcswch ,表示每秒非自愿上下文切换(non voluntary context switches)的次数

中断处理也需要上下文切换,可以通过 /proc/interrupts 查看中断最多的类型

CPU使用率

通过 top 可以查看 CPU 在不同场景下的运行时间:

20221222181330

为了在具体进程找到占用 CPU 时钟最多的函数或者指令,可以通过 perf top 查看函数名或函数地址

软中断

Linux 将中断处理过程分成了两个阶段,也就是上半部和下半部:

网卡接收到数据包后,会通过硬件中断的方式通知内核有新的数据到了对上半部来说,既然是快速处理,其实就是要把网卡的数据读到内存中,然后更新一下硬件寄存器的状态(表示数据已经读好了),最后再发送一个软中断信号,通知下半部做进一步的处理下半部被软中断信号唤醒后,需要从内存中找到网络数据,再按照网络协议栈,对数据进行逐层解析和处理,直到把它送给应用程序

上半部会打断 CPU 正在执行的任务,然后立即执行中断处理程序。而下半部以内核线程的方式执行,并且每个 CPU 都对应一个软中断内核线程,名字为 “ksoftirqd/CPU 编号”

当软中断事件的频率过高时,内核线程也会因为 CPU 使用率过高而导致软中断处理不及时,进而引发网络收发延迟、调度缓慢等性能问题

内存

通过 free 可以查看整体内存使用情况:

通过 top 查看具体进程的内存情况:

mindmap  内存性能指标    系统内存指标      已用内存      剩余内存      可用内存      缺页异常        主缺页异常        次缺页异常      缓存/缓冲区        使用量        命中率      Slabs    进程内存指标      虚拟内存(VSS)      常驻内存(RSS)      按比例分配共享内存后的物理内存(PSS)      独占内存(USS)      共享内存      SWAP内存      缺页异常        主缺页异常        次缺页异常    SWAP      已用空间      剩余空间      换入速度      换出速度

一些分析诊断工具

系统层面的优化手段:

分配与回收

不同内存分配器(TCMalloc、Ptmalloc2)适合不同的场景(分配单元大小、是否并发环境),栈上分配的内存相比堆内分配的内存,申请效率高,回收也容易

malloc()

对小块内存(小于 128K),C 标准库使用 brk() 来分配,也就是通过移动堆顶的位置来分配内存。这些内存释放后并不会立刻归还系统,而是被缓存起来,这样就可以重复使用,brk() 方式的缓存,可以减少缺页异常的发生,提高内存访问效率

而大块内存(大于 128K),则直接使用内存映射 mmap() 来分配,也就是在文件映射段找一块空闲内存分配出去

当进程通过 malloc() 申请内存后,内存并不会立即分配,而是在首次访问时,才通过缺页异常陷入内核中分配内存

buff/cache

buff 是对磁盘读写数据的缓存,而 cache 是对文件读写数据的缓存

在读写普通文件时,会经过文件系统,由文件系统负责与磁盘交互;而读写磁盘或者分区时,就会跳过文件系统,也就是所谓的裸I/O

swap

swap 是一种虚拟内存交换到磁盘的机制

有一个专门的内核线程用来定期回收内存,也就是 kswapd0

kswapd0 定义了三个内存阈值:页最小阈值(pages_min)、页低阈值(pages_low)和页高阈值(pages_high)

NUMA 下,多个处理器被划分到不同 Node 上,且每个 Node 都拥有自己的本地内存空间,每个 Node 会有自己的 swap

Linux 提供了一个 /proc/sys/vm/swappiness 选项,用来调整使用 Swap 的积极程度:swappiness 的范围是 0-100,数值越大,越积极使用 Swap,也就是更倾向于回收匿名页;数值越小,越消极使用 Swap,也就是更倾向于回收文件页

IO

mindmap  IO 性能指标    磁盘      使用率      IOPS      吞吐量      响应时间      缓冲区      相关因素        读写类型(如顺序还是随机)        读写比例        读写大小        存储类型(如RAID级别、本地还是网络)    文件系统      存储空间容量、使用量以及剩余空间      索引节点容量、使用量以及剩余量      缓存        页缓存        目录项缓存        索引节点缓存        具体文件系统缓存(如ext4的缓存)      IOPS(文件IO)      响应时间(延迟)      吞吐量(B/s)

一些优化手段:

容量

Linux 通过 inode 记录文件的元数据

可以通过 df -i 查看 inode 所占用的空间

缓存

内核使用 Slab 机制,管理目录项和 inode 的缓存,可以通过 /proc/slabinfo 查看这个缓存

IO栈

由上到下分为三个层次,分别是:

磁盘性能指标

在数据库、大量小文件等这类随机读写比较多的场景中,IOPS 更能反映系统的整体性能;而在多媒体等顺序读写较多的场景中,吞吐量才更能反映系统的整体性能

网络

网络包的接收流程:当一个网络帧到达网卡后,网卡会通过 DMA 方式,把这个网络包放到收包队列中,通过硬中断告诉中断处理程序已经收到了网络包,接着,网卡中断处理程序会为网络帧分配内核数据结构(sk_buff),并将其拷贝到 sk_buff 缓冲区中;然后再通过软中断,通知内核收到了新的网络帧,内核协议栈从缓冲区中取出网络帧,并通过网络协议栈,从下到上逐层处理这个网络帧

网络包发送流程:内核协议栈处理过的网络包被放到发包队列后,会有软中断通知驱动程序发包队列中有新的网络帧需要发送。驱动程序通过 DMA ,从发包队列中读出网络帧,并通过物理网卡把它发送出去

性能指标

通过 ifconfig 可以看到网卡网络收发的字节数、包数、错误数以及丢包情况:

netstat 可以看到套接字的接收队列(Recv-Q)和发送队列(Send-Q)

当套接字处于连接状态(Established)时,Recv-Q 表示套接字缓冲还没有被应用程序取走的字节数(即接收队列长度)。而 Send-Q 表示还没有被远端主机确认的字节数(即发送队列长度)。

当套接字处于监听状态(Listening)时,Recv-Q 表示全连接队列的长度。而 Send-Q 表示全连接队列的最大长度。全连接是指服务器收到了客户端的 ACK,完成了 TCP 三次握手,然后就会把这个连接挪到全连接队列中,半连接是指还没有完成 TCP 三次握手的连接

netstat -s 可以查看协议栈各层的统计信息

工作模型

主进程 + 多个 worker 子进程:主进程执行 bind() + listen() 后,创建多个子进程;然后在每个子进程中,都通过 accept() 或 epoll_wait() ,来处理相同的套接字

监听到相同端口的多进程模型:所有的进程都监听相同的接口,并且开启 SO_REUSEPORT 选项,由内核负责将请求负载均衡到这些监听进程中去

NAT

NAT 基于 Linux 内核的 conntrack 连接跟踪机制来实现,Linux 内核需要为 NAT 维护状态,维护状态也带来了很高的性能成本

传输层

握手优化

  1. tcp_syn_retries:控制SYN重传次数。通过调整此参数,可以避免在网络状况较差的情况下过多地重传SYN报文,减少握手过程的延迟。
  2. tcp_max_syn_backlog:调整SYN半连接队列的长度。根据netstat -s的统计结果,判断队列长度是否合适。通过调整此参数,可以防止SYN队列溢出,确保连接建立成功。
  3. tcp_synack_retries:控制服务器回复SYN+ACK报文的重试次数。在网络稳定的情况下,适当减小此参数的值,可以减少不必要的重试,提高握手的效率。
  4. tcp_syncookies:设置为1以启用SYN cookie功能。在遭受SYN洪泛攻击时,当SYN队列满后,启用syncookie可以确保连接成功建立,提高系统的抗攻击能力。
  5. tcp_abort_on_overflow:通过向客户端发送RST报文通知连接建立失败,防止accept队列溢出。当accept队列溢出时,默认系统会丢弃ACK报文。调整此参数,可以在队列溢出时采取更明确的处理方式,提高系统的稳定性。
  6. backlog参数和somaxconn系统参数:通过调整listen函数的backlog参数和系统的somaxconn参数,可以提高accept队列的上限。这对于处理大量连接请求时非常有用,确保能够容纳更多的连接并提高系统的扩展性和性能
---title: 使用TFO(TCP Fast Open )减少一轮RTT---sequenceDiagram  participant Client as 客户端  participant Server as 服务器  Client->>Server: SYN (请求 TFO 功能)  Note over Server: 生成加密的 Cookie  Server-->>Client: SYN+ACK (带加密的 Cookie)  Note over Client: 缓存 Cookie  Client->>Server: SYN+Data (携带请求数据和缓存的 Cookie)  Server-->>Client: ACK+Data (携带响应数据)

挥手优化

  1. tcp_orphan_retries:允许在tcp_orphan_retries次数内重发FIN报文。该参数控制了主动方在处理丢包情况时的重传次数。通过适当调整此参数,可以减少因丢包而导致挥手过程延迟的情况,从而优化性能。2.tcp_fin_timeout:定义了在FIN_WAIT2状态下,在tcp_fin_timeout秒内没有收到对方的FIN报文时,连接直接关闭。调整此参数可以控制在没有收到对方FIN报文时的等待时间,避免无效等待,提高挥手的效率。
  2. tcp_max_orphans:定义了最大孤儿连接的数量。孤儿连接指的是关闭连接时对方没有收到FIN报文的连接。通过设置该参数,可以限制系统中孤儿连接的数量,防止资源被过多的孤儿连接占用。
  3. tcp_max_tw_buckets:定义了最大TIME_WAIT状态的数量。TIME_WAIT状态是指在挥手过程中进入的状态,持续一段时间以确保网络中的延迟数据段被处理完毕。通过设置该参数,可以限制系统中TIME_WAIT状态的数量,避免占用过多的资源。
  4. tcp_tw_reuse和tcp_timestamps:设置tcp_tw_reuse为1,并启用tcp_timestamps,可以将TIME_WAIT状态的端口复用于作为客户端的新连接。这样可以提高端口的利用率,避免TIME_WAIT状态占用过多的资源,但同时有数据错乱的风险

缓冲区优化

  1. tcp_window_scaling:通过将tcp_window_scaling设置为1,在Linux下提升滑动窗口的上限。滑动窗口是控制TCP发送速度和接收方处理能力的重要参数。通过提高滑动窗口的上限,可以提高TCP的发送速度,同时兼顾接收方的处理能力,从而优化性能。
  2. tcp_mem:内核缓冲区大小的调节参考依据。内核缓冲区决定了滑动窗口的上限。为了避免过大的缓冲区减少并发连接数,应将缓冲区的上限设置为带宽时延积。tcp_mem参数提供了缓冲区大小的参考范围。
  3. Socket 的 SO_SNDBUF:发送缓冲区大小的调节功能。默认情况下,Linux会自动调节发送缓冲区的大小。通过适当调整SO_SNDBUF选项,可以对发送缓冲区大小进行手动设置,以满足带宽和时延积的要求。
  4. tcp_moderate_rcvbuf:接收缓冲区大小的调节功能。通过将tcp_moderate_rcvbuf设置为1,开启接收缓冲区的自动调节功能。根据tcp_mem参数的设置,系统可以根据网络状况和带宽时延积的要求自动调整接收缓冲区的大小,以优化性能

拥塞控制优化

  1. 连接建立成功后,拥塞控制算法就会发生作用,首先进入慢启动阶段。决定连接此时网速的是初始拥塞窗口,改它。通常,在带宽时延积较大的网络中,应当调高初始拥塞窗口
  2. 可以通过 tcp_congestion_control 配置以 BBR 算法为代表的测量型拥塞控制算法

TLS优化

  1. 对称加密算法:选择安全性高且性能好的对称加密算法,如AES-GCM,该算法能够充分利用多核CPU的并行计算能力
  2. 密钥协商算法:选择性能较好的密钥协商算法,如基于椭圆曲线的ECDH算法,尤其是X25519曲线。
  3. 会话复用和缓存:通过长连接复用会话,减少密钥协商次数,并使用会话缓存和会话票据。
  4. 升级到TLS1.3:提升握手速度(减少了一次RTT)和安全性,限制不安全算法,难以进行降级攻击

网络层

路由和转发的角度

调整 MTU 大小:很多网络设备都支持巨帧,可以把 MTU 调大为 9000,提升网络吞吐量

为了避免 ICMP 主机探测、ICMP Flood 等各种网络问题:可以禁止 ICMP 协议,即设置 net.ipv4.icmp_echo_ignore_all = 1,还可以禁止广播 ICMP,即设置 net.ipv4.icmp_echo_ignore_broadcasts = 1

链路层

由于网卡收包后调用的中断处理程序(特别是软中断),需要消耗大量的 CPU。所以,将这些中断处理程序调度到不同的 CPU 上执行,就可以显著提高网络吞吐量

将原先内核中通过软件处理的功能卸载到网卡中,通过硬件来执行:

对于网络接口本身:

内核线程

Linux 在启动过程中 2 号进程为 kthreadd 进程,在内核态运行,用来管理内核线程

ps -f --ppid 2 -p 2