网络模型

OSI七层网络模型

四层网络

linux网络

linux网络收发流程
网络包的接收流程
  1. 当一个网络帧到达网卡时,网卡通过DMA方式把网络包放到收包队列中,然后通过硬中断,告知中断处理程序已经接收到网络包。
  2. 网卡中断处理程序为网络帧分配内核数据结构sk_buff,并将其拷贝到sk_buff缓冲区,然后通过软中断,通知内核收到了新的网络帧。
  3. 内核协议栈从缓冲区中取出网络帧,并通过网络协议栈从下往上逐层处理这个网络帧。
    内核协议栈处理网络帧的过程如下:
  • 在数据链路层检查报文的合法性,检查上层协议类型(IPV4/6),去掉数据链路层帧头、帧尾,交给网络层
  • 网络层取出IP头,判断网络包的下一步去向,比如交由上层处理或者转发。如果是确认该网络包交由本机则取出上层协议类型(TCP/UDP),去掉IP头,交由传输层。
  • 传输层取出TCP头或者UDP头后,根据<源ip、源端口、目的ip、目的端口>四元组作为标识,找到对应的socket,并将数据拷贝到socket的接收缓冲区中。
    应用程序就可以使用socket接口读取到最新的数据了。
相关缓冲区
  1. 网卡收发网络包是,通过DMA方式交互的环形缓冲区: 涉及DMA和网卡交互,属于网卡设备驱动范围。
  2. 网卡中断处理程序为网络帧分配的内核数据结构sk_buff缓冲区。
  3. 应用程序通过网络套接字与网络协议栈交互的套接字缓冲区。
    这些缓冲区都处于内核管理的内存中。
网络包的发送流程
  1. 应用程序调用socket api发送网络包
  2. 套接字层会把数据包放到socket发送缓冲区中
  3. 网络协议栈从socket发送缓冲区中取出数据包,按照TCP/IP协议栈,从上到下逐层处理。
  4. 传输层:增加TCP/IP头; 网络层:执行路由查找并确认下一跳的IP,并且按照MTU大小进行分片;网络接口层进行物理寻址,找到下一跳的MAC地址,然后添加帧头和帧尾,放到发包队列中
  5. 软中断通知驱动程序告知有新的网络帧需要发送
  6. 驱动程序通过DMA从发包队列中读取网络帧,并通过物理网卡将其发送出去。

名词有点多,一个一个来看:

  • DMA: Direct Memory Access,译作直接存储器访问,将数据从一个地址空间复制到另一个地址空间,提供外设和存储器之间或者存储器与存储器之间的高速素具传递
  • 硬中断:
  • 软中断:软中断处理,有专门的内核线程 ksoftirqd。每个 CPU 都会绑定一个 ksoftirqd内核线程。
  • sk_buff:一个维护网络帧结构的双向链表,链表中的每一个元素都是一个网络帧Packet

多路复用

水平触发与边缘触发

水平触发(level-trggered):
边缘触发(edge-triggered):

网络性能指标

  1. 带宽: 链路的最大传输速率,单位为b/s。带宽和物理网卡的配置直接关联,实际带宽取决于整个网络链路中最小的一个。
  2. 时延: 网络请求发出后,一直到收到远端响应所需要的事件延迟。在不同场景下含义有所不同,比如连接建立的时间(TCP握手延时)或者一个数据包往返所需时间(rtt)
  3. 吞吐量:标识没有丢包时的最大数据传输速率,单位为b/s。吞吐量受带宽限制。
  4. PPS: 即packet per second的缩写,标识以网络包为单位的传输速率,通常用来评估网络的转发能力。在以linux服务器转发时,很容易受到网络包大小的影响。

网络性能测试

linux网络基于TCP/IP协议栈,而不同协议层的行为不同。在评估网络性能前,需要确定应用程序基于协议栈的哪一层:

  1. 基于http或者https的web应用程序,属于应用层,需要测试http/https的性能。
  2. 对于游戏服务器,为了支持更多在线人数,通常基于tcp/udp与客户端交互,此时需要测试tcp/udp的性能。
  3. 将linux作为软交换机或者路由器来使用,此时则更关注网络包的处理能力,特别是网络层的转发性能。

