You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
传输速率:一般以 PPS(Packet Per Second,包 / 秒)衡量,即以网络包为单位的传输速率,常用于评估网络转发能力,比如硬件交换机通常可以达到线性转发(即 PPS 可以达到或者接近理论最大值),基于 Linux 服务器的转发则容易受网络包大小的影响;而对 TCP 或 Web 服务,更多会用并发连接数和每秒请求数(QPS,Query per Second)等指标反映实际应用程序的性能。
-- auth.lua-- example script that demonstrates response handling and-- retrieving an authentication token to set on all future-- requeststoken=nilpath="/authenticate"request=function()
returnwrk.format("GET", path)
endresponse=function(status, headers, body)
ifnottokenandstatus==200thentoken=headers["X-Token"]
path="/resource"wrk.headers["X-Token"] =tokenendend
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
{% pullquote mindmap mindmap-md %}
{% endpullquote %}
网络模型
OSI 网络模型:即来自国际标准化组织制定的开放式系统互联通信参考模型(Open System Interconnection Reference Model),分为七层,其各自负责:
应用层:为应用程序提供统一的接口。
表示层:把数据转换成兼容接收系统的格式。
会话层:维护计算机之间的通信连接。
传输层:为数据加上传输表头,形成数据包。
网络层:数据的路由和转发。
数据链路层:MAC 寻址、错误侦测和改错。
物理层:在物理网络中传输数据帧。
TCP/IP 网络模型:Linux 中具体实现的四层网络模型。
应用层:向用户提供一组应用程序,比如 HTTP、FTP、DNS 等。对应 OSI 模型的应用层、表示层和会话层。
传输层:端到端的通信,比如 TCP、UDP 等。
网络层:网络包的封装、寻址和路由,比如 IP、ICMP 等。
网络接口层:网络包在物理网络中的传输,比如 MAC 寻址、错误侦测以及通过网卡传输网络帧等。对应 OSI 模型的数据链路层和物理层。
网络传输时,数据包会按照协议栈对上层数据逐层进行处理,然后封装该层的协议头,再发送给下一层(在原有的负载前后增加固定格式的元数据)。
由于物理链路中不能传输任意大小的数据包,网络接口配置的最大传输单元(MTU,以太网中默认值为 1500)规定了最大的 IP 包大小。
Linux 内核中的网络协议栈也类似 TCP/IP 的四层结构:
其中网卡是发送和接收网络包的基本设备。在系统启动时,网卡通过内核中的网卡驱动程序注册到系统中。在网络收发过程中,内核通过中断与网卡进行交互。
网络收发流程
网络包接收
网卡接收到网络帧:
通过 DMA 把包放到收包队列中。
通过硬中断告知中断处理程序已收到网络包。
网卡中断处理程序为网络帧分配内核数据结构(sk_buff),将其拷贝到 sk_buff 缓冲区中。
通过软中断通知内核收到了新的网络帧。
内核协议栈从缓冲区中取出网络帧,通过网络协议栈从下到上逐层处理。
网络协议栈解析:
链路层:检查报文合法性,找出上层协议类型(比如 IPv4),再去除帧头帧尾,交给网络层。
网络层:取出 IP 头,判断网络包下一步走向:交给上层处理或转发等。当确认这个包要发送到本机后,即取出上层协议类型(比如 TCP),去除 IP 头再交给传输层。
传输层:取出 TCP/UDP 头后,根据
<源 IP、源端口、目的 IP、目的端口>
四元组作为标识,并把数据拷贝到对应的 Socket 的接收缓存中。随即应用程序可以通过 Socket 接口读取到新接收的数据。
网络包发送
与接收相反,应用程序调用 Socket API(比如
sendmsg
)发起系统调用发送网络包。进入内核态的套接字层,把数据包放到 Socket 发送到缓冲区中。
网络协议栈从 Socket 发送缓冲区中取出数据包,再按照 TCP/IP 栈从上到下逐层处理:比如增加 TCP 头、IP 头,执行路由查找下一跳 IP、按照 MTU(Maximum Transmission Unit)分片等。
分片后的网络包再送到网络接口层进行物理地址寻址,以找到下一跳的 MAC 地址;再添加帧头和帧尾放到发包队列中。
随后发起软中断通知驱动程序,由驱动程序通过 DMA 从发包队列中读出网络帧,通过物理网卡把它发送出去。
套接字
套接字可屏蔽 Linux 内核中不同协议的差异,为应用程序提供统一访问接口。
每个套接字都有读写缓冲区:
读缓冲区缓存远端发来的数据,读缓冲区满就不能再接收新的数据。
写缓冲区缓存要发出去的数据,写缓冲区满,应用程序写操作就会被阻塞。
性能指标
分析网络问题常参考以下指标:
带宽:链路的最大传输速率,单位通常为 b/s。常用带宽有 1000M、10G、40G、100G 等。
吞吐量:表示单位时间内成功传输的数据量,单位通常为 b/s(比特 / 秒)或者 B/s(字节 / 秒)。其受带宽限制,网络的使用率 == 吞吐量 / 带宽。
延时:表示从网络请求发出后一直到收到远端响应所需的时间。可以表示为建立连接需要的时间(比如 TCP 握手延时),或一个数据包往返所需的时间(比如 RTT)等。
传输速率:一般以 PPS(Packet Per Second,包 / 秒)衡量,即以网络包为单位的传输速率,常用于评估网络转发能力,比如硬件交换机通常可以达到线性转发(即 PPS 可以达到或者接近理论最大值),基于 Linux 服务器的转发则容易受网络包大小的影响;而对 TCP 或 Web 服务,更多会用并发连接数和每秒请求数(QPS,Query per Second)等指标反映实际应用程序的性能。
其中带宽跟物理网卡配置是直接关联的,只是实际带宽会受限于整个网络链路中最小的模块。而“网络带宽测试”实际上是测试吞吐量。
除此之外还关心网络可用性(网络能否正常通信)、并发连接数(TCP 连接数量)、丢包率(丢包百分比)、重传率(重新传输的网络包比例)等。
网络配置
分析网络问题需要查看网络接口的配置状态,使用
ifconfig
、ip
命令(net-tools/iproute2):网络 I/O 异常状态:
errors:错误数据包数,校验错误、帧同步错误等。
dropped:丢弃数据包数。数据包已经收到了 Ring Buffer,但因为内存不足等原因丢包。
overruns:超限数据包数。网络 I/O 速度过快,导致 Ring Buffer 中的数据包来不及处理(队列满)而导致的丢包;
carrier:发生 carrirer 错误的数据包数。比如双工模式不匹配、物理电缆出现问题等。
collisions:碰撞数据包数。
套接字信息
使用
netstat
、ss
查看套接字信息:如果接收队列(Recv-Q)和发送队列(Send-Q)的值不为 0,说明有网络包的堆积发生。当 Socket 的状态为:
Established:Recv-Q 表示套接字缓冲未被应用程序取走的字节数(即接收队列长度),Send-Q 表示还没有被远端主机确认的字节数(即发送队列长度)。
Listening:Recv-Q 表示全连接队列的长度,Send-Q 表示全连接队列的最大长度。
全连接:服务器收到了客户端的 ACK、完成了 TCP 三次握手,然后把这个连接放到全连接队列中。全连接中的套接字还需要被
accept()
系统调用取走,服务器才可以开始真正处理客户端请求。半连接:未完成 TCP 三次握手的连接,服务器收到客户端 SYN 包后会把它放到半连接队列中,再向客户端发送 SYN+ACK 包。
协议栈信息
使用
netstat
或ss
查看协议栈信息:包括 TCP 协议的主动连接、被动连接、失败重试、发送和接收的分段数量等。网络质量统计信息
使用
sar
可查看网络统计信息,比如网络接口(DEV)、网络接口错误(EDEV)、TCP、UDP、ICMP 等:使用
ethtool
可查询带宽(单位 Gb/s 或 Mb/s,注意是比特):使用基于 ICMP 协议的
ping
可测试远程主机的连通性和延时:优化方法
从 C10K、C100K、C1000K、C10M 问题入手可以更好理解 Linux 网络工作原理。
从 C1000K 开始,依靠单机支撑就需要仔细考虑:
从物理资源的角度:处理大量请求需要配备大内存、万兆网卡,以及基于多网卡绑定以承载大吞吐量。
从软件资源的角度:大量的连接也会占用大量的软件资源,如文件描述符、连接状态跟踪(CONNTRACK)、网络协议栈缓存(比如套接字读写缓存、TCP 读写缓存)等。
从优化的角度:中断处理处理成本也很高,需要多队列网卡、中断负载均衡、CPU 绑定、RPS/RFS(软中断负载均衡到多个 CPU 核上),以及将网络包的处理卸载(Offload)到网络设备(如 TSO/GSO、LRO/GRO、VXLAN OFFLOAD)等各种硬件和软件的优化。
I/O 模型优化
当物理资源足够,首先考虑的是 I/O 模型:如何在一个线程内处理多个请求、如何节省资源地处理用户请求。
I/O 多路复用是著名的并发解决方案,其提供两种 I/O 事件通知方式:
水平触发:只要文件描述符可以非阻塞地执行 I/O 就会触发通知。即应用程序可随时检查文件描述符的状态,然后再根据状态进行 I/O 操作。
边缘触发:只有在文件描述符的状态发生改变(I/O 请求达到)时才发送一次通知。此时应用程序需要尽可能多地执行 I/O,直到无法继续读写才可以停止。如果 I/O 没执行完,或因为某种原因未来得及处理,这次通知就会丢失。
I/O 多路复用也有多种实现方法:
非阻塞 I/O + 水平触发通知:select 和 poll 从文件描述符列表中找出可以执行 I/O ,再进行真正的读写。由于 I/O 是非阻塞的,一个线程中就可以同时监控一批套接字文件描述符,达到单线程处理多请求的目的。但也存在以下问题:
需要对文件描述符列表进行轮询,请求数多时会比较耗时。
select 使用固定长度的位相量表示文件描述符集合,有最大描述符数量的限制(32 位系统 1024);轮询检查套接字状态,处理耗时 O(N)。
poll 使用不定长数组存放文件描述符,没有最大描述符数量的限制(只受系统文件描述符限制),但也需要轮询文件描述符列表。
调用 select 和 poll 时需要把文件描述符集合从用户空间传入内核空间,由内核修改后再传出到用户空间中,存在切换成本。
非阻塞 I/O + 边缘触发通知:Linux 2.6 新增的 epoll 使用 红黑树 在内核中管理 文件描述符,不需要应用程序在每次操作时都传入传出;使用事件驱动机制,只关注有 I/O 事件发生的文件描述符,而不是轮询扫描整个集合。
异步 I/O(Asynchronous I/O):Linux 2.6 新增的异步 I/O 允许应用程序同时发起多个 I/O 操作,在 I/O 完成后系统以事件通知(比如信号或者回调函数)告知应用程序前来查询结果。然而在较长时间内都不完善(比如 glibc 提供的异步 I/O 库),且使用难度较高。
工作模型优化
I/O 多路复用还可以结合不同工作模型使用:
主进程 + 多个 worker 子进程
Nginx 反向代理的原理就是基于此:主进程用于初始化套接字,执行
bind()
+listen()
后创建多个子进程、管理其生命周期,每个子进程都通过accept()
或epoll_wait()
来处理相同套接字。可以用线程代替进程:主线程负责套接字初始化和子线程状态的管理,而子线程则负责实际的请求处理。线程的调度和切换成本比较低,再进一步把
epoll_wait()
都放到主线程中,保证每次事件都只唤醒主线程,子线程只需负责后续的请求处理。监听到相同端口的多进程
所有的进程监听相同端口,并开启
SO_REUSEPORT
选项(Linux 3.9+),由内核将请求负载均衡到监听进程中。内核确保只有一个进程被唤醒,不会出现惊群问题。
DPDK 和 XDP
在 C10M 问题中,单靠常规软硬件优化已经难以满足需求。
DPDK(Data Plane Development Kit,数据平面开发套件)是用户态网络标准,其跳过内核协议栈、由用户态进程轮询处理网络接收:
PPS 非常高的场景中,查询时间比实际工作时间少很多,绝大部分时间都能处理网络包。
跳过内核协议栈后省去了繁杂的处理路径,应用程序可针对具体场景优化网络包处理逻辑,不需要关注所有细节。
通过大页、CPU 绑定、内存对齐、流水线并发等多种机制,优化网络包的处理效率。
XDP(eXpress Data Path):是 Linux 内核提供的高性能网络数据路径,它允许网络包在进入内核协议栈之前就进行处理。XDP 底层和 bcc-tools 一样,都是基于 Linux 内核的 eBPF 机制实现。其对内核的要求比较高(Linux 4.8+),并且不提供缓存队列。基于 XDP 的应用程序通常是专用的网络应用,如 IDS(入侵检测系统)、DDoS 防御、 cilium 容器网络插件等。
性能测试
基准测试
测试前应确定待评估的应用程序网络性能属于协议栈哪一层。
转发性能
包括网络接口层和网络层,通常关注每秒可处理的网络包数 PPS,特别是 64B 小包的处理能力。
使用
hping3
工具可测试网络包处理能力的性能,但更常用的是内核自带的 pktgen,其作为一个内核线程运行,需要加载 pktgen 内核模块后再通过/proc
文件系统来交互:pktgen 在每个 CPU 上启动一个内核线程,可通过
/proc/net/pktgen
下的同名文件与线程交互;pgctrl
用于开启和停止测试。假设一台机器通过 eth0 网卡向另一台机器发包:
一段时间测试完成后,可以从
/proc
获取结果:测试结果可以参照千兆交换机的 PPS:线速(满负载、无差错转发)PPS 即 1000Mbit 除以以太网帧的大小,
1000Mbps/((64+20)*8bit) = 1.5 Mpps
(20B 为以太网帧前导和帧间距的大小)。TCP/UDP 性能
一些应用直接基于 TCP/UDP 构建服务,适合使用
iperf
和netperf
测试吞吐量。安装 iperf3:目标机器上启动 iperf3 服务端:
# -s 启动服务端,-i 汇报间隔,-p 监听端口 iperf3 -s -i 1 -p 10000
在另一台机器上运行 iperf3 客户端:
# -c 启动客户端(指定目标服务器 ip),-b 目标带宽(单位是bits/s),-t 表示测试时间,-P 并发数,-p 目标服务器监听端口 iperf3 -c 192.168.0.30 -b 1G -t 15 -P 2 -p 10000
在服务端上查看输出:
HTTP 性能
webbench
、ab
(Apache 自带)等都是常用的 HTTP 压力测试工具,主要测试 HTTP 服务的每秒请求数、请求延迟、吞吐量以及请求延迟的分布情况等。安装 ab:apt install -y apache2-utils # CentOS: yum install -y httpd-tools
使用
ab
测试 Nginx 性能:应用负载性能
工具本身的性能对测试至关重要。通过以上工具测试不能直接表示应用程序的实际性能,其与用户的实际请求很可能不一致。比如用户请求往往附带各种负载(payload),会影响 Web 应用程序内部的处理逻辑从而影响最终性能。
因此测试还要求性能工具本身可以模拟用户的请求负载,常用的工具有 wrk、TCPCopy、Jmeter、LoadRunner 等(后两者甚至提供脚本录制、回放、GUI 等功能)。
wrk 内置 LuaJIT,可以用来实现复杂场景的性能测试。在调用 Lua 脚本时可将 HTTP 请求分为三个阶段,即 setup、running、done:
比如可以在 setup 阶段,为请求设置认证参数:
再启动测试:
优化总结
性能优化首先是要确定目标,不同应用中每个指标的优化标准、优先级都不同:
对于 NAT 网关而言,其直接影响整个数据中心的网络出入性能,所以通常需要达到或接近线性转发,PPS 是最主要的性能目标。
对于数据库、缓存等系统而言,目的是快速完成网络收发,即延迟是主要的性能目标。
对于 Web 服务而言,需要同时兼顾吞吐量和延迟。
应用层
即优化 I/O 模型、工作模型以及应用层网络协议。
选用网络模型:
使用 I/O 多路复用 epoll 取代 select 和 poll。
使用异步 I/O(比较复杂)。
选用工作模型:
主进程(管理网络连接)+ 多个 worker 子进程(实际的业务处理)。
监听到相同端口的多进程模型。所有进程监听相同接口,开启 SO_REUSEPORT 选项,由内核负责进程的负载均衡。
协议优化:
使用长连接取代短连接:可显著降低 TCP 连接建立成本,在每秒请求次数较多时效果非常明显。
使用内存来缓存不常变化的数据:可降低网络 I/O 次数,同时加快应用程序的响应速度。
使用 Protocol Buffer 等序列化的方式,压缩网络 I/O 的数据量,可提高应用程序的吞吐。
使用 DNS 缓存、预取、HTTPDNS 等方式减少 DNS 解析的延迟,并提升网络 I/O 整体速度。
套接字层
为了提高网络的吞吐量,通常需要调整缓冲区的大小。
增大每个套接字的缓冲区大小 net.core.optmem_max。
增大套接字接收缓冲区大小 net.core.rmem_max 和发送缓冲区大小 net.core.wmem_max。
增大 TCP 接收缓冲区大小 net.ipv4.tcp_rmem 和发送缓冲区大小 net.ipv4.tcp_wmem(三个数值分别是 min,default,max,系统会根据设置自动调整 TCP 接收 / 发送缓冲区的大小,UDP 同理)。
修改网络连接行为的配置项:
为 TCP 连接设置 TCP_NODELAY 可禁用 Nagle 算法。
为 TCP 连接开启 TCP_CORK 可让小包聚合成大包后再发送(会阻塞小包发送)。
使用 SO_SNDBUF 和 SO_RCVBUF 可分别调整套接字发送缓冲区和接收缓冲区大小。
传输层
主要时优化 TCP 和 UDP 协议。首先要了解 TCP 基本原理(比如流量控制、慢启动、拥塞避免、延迟确认)以及状态流转:
TCP
TIME_WAIT 状态优化:对于请求量较大的场景,可能会看到大量处于 TIME_WAIT 状态的连接占用内存和端口资源。
增大处于 TIME_WAIT 状态的连接数量 net.ipv4.tcp_max_tw_buckets ,并增大连接跟踪表的大小 net.netfilter.nf_conntrack_max。
减小 net.ipv4.tcp_fin_timeout 和 net.netfilter.nf_conntrack_tcp_timeout_time_wait ,让系统尽快释放其所占用的资源。
开启端口复用 net.ipv4.tcp_tw_reuse。被 TIME_WAIT 状态占用的端口还能用到新连接中。增大本地端口的范围 net.ipv4.ip_local_port_range 以支持更多连接、提高整体并发能力。
增加最大文件描述符数量。使用 fs.nr_open 和 fs.file-max 分别增大进程和系统的最大文件描述符数;或在应用程序的 systemd 配置文件中设置应用程序的最大文件描述符数 LimitNOFILE。
缓解 SYN FLOOD:
增大 TCP 半连接最大数量 net.ipv4.tcp_max_syn_backlog ,或开启 TCP SYN Cookies net.ipv4.tcp_syncookies 绕开半连接数量限制(不可同时使用)。
减少 SYN_RECV 状态连接重传 SYN+ACK 包次数 net.ipv4.tcp_synack_retries。
Keepalive 优化:长连接场景通常使用 Keepalive 检测 TCP 连接状态,以便对端连接断开后自动回收。系统默认的 Keepalive 探测间隔和重试次数一般无法满足应用程序的性能要求。
缩短最后一次数据包到 Keepalive 探测包的间隔时间 net.ipv4.tcp_keepalive_time。
缩短发送 Keepalive 探测包的间隔时间 net.ipv4.tcp_keepalive_intvl。
减少 Keepalive 探测失败后,一直到通知应用程序前的重试次数 net.ipv4.tcp_keepalive_probes。
UDP
增大套接字缓冲区大小以及 UDP 缓冲区范围。
增大本地端口号的范围。
根据 MTU 大小调整 UDP 数据包大小,减少或避免分片。
网络层
主要是对路由、 IP 分片以及 ICMP 等进行调优。
从路由和转发的角度出发:
在需要转发的服务器中(比如 NAT 网关或使用 Docker 容器时),开启 IP 转发,即设置 net.ipv4.ip_forward = 1。
调整数据包的生存周期 TTL,比如设置 net.ipv4.ip_default_ttl = 64。而增大该值会降低系统性能。
开启数据包的反向地址校验,比如设置 net.ipv4.conf.eth0.rp_filter = 1。可以防止 IP 欺骗,并减少伪造 IP 带来的 DDoS 问题。
从分片的角度出发:最主要是调整 MTU 的大小。
通常根据以太网标准设置,网络帧最大为 1518B,去掉以太网头部 18B 后剩余 1500 即为 MTU。在使用 VXLAN、GRE 等叠加网络技术会使原来的网络包变大,MTU 也要相应增大。
很多网络设备支持巨帧,还可以把 MTU 调大为 9000,以提高网络吞吐量。
从 ICMP 的角度出发:为避免 ICMP 主机探测、ICMP Flood 等网络问题,可通过内核选项限制 ICMP 行为。
禁止 ICMP 协议,即设置 net.ipv4.icmp_echo_ignore_all = 1,外部主机无法通过 ICMP 探测主机。
禁止广播 ICMP,即设置 net.ipv4.icmp_echo_ignore_broadcasts = 1。
链路层
主要是优化网络包的收发、网络功能卸载以及网卡选项。
网卡收包后调用中断处理程序(特别是软中断)需要消耗大量的 CPU,而将中断处理程序调度到不同的 CPU 上执行可以显著提高网络吞吐量。
为网卡硬中断配置 CPU 亲和性(smp_affinity),或者开启 irqbalance 服务。
开启 RPS(Receive Packet Steering)和 RFS(Receive Flow Steering),将应用程序和软中断的处理调度到相同 CPU 上,可以增加 CPU 缓存命中率,减少网络延迟。
利用网卡的功能:在内核中通过软件处理的功能转移到网卡中。
TSO(TCP Segmentation Offload)和 UFO(UDP Fragmentation Offload):在 TCP/UDP 协议中直接发送大包;而 TCP 包的分段(按照 MSS 分段)和 UDP 的分片(按照 MTU 分片)功能由网卡来完成 。
GSO(Generic Segmentation Offload):在网卡不支持 TSO/UFO 时,将 TCP/UDP 包的分段延迟到进入网卡前再执行。可以减少 CPU 的消耗,以及在发生丢包时只重传分段后的包。
LRO(Large Receive Offload):在接收 TCP 分段包时,由网卡将其组装合并后再交给上层网络处理。在需要 IP 转发时下不能开启 LRO,如果多个包的头部信息不一致,LRO 合并会导致网络包的校验错误。
GRO(Generic Receive Offload):修复了 LRO 的缺陷并且更为通用,同时支持 TCP 和 UDP。
RSS(Receive Side Scaling):也称为多队列接收,基于硬件的多个接收队列来分配网络接收进程,使得让多个 CPU 来处理接收到的网络包。
VXLAN 卸载:让网卡完成 VXLAN 的组包功能。
优化网络接口:可提升网络吞吐量。
开启网络接口的多队列功能。每个队列可以用不同的中断号调度到不同 CPU 上执行,从而提升网络的吞吐量。
增大网络接口的缓冲区大小以及队列长度等,提升网络传输的吞吐量(可能导致延迟增大)。
使用 Traffic Control 工具为不同网络流量配置 QoS。
绕过内核协议栈:
DPDK:跳过内核协议栈,直接由用户态进程轮询处理网络请求。再结合大页、CPU 绑定、内存对齐、流水线并发等多种机制,优化网络包的处理效率。
XDP:内核自带,在网络包进入内核协议栈前就对其进行处理。
关于工具和参数的选用,可以参考下表:
参考
分析案例可参考:
37 | 案例篇:DNS 解析时快时慢,我该怎么办?
38 | 案例篇:怎么使用 tcpdump 和 Wireshark 分析网络流量?
39 | 案例篇:怎么缓解 DDoS 攻击带来的性能下降问题?
40 | 案例篇:网络请求延迟变大了,我该怎么办?
41 | 案例篇:如何优化 NAT 性能?(上)
42 | 案例篇:如何优化 NAT 性能?(下)
Beta Was this translation helpful? Give feedback.
All reactions