linux之网络
网络模型
OSI七层网络模型
四层网络
linux网络
linux网络收发流程
网络包的接收流程
- 当一个网络帧到达网卡时,网卡通过DMA方式把网络包放到收包队列中,然后通过硬中断,告知中断处理程序已经接收到网络包。
- 网卡中断处理程序为网络帧分配内核数据结构
sk_buff
,并将其拷贝到sk_buff
缓冲区,然后通过软中断,通知内核收到了新的网络帧。 - 内核协议栈从缓冲区中取出网络帧,并通过网络协议栈从下往上逐层处理这个网络帧。
内核协议栈处理网络帧的过程如下:
- 在数据链路层检查报文的合法性,检查上层协议类型(IPV4/6),去掉数据链路层帧头、帧尾,交给网络层
- 网络层取出IP头,判断网络包的下一步去向,比如交由上层处理或者转发。如果是确认该网络包交由本机则取出上层协议类型(TCP/UDP),去掉IP头,交由传输层。
- 传输层取出TCP头或者UDP头后,根据
<源ip、源端口、目的ip、目的端口>
四元组作为标识,找到对应的socket
,并将数据拷贝到socket
的接收缓冲区中。
应用程序就可以使用socket
接口读取到最新的数据了。
相关缓冲区
- 网卡收发网络包是,通过
DMA
方式交互的环形缓冲区: 涉及DMA
和网卡交互,属于网卡设备驱动范围。 - 网卡中断处理程序为网络帧分配的内核数据结构
sk_buff
缓冲区。 - 应用程序通过网络套接字与网络协议栈交互的套接字缓冲区。
这些缓冲区都处于内核管理的内存中。
网络包的发送流程
- 应用程序调用
socket api
发送网络包 - 套接字层会把数据包放到
socket
发送缓冲区中 - 网络协议栈从
socket
发送缓冲区中取出数据包,按照TCP/IP协议栈,从上到下逐层处理。 - 传输层:增加TCP/IP头; 网络层:执行路由查找并确认下一跳的IP,并且按照MTU大小进行分片;网络接口层进行物理寻址,找到下一跳的MAC地址,然后添加帧头和帧尾,放到发包队列中
- 软中断通知驱动程序告知有新的网络帧需要发送
- 驱动程序通过
DMA
从发包队列中读取网络帧,并通过物理网卡将其发送出去。
名词有点多,一个一个来看:
- DMA: Direct Memory Access,译作直接存储器访问,将数据从一个地址空间复制到另一个地址空间,提供外设和存储器之间或者存储器与存储器之间的高速素具传递
- 硬中断:
- 软中断:软中断处理,有专门的内核线程
ksoftirqd
。每个 CPU 都会绑定一个ksoftirqd
内核线程。 sk_buff
:一个维护网络帧结构的双向链表,链表中的每一个元素都是一个网络帧Packet
。
多路复用
水平触发与边缘触发
水平触发(level-trggered):
边缘触发(edge-triggered):
网络性能指标
- 带宽: 链路的最大传输速率,单位为b/s。带宽和物理网卡的配置直接关联,实际带宽取决于整个网络链路中最小的一个。
- 时延: 网络请求发出后,一直到收到远端响应所需要的事件延迟。在不同场景下含义有所不同,比如连接建立的时间(TCP握手延时)或者一个数据包往返所需时间(rtt)
- 吞吐量:标识没有丢包时的最大数据传输速率,单位为b/s。吞吐量受带宽限制。
- PPS: 即packet per second的缩写,标识以网络包为单位的传输速率,通常用来评估网络的转发能力。在以linux服务器转发时,很容易受到网络包大小的影响。
网络性能测试
linux网络基于TCP/IP协议栈,而不同协议层的行为不同。在评估网络性能前,需要确定应用程序基于协议栈的哪一层:
- 基于http或者https的web应用程序,属于应用层,需要测试http/https的性能。
- 对于游戏服务器,为了支持更多在线人数,通常基于tcp/udp与客户端交互,此时需要测试tcp/udp的性能。
- 将linux作为软交换机或者路由器来使用,此时则更关注网络包的处理能力,特别是网络层的转发性能。
底层协议是其上各层网络协议的基础,底层的性能决定了高层的网络性能。
测试网络接口层和网络层的性能
网络接口层和网络层主要负责网络包的封装、寻址、路由、发送以及接收。这两层中每秒可处理的网络包PPS即是最重要的性能指标,特别是64B小包的处理能力。
Linux 内核自带的高性能网络测试工具 pktgen
tcp/udp性能测试
iperf
和 netperf
都是最常用的网络性能测试工具
http性能测试
要测试 HTTP 的性能,也有大量的工具可以使用,比如 ab、webbench 等,都是常用的 HTTP 压力测试工具。其中,ab 是 Apache 自带的 HTTP 压测工具,主要测试 HTTP 服务的每秒请求数、请求延迟、吞吐量以及请求延迟的分布情况等。
为了得到应用程序的实际性能,就要求性能工具本身可以模拟用户的请求负载,而 iperf、ab 这类工具就无能为力了。幸运的是,我们还可以用 wrk、TCPCopy、Jmeter 或者 LoadRunner 等实现这个目标。
网络性能优化
确定优化目标
参考TCP/IP分层协议簇,确定各层的性能预期。
除了上述描述的,针对传输层TCP/UDP,网络包的大小会影响吞吐量(BPS)、连接数、延迟等指标。
选用网络性能工具
根据指标选用工具
根据工具查询指标
网络性能优化
在获得网络基准测试报告后,通过相关性能工具,定位出网络性能瓶颈后,就可以进行优化了。
参考上述 LINUX系统下的网络协议栈和网络收发流程进行网络性能优化。
一般来说可以从: 应用程序,套接字、传输层、网络层和链路层等角度进行优化。
应用程序优化
应用程序通常是通过套接字接口进行网络操作。由于网络收发比较耗时,所以应用程序的优化,主要就是对网络IO和进程自身工作模型的优化。
- 网络IO优化:
- 使用epoll替代select和poll
- 使用异步io。 AIO允许应用程序同时发起很多IO操作,而不用等待操作完成,等到io完成后,系统会用
事件通知
的方式告知应用程序结果。不过AIO使用比较复杂,需要小心处理很多边缘情况。
- 进程模型优化:
- 主进程+ 多个worker子进程。 由主进程负责管理网络连接,子进程负责处理实际业务。
- 监听相同端口的多进程模型: 所有进程监听相同接口,并且开启
SO_REUSEPORT
选项,由内核负责,将请求负载均衡到这些监听进程中。
另外,应用层协议的优化也很重要,有如下常见的优化手段:
- 使用长连接取代短连接,可以显著降低TCP建立连接的成本。在每秒请求次数较多时,效果明显。
- 使用内存等形式缓存不常变化的数据,可以降低网络IO次数,加快应用程序响应速度。
- 使用
protocol buffer
等序列化方式,压缩网络io的数据量,提高应用程序的吞吐。 - 使用
DNS预存、预取、HTTPDNS
等方式,减少DNS解析的延迟。
套接字优化
套接字可以屏蔽掉linux内核中不同协议的差异,为应用程序提供统一的访问接口。每个套接字都有一个读写缓冲区。
- 读缓冲区: 缓存远端发来的数据,如果满就不能再接收新的数据。
- 写缓冲区:缓存要发出去的数据,如果满,应用程序的写操作就会被阻塞。
故而,为了提高网络吞吐量,通常需要调整这些缓冲区大小:
- 增大每个套接字的缓冲区大小:
net.core.optmem_max
- 增大套接字接收缓冲区大小:
net.core.rmem_max
- 增大套接字发送缓冲区大小:
net.core.wmem_max
- 增大TCP接收缓冲区大小:
net.ipv4.tcp_rmem
- 增大TCP发送缓冲区大小:
net.ipv4.tcp_wmem
除此之外,套接字的内核选项如下:
传输层优化
实际上是指对两种协议的优化。
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
协议特点进行攻击而引发的性能问题,可以考虑优化与syn
状态相关的内核选项:
增大
tcp
半连接的最大数量net.ipv4.tcp_max_syn_backlog
,或者开启TCP SYN Cookies net.ipv4.tcp_syncookies
,来绕开半连接数量限制问题。[连着不可同时使用]减少
SYN_RECV
状态连接重传SYN+ACK
包的次数net.ipv4.tcp_synack_retries
.
- 在长连接的场景中,通常使用
keepalive
检测tcp连接的状态,以便对端连接断开后可以自动回收。但是keepalive
探测间隔和重试次数一般都无法满足应用程序的性能,此时需要优化与keepalive
相关的内核选项:
- 缩短最后一次数据包到
keepalive
探测包之间的间隔时间net.ipv4.tcp_keepalive_time
; - 缩短发送
keepalive
探测包的间隔时间net.ipv4.tcp_keepalive_intvl
- 减少
keepalive
探测失败后,一直到通知应用程序前的重试次数net.ipv4.tcp_keepalive_probes
.
另外,如果使用不同的优化方法,可能会产生冲突。比如服务端使用nagle
算法,客户端开启延时确认,就很容易导致网络延时增大。
对于NAT
服务器而言,如果开启net.ipv4.tcp_tw_recycle
就很容易导致各种连接失败,已于内核的4.1版本中删除。
UDP协议优化
- 增大套接字缓冲区大小和
UDP
缓冲区范围; - 增大本地端口号范围;
- 根据
MTU
大小,调整UDP
数据包的大小,减少或者避免分片。
网络层优化
网络层负责网络包的封装、寻址和路由,包括IP、ICMP协议。在网络层最主要的优化是对路由、IP
分片以及ICMP
等进行调优。
- 从路由和转发的角度,可以调整如下内核选项:
- 在需要转发的服务器中,比如用作
nat网关
的服务器或者使用docker容器时,设置net.ipv4.ip_forward=1
开启IP转发
。 - 调整数据包的生存周期
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也需要调整。
以VXLAN为例,它在原来报文的基础上,增加了 14B 的以太网头部、 8B 的 VXLAN 头部、8B 的 UDP 头部以及 20B 的 IP 头部。换句话说,每个包比原来增大了 50B。
所以就需要交换机、路由器等的MTU
增大到1550,或者把VXLAN
封包前的MTU
减小为1450。
当然,考虑到许多网络设备都支持巨帧
,这种情况下,可以将MTU
调大为9000,以提高网络吞吐量。 - 以ICMP角度出发,为了避免ICMP主机探测、ICMP flood等各种网络问题,可以通过内核选项,限制ICMP行为。
- 设置
net.ipv4.icmp_echo_ignore_all=1
禁用ICMP
协议,外部主机就无法通过ping
等使用ICMP
协议的命令来探测主机。 - 设置
net.ipv4.icmp_echo_ignore_broadcasts=1
禁止广播ICMP
.
链路层优化
链路层负责网络包在物理网络中的传输:MAC寻址
、错误探测、通过网卡传输网络帧。
- 由于网卡收包之后调用的中断处理程序(特别是软中断),需要消耗大量的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|UDP
包的分段|分片功能交由网卡完成。- GSO (Generic Segmentation Offload): 网卡不支持TSO|ufo时,将分片和分段操作延迟到进入网卡前再执行。
- LRO(Large Receive Offload):在接收 TCP 分段包时,由网卡将其组装合并后,再交给上层网络处理。不过要注意,在需要 IP 转发的情况下,不能开启 LRO,因为如果多个包的头部信息不一致,LRO 合并会导致网络包的校验错误。
- GRO(Generic Receive Offload):GRO 修复了 LRO 的缺陷,并且更为通用,同时支持 TCP 和 UDP。
- RSS(Receive Side Scaling):也称为多队列接收,它基于硬件的多个接收队列,来分配网络接收进程,这样可以让多个 CPU 来处理接收到的网络包。
- VXLAN 卸载:也就是让网卡来完成 VXLAN 的组包功能。
- 网络接口本身
- 开启网络接口的多队列功能,每个队列可以用不同的中断号,调度到不同的CPU上执行。
- 增大网络接口的缓冲区和队列长度
- 使用
traffic control
工具,为不同的网络流量配置QOS
另外,在单机10M
场景下,网络协议栈的冗长流程才是最主要的性能负担。这种场景下,可以使用两种方式进行优化。
- 使用
DPDK
技术,跳过内核协议栈,直接由用户态进程用轮询的方式,处理网络请求。同时结合大页、cpu绑定、内存对齐、流水线并发等多种机制,优化网络包处理效率。 - 使用内核自带的
XDP
技术,在网络包进入内核协议前就对齐进行处理。nslookup ip
time nslookup ip
ping -c3 www.baidu.com
dnsmasq /etc/init.d/dnsmasq start
dig +trace +nodnssec time.geekbang.org
strace
域名劫持和规避
客户端:HTTPDNS
全局负载均衡(GSLB)
PTR 请求
tcpdump
iptable
traceroute
tracepath
nc
/proc/sys/fs/file-max
- 最大连接数是不是受限于65535个端口?
无论TCP/UDP,端口号只占16位,最大值为65535,是不是说如果使用TCP协议,单台机器、单个IP地址,并发连接数最大也只有65535个呢?
[困扰许久的问题]
linux中,通过协议、源ip、源端口、目的ip、目的端口
这个五元组来标识一个连接。
- 客户端,对于客户端而言,每个请求都需要耗费一个本地端口去连接远程服务器,由于本地端口是独占的,所以客户端最多只能发起65535个连接。
- 服务器,其通常是监听在固定端口上,等待客户端连接。由于客户端的源ip、源端口是可变的。极限情况下,服务器端的理论最大连接数可以达到2^48
服务器端是支持海量连接数的,当然还受到linux协议栈本身的性能、各种物理和软件的资源限制,实际情况会低很多C10M.
引用
1. 极客时间-linux性能优化
2. 水平触发和边缘触发
3. 陈硕-muduo网络库与服务模型介绍
4. linux版本查看
5. dns排障实例
6. rfc
7. wireshark四次挥手案例
8. 林沛满《Wireshark网络分析就这么简单》和《Wireshark网络分析的艺术》
9. DMA原理介绍