底层协议是其上各层网络协议的基础,底层的性能决定了高层的网络性能。

测试网络接口层和网络层的性能

网络接口层和网络层主要负责网络包的封装、寻址、路由、发送以及接收。这两层中每秒可处理的网络包PPS即是最重要的性能指标,特别是64B小包的处理能力。

Linux 内核自带的高性能网络测试工具 pktgen

tcp/udp性能测试

iperfnetperf 都是最常用的网络性能测试工具

http性能测试

要测试 HTTP 的性能,也有大量的工具可以使用,比如 ab、webbench 等,都是常用的 HTTP 压力测试工具。其中,ab 是 Apache 自带的 HTTP 压测工具,主要测试 HTTP 服务的每秒请求数、请求延迟、吞吐量以及请求延迟的分布情况等。

为了得到应用程序的实际性能,就要求性能工具本身可以模拟用户的请求负载,而 iperf、ab 这类工具就无能为力了。幸运的是,我们还可以用 wrk、TCPCopy、Jmeter 或者 LoadRunner 等实现这个目标。

网络性能优化

确定优化目标

参考TCP/IP分层协议簇,确定各层的性能预期。
除了上述描述的,针对传输层TCP/UDP,网络包的大小会影响吞吐量(BPS)、连接数、延迟等指标。

选用网络性能工具

根据指标选用工具
根据工具查询指标

网络性能优化

在获得网络基准测试报告后,通过相关性能工具,定位出网络性能瓶颈后,就可以进行优化了。
参考上述 LINUX系统下的网络协议栈和网络收发流程进行网络性能优化。
一般来说可以从: 应用程序,套接字、传输层、网络层和链路层等角度进行优化。

应用程序优化

应用程序通常是通过套接字接口进行网络操作。由于网络收发比较耗时,所以应用程序的优化,主要就是对网络IO和进程自身工作模型的优化。

  1. 网络IO优化:
    • 使用epoll替代select和poll
    • 使用异步io。 AIO允许应用程序同时发起很多IO操作,而不用等待操作完成,等到io完成后,系统会用事件通知的方式告知应用程序结果。不过AIO使用比较复杂,需要小心处理很多边缘情况。
  2. 进程模型优化:
    • 主进程+ 多个worker子进程。 由主进程负责管理网络连接,子进程负责处理实际业务。
    • 监听相同端口的多进程模型: 所有进程监听相同接口,并且开启SO_REUSEPORT选项,由内核负责,将请求负载均衡到这些监听进程中。

另外,应用层协议的优化也很重要,有如下常见的优化手段:

  1. 使用长连接取代短连接,可以显著降低TCP建立连接的成本。在每秒请求次数较多时,效果明显。
  2. 使用内存等形式缓存不常变化的数据,可以降低网络IO次数,加快应用程序响应速度。
  3. 使用protocol buffer等序列化方式,压缩网络io的数据量,提高应用程序的吞吐。
  4. 使用DNS预存、预取、HTTPDNS等方式,减少DNS解析的延迟。
套接字优化

套接字可以屏蔽掉linux内核中不同协议的差异,为应用程序提供统一的访问接口。每个套接字都有一个读写缓冲区。

  • 读缓冲区: 缓存远端发来的数据,如果满就不能再接收新的数据。
  • 写缓冲区:缓存要发出去的数据,如果满,应用程序的写操作就会被阻塞。
    故而,为了提高网络吞吐量,通常需要调整这些缓冲区大小:
  1. 增大每个套接字的缓冲区大小: net.core.optmem_max
  2. 增大套接字接收缓冲区大小: net.core.rmem_max
  3. 增大套接字发送缓冲区大小: net.core.wmem_max
  4. 增大TCP接收缓冲区大小: net.ipv4.tcp_rmem
  5. 增大TCP发送缓冲区大小: net.ipv4.tcp_wmem
    除此之外,套接字的内核选项如下:
