网络知识总结

processon

processon

基于网络层,结合 Linux 系统的网络协议栈和网络收发流程。分析和定位网络瓶颈。定位出网络性能瓶颈后,根据瓶颈所在的协议层,进行优化。从应用程序、套接字、传输层、网络层再到链路层等,对每个层次进行逐层优化。

无法使用协议优化的时候,可以考虑,使用 DPDK 等用户态方式,绕过内核协议栈;或者,使用 XDP,在网络包进入内核协议栈前进行处理。

1625673798220-d3b1e4f6-c915-41d6-8a40-da94ae0dbe24.png

性能指标和工具

1625673750480-62a5a78d-c44f-41bd-9d43-034f19eb2ded.png

1625673764936-b5b2d59b-c841-44e3-81e1-20f3dee9c866.png

各层性能优化

获得网络基准测试报告,然后通过相关性能工具,定位出网络性能瓶颈。再 接下来的优化工作

应用程序--主要对网络 I/O 和进程自身工作模型的优化。

  • 网络 I/O 的角度

    • I/O 多路复用技术 epoll,主要用来取代 select 和 poll。是解决 C10K 问题的关键
    • 使用异步 I/O(Asynchronous I/O,AIO)。AIO 允许应用程序同时发起很多 I/O 操作,而不用等待这些操作完成。等到 I/O 完成后,系统会用事件通知的方式,告诉应 用程序结果。不过,AIO 的使用比较复杂,你需要小心处理很多边缘情况。
  • 进程的工作模型角度

    • 第一种,主进程 + 多个 worker 子进程。其中,主进程负责管理网络连接,而子进程负责 实际的业务处理。这也是最常用的一种模型。
    • 第二种,监听到相同端口的多进程模型。在这种模型下,所有进程都会监听相同接口,并且 开启 SO_REUSEPORT 选项,由内核负责,把请求负载均衡到这些监听进程中去。
  • 应用层的网络协议优化:

    • 使用长连接取代短连接,可以显著降低 TCP 建立连接的成本。在每秒请求次数较多时, 这样做的效果非常明显。
    • 使用内存等方式,来缓存不常变化的数据,可以降低网络 I/O 次数,同时加快应用程序 的响应速度。
    • 使用 Protocol Buffer 等序列化的方式,压缩网络 I/O 的数据量,可以提高应用程序的吞 吐。
    • 使用 DNS 缓存、预取、HTTPDNS 等方式,减少 DNS 解析的延迟,也可以提升网络 I/O 的整体速度。

套接字--优化套接字的缓冲区大小

屏蔽掉 Linux 内核中不同协议的差异,为应用程序提供统一的访问接口。

每个套接字,都有一个读写缓冲区。读缓冲区,缓存了远端发过来的数据。如果读缓冲区已满,就不能再接收新的数据。写缓冲区,缓存了要发出去的数据。如果写缓冲区已满,应用程序的写操作就会被阻塞。

为了提高网络的吞吐量,增大每个套接字的缓冲区大小

1625674208771-dc9126ec-282d-4e8a-8af9-f82d5fdd5496.png

  • tcp_rmem 和 tcp_wmem 的三个数值分别是 min,default,max,系统会根据这些设 置,自动调整 TCP 接收发送缓冲区的大小。
  • udp_mem 的三个数值分别是 min,pressure,max,系统会根据这些设置,自动调整 UDP 发送缓冲区的大小。

发送缓冲区大小,理想数值是吞吐量 * 延迟,这样才可以达到最大网络利用 率。

套接字接口还提供了一些配置选项,用来修改网络连接的行为: 为 TCP 连接设置 TCP_NODELAY 后,就可以禁用 Nagle 算法;为 TCP 连接开启 TCP_CORK 后,可以让小包聚合成大包后再发送(注意会阻塞小包的 发送);使用 SO_SNDBUF 和 SO_RCVBUF ,可以分别调整套接字发送缓冲区和接收缓冲区的大 小。

传输层--主要是优化 TCP 和 UDP 协议

TCP 协议的优化

