简介
高并发的系统架构中,任何细微调整,稍有不注意便会引起连锁反应,只有系统地了解整个网络栈,在处理疑难杂症或者系统优化工作中,才能做到手中有粮心中不慌。在本节,我们概览一个 Linux 系统收包的流程,以便了解高并发系统所面临的性能瓶颈问题以及相关的优化策略。
收包过程
- 网卡 eth0 收到数据包。
- 网卡通过 DMA 将数据包拷贝到内存的环形缓冲区(Ring Buffer,在网卡中有 RX Ring 和 TX Ring 两种缓冲)。
- 数据从网卡拷贝到内存后, 网卡产生 IRQ(Interupt ReQuest,硬件中断)告知内核有新的数据包达到。
- 内核收到中断后, 调用相应中断处理函数,开始唤醒 ksoftirqd 内核线程处理软中断。
- 内核进行软中断处理,调用 NAPI poll 接口来获取内存环形缓冲区(ring buffer)的数据包,送至更上层处理。
- 内核中网络协议栈:L2 处理。
- 内核中网络协议栈:L3 处理。
- 内核中网络协议栈:L4 处理。
- 网络协议栈处理数据后,并将其发送到对应应用的 socket 接收缓冲区。
高并发瓶颈
- 用户进程调用系统调用陷入内核态的开销。
- CPU 响应包的硬中断 CPU 开销
- ksoftirqd 内核线程的软中断上下文开销。
RX/TX Ring 优化
处理一个数据包会有各类的中断、softirq 等处理,因为分配给 Ring Buffer 的空间是有限的,当收到的数据包速率大于单个 CPU 处理速度的时,Ring Buffer 可能被占满并导致新数据包被自动丢弃。一个 CPU 去处理 Ring Buffer 数据会很低效,这个时候就产生 RSS、RPS 等多核并发机制来提升内核网络包的处理能力。
但是注意,开启多核并发特性
,会挤压业务代码的执行时间,如果业务属于 CPU 密集型
,会导致业务性能下降
。是否开启多核处理,需要根据业务场景考虑,根据笔者的经验来看,例如此类负载均衡服务器
、网关
、集群核心转发节点
等网络I/O 密集型场景
可以尝试优化 RSS、RPS 等配置
。
1. 判断是否需进行优化
当类似 LVS、集群核心交换节点、负载均衡服务器的场景 PPS(Packet Per Second,包每秒)指标存在一定的优化空间且这些核心节点影响了集群业务,那我们可以查看系统状态以决定是否进行内核优化。
首先我们确定是否存在丢包状况
,如果存在则进行相应的调整。查询网卡收包情况
(RX 为收到的数据、TX 为发送的数据)。
1ifconfig eth0 | grep -E 'RX|TX'
eth0
是你具体服务器的网卡地址
出现以下结果说明RX dropped 表示数据包已经进入了 Ring Buffer,但是由于内存不够等系统原因,导致在拷贝到内存的过程中被丢弃,RX overruns
为 Ring Buffer 传输的 IO 大于 kernel 能够处理的 IO 导致,overruns
的错误意味着 CPU 无法即使的处理中断是造成 Ring Buffer 溢出
。
1RX packets 490423734 bytes 193802774970 (180.4 GiB)
2RX errors 12732344 dropped 9008921 overruns 3723423 frame 0
3TX packets 515280693 bytes 140609362555 (130.9 GiB)
4TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
2. RSS 下的多队列调整
RSS(receive side steering)利用网卡多队列特性,将每个核分别跟网卡的一个首发队列绑定,以达到网卡硬中断和软中断均衡的负载在各个 CPU 中,RSS 要求网卡必须要支持多队列特性。(注意:对于大部分驱动,修改以下配置会使网卡先 down 再 up,因此会造成丢包
!)
查询 RX/TX 队列配置和使用情况。
1ethtool -l eth0
1Channel parameters for eth0:
2Pre-set maximums:
3RX: 0
4TX: 0
5Other: 0
6Combined: 8
7Current hardware settings:
8RX: 0
9TX: 0
10Other: 0
11Combined: 4
可以看到硬件最多支持 6 个,当前使用了 4 个。将 RX 和 TX queue 数量都设为 8。
1ethtool -L eth0 combined 8
3. 队列大小调整
增大 ring buffer 可以在 PPS(packets per second)很大时缓解丢包
问题
查看队列大小。
1ethtool -g eth0
1Ring parameters for eth0:
2Pre-set maximums:
3RX: 1024
4RX Mini: 0
5RX Jumbo: 0
6TX: 1024
7Current hardware settings:
8RX: 512
9RX Mini: 0
10RX Jumbo: 0
11TX: 512
以上输出显示网卡最多支持 1024 个 RX/TX 数据包大小,但是现在只用到了 512 个。 ethtool -G 修改 queue 大小。
1ethtool -G eth0 rx 1024
内核协议栈优化
一个传输少量数据的 TCP 短连接的生命周期中,握手、挥手阶段将近占用了 70%
的资源消耗,在一个高并发的服务场景,如负载均衡、数据库等,针对性的去优化较为保守内核参数提提升服务性能的必要手段。在本文中,内核协议栈参数优化方向为 TCP 握手流程中队列大小、挥手 TIME_WAITE、keepalive 保活机制以及拥塞控制。
TCP 握手流程参数优化
握手流程中有两个队列较为关键,当队列满时,多余的连接将会被丢弃。
SYN Queue
也被称为半连接队列
,是内核保持的未被 ACK 的 SYN 包最大队列长度,通过内核参数net.ipv4.tcp_max_syn_backlog
配置,高并发的环境下建议设置为1024 或更高
。Accept Queue
也被称为全连接队列
, 是一个 socket 上等待应用程序 accept 的最大队列长度。取值为 min(backlog,net.core.somaxconn)。
TCP 连接保活参数优化
当 TCP 建立连接后,会有个发送一个空的 ACK 的探测行为来保持连接(keepalive)。keepalive 受以下参数影响:
net.ipv4.tcp_keepalive_time
最大闲置时间net.ipv4.tcp_keepalive_intvl
发送探测包的时间间隔net.ipv4.tcp_keepalive_probes
最大失败次数,超过此值后将通知应用层连接失效 在大规模的集群内部,如果keepalive_time
设置较短且发送较为频繁
,会产生大量的空 ACK 报文
,存在塞满 RingBuffer 造成 TCP 丢包甚至连接断开问题。
TCP 连接断开参数优化
由于 TCP 双全工
的特性,安全关闭一个连接需要四次挥手。但在一个复杂的网络环境下,会存在很多异常情况,异常断开连接会导致产生孤儿连(半连接)。 这种连接既不能发送数据,也无法接收数据,累计过多,会消耗大量系统资源。在高并发的场景下,孤儿连过多会引起资源不足,产生 Address already in use: connect
类似的错误。
挥手流程中的主要优化为 TIME_WAIT
的参数调整。TIME_WAIT
是 TCP 挥手的最后一个状态。当收到被动方发来的 FIN 报文后,主动方回复 ACK,表示确认对方的发送通道已经关闭,继而进入TIME_WAIT 状态 ,等待 2MSL 时间后关闭连接。
如果发起连接一方的 TIME_WAIT
状态过多,会占满了所有端口资源,则会导致无法创建新连接。TIME_WAIT
的问题在反向代理中比较明显
,例如 nginx 默认行为下会对于 client 传来的每一个 request 都向 upstream server 打开一个新连接,高 QPS 的反向代理
将会快速积累 TIME_WAIT 状态的 socket,直到没有可用的本地端口,无法继续向 upstream 打开连接,此时服务将不可用。
net.ipv4.tcp_max_tw_buckets
,此数值定义系统在同一时间最多能有多少 TIME_WAIT 状态,当超过这个值时,系统会直接删掉这个 socket 而不会留下 TIME_WAIT 的状态
net.ipv4.ip_local_port_range
,TCP 建立连接时 client 会随机从该参数中定义的端口范围中选择一个作为源端口。可以调整该参数范围增大可选择的端口范围
。
相关配置参考
1net.ipv4.tcp_tw_recycle = 0
2net.ipv4.tcp_tw_reuse = 1
3net.ipv4.ip_local_port_range = 1024 65535
4net.ipv4.tcp_rmem = 16384 262144 8388608
5net.ipv4.tcp_wmem = 32768 524288 16777216
6net.core.somaxconn = 8192
7net.core.rmem_max = 16777216
8net.core.wmem_max = 16777216
9net.core.wmem_default = 2097152
10net.ipv4.tcp_max_tw_buckets = 5000
11net.ipv4.tcp_max_syn_backlog = 10240
12net.core.netdev_max_backlog = 10240
13net.netfilter.nf_conntrack_max = 1000000
14net.ipv4.netfilter.ip_conntrack_tcp_timeout_established = 7200
15net.core.default_qdisc = fq_codel
16net.ipv4.tcp_congestion_control = bbr
17net.ipv4.tcp_slow_start_after_idle = 0