传输层优化

实际上是指对两种协议的优化。

TCP协议优化

流量控制:
慢启动:
拥塞控制:
延迟确认:
状态流图:如下图

  1. 请求数比较大的场景下,大量处于TIME_WAIT状态的连接,会占用大量内存和端口资源,此时可以优化与TIME_WAIT转台相关的内核选项:
  • 增大处于TIME_WAIT状态的连接数量net.ipv4.tcp_max_tw_buckets,并增大连接跟踪表的大小net.netfilter.nf_conntrack_max
  • 减小net.ipv4.tcp_fin_timeoutnet.netfilter.nf_conntrack_tcp_timeout_time_wait,让系统尽快释放所占用的资源。
  • 开启端口复用net.ipv4.tcp_tw_reuse,这样被TIME_WAIT状态占用的端口,还能用到新建的连接中。
  • 增大本地端口范围net.ipv4.ip_local_port_range,这样可以支持更多的连接,提高整体的并发能力。
  • 增大文件描述符的数量:使用fs.nr_openfs.file_max分别增大进程和系统的最大文件描述符;或者在应用程序的systemd配置文件中,配置limitnofile设置应用程序的最大文件描述符。
  1. 缓解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.

  1. 在长连接的场景中,通常使用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协议优化
  1. 增大套接字缓冲区大小和UDP缓冲区范围;
  2. 增大本地端口号范围;
  3. 根据MTU大小,调整UDP数据包的大小,减少或者避免分片。
网络层优化

网络层负责网络包的封装、寻址和路由,包括IP、ICMP协议。在网络层最主要的优化是对路由、IP分片以及ICMP等进行调优。

  1. 从路由和转发的角度,可以调整如下内核选项:
  • 在需要转发的服务器中,比如用作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问题。
  1. 从分片的角度,调整MTU大小
    以太网标准规定,一个网络帧最大为 1518B,那么去掉以太网头部的 18B 后,剩余的 1500 就是以太网 MTU 的大小。在使用VXLAN、GRE等叠加网络技术后,会使原来的网络包变大,使得MTU也需要调整。
    以VXLAN为例,它在原来报文的基础上,增加了 14B 的以太网头部、 8B 的 VXLAN 头部、8B 的 UDP 头部以及 20B 的 IP 头部。换句话说,每个包比原来增大了 50B。
    所以就需要交换机、路由器等的MTU增大到1550,或者把VXLAN封包前的MTU减小为1450。
    当然,考虑到许多网络设备都支持巨帧,这种情况下,可以将MTU调大为9000,以提高网络吞吐量。
  2. 以ICMP角度出发,为了避免ICMP主机探测、ICMP flood等各种网络问题,可以通过内核选项,限制ICMP行为。
  • 设置net.ipv4.icmp_echo_ignore_all=1禁用ICMP协议,外部主机就无法通过ping等使用ICMP协议的命令来探测主机。
  • 设置net.ipv4.icmp_echo_ignore_broadcasts=1禁止广播ICMP.
链路层优化

链路层负责网络包在物理网络中的传输:MAC寻址、错误探测、通过网卡传输网络帧。

  1. 由于网卡收包之后调用的中断处理程序(特别是软中断),需要消耗大量的CPU,所以将这些中断处理程序调度到不同的CPU上执行,就可以显著提高网络吞吐量。
  • 为网卡硬中断配置CPU亲和性(smp_affinity),或者开启irqbalance
  • 开启RPS (receive packet steering)RFS (receive flow steering)将应用程序和软中断的处理,调度到相同的CPU上。这样就可以增加CPU命中率,减少网络延时。
  1. 原来内核中通过软件处理的功能可以卸载到网卡中,通过硬件执行:
  • 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 的组包功能。
  1. 网络接口本身
  • 开启网络接口的多队列功能,每个队列可以用不同的中断号,调度到不同的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

  1. 最大连接数是不是受限于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原理介绍