临时修改使用sysctl,持久化写入文件/etc/sysctl.conf

  • 第一类,在请求数比较大的场景下,你可能会看到大量处于 TIME_WAIT 状态的连接,它们会占用大量内存和端口资源。这时,我们可以优化与 TIME_WAIT 状态相关的内核选 项
    • 增大处于 TIME_WAIT 状态的连接数量 net.ipv4.tcp_max_tw_buckets ,并NAT增大连接跟踪表的大小 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。

1625674738426-9c7645ad-de74-4450-bdc0-b15d90ee09ae.png

服务器端开启 Nagle 算法,而客户 端开启延迟确认机制,就很容易导致网络延迟增大。

在使用 NAT 的服务器上,如果开启 net.ipv4.tcp_tw_recycle ,就很容易导致各种 连接失败。

UDP协议的优化

UDP 提供了面向数据报的网络协议,它不需要网络连接,也不提供可靠性保障。

增大套接字缓冲区大小以及 UDP 缓冲区范围;

增大本地端口号的范围;

根据 MTU 大小,调整 UDP 数据包的大小,减少或者避免分片的发生。

网络层--主要是优化路由、转发、分片以及 ICMP 协议;

网络层,负责网络包的封装、寻址和路由,包括 IP、ICMP 等常见协议。在网络层,最主 要的优化,其实就是对路由、 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(Maximum Transmission Unit)的 大小。
    • 通常,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 的行为。
    • 禁止 ICMP 协议,即设置 net.ipv4.icmp_echo_ignore_all = 1。这样,外 部主机就无法通过 ICMP 来探测主机。
    • 禁止广播 ICMP,即设置 net.ipv4.icmp_echo_ignore_broadcasts = 1。

链路层--主要是优化网络包的收发、网络功能卸载以及网卡选项。

链路层负责网络包在物理网络中的传输,比如 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 包的分段(按照 MSS 分段)和 UDP 的分片 (按照 MTU 分片)功能,由网卡来完成 。
  • GSO(Generic Segmentation Offload):在网卡不支持 TSO/UFO 时,将 TCP/UDP 包的分段,延迟到进入网卡前再执行。这样,不仅可以减少 CPU 的消耗,还可以在发生 丢包时只重传分段后的包。
  • 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。

一种极限场景。C10M 问题,

在单机并发 1000 万的场景中,对 Linux 网络协议栈进行的各种优化策略,基本都没有太 大效果。因为这种情况下,网络协议栈的冗长流程,其实才是最主要的性能负担。

  • 第一种,使用 DPDK 技术,跳过内核协议栈,直接由用户态进程用轮询的方式,来处理网 络请求。同时,再结合大页、CPU 绑定、内存对齐、流水线并发等多种机制,优化网络包 的处理效率。
  • 使用内核自带的 XDP 技术,在网络包进入内核协议栈前,就对其进行处理,这样 也可以实现很好的性能。

最大连接数是不是受限于 65535 个端口

无论 TCP 还是 UDP,端口号都只占 16 位,也就说其最大值也只有 65535。那 是不是说,如果使用 TCP 协议,在单台机器、单个 IP 地址时,并发连接数最大也只有 65535 呢

Linux 协议栈,通过五元组来标志一个连接(即协议,源 IP、源端口、目的 IP、目的端口)。

对客户端来说,每次发起 TCP 连接请求时,都需要分配一个空闲的本地端口,去连接远端 的服务器。由于这个本地端口是独占的,所以客户端最多只能发起 65535 个连接。

对服务器端来说,其通常监听在固定端口上(比如 80 端口),等待客户端的连接。根据五 元组结构,我们知道,客户端的 IP 和端口都是可变的。如果不考虑 IP 地址分类以及资源限 制,服务器端的理论最大连接数,可以达到 2 的 48 次方(IP 为 32 位,端口号为 16 位),远大于 65535。

客户端最大支持 65535 个连接,而服务器端可支持的连接数是海量的。 当然,由于 Linux 协议栈本身的性能,以及各种物理和软件的资源限制等,这么大的连接 数,还是远远达不到的(实际上,C10M 就已经很难了)。

posted on 2025-10-12 21:34  chuchengzhi  阅读(19)  评论(0)    收藏  举报

导航

杭州技术博主,专注分享云计算领域实战经验、技术教程与行业洞察, 打造聚焦云计算技术的垂直博客,助力开发者快速掌握云服务核心能力。

褚成志 云计算 技术博客