第一章 应用层优化:能不发就不发,能少发就少发
1.1 减少不必要的网络 IO
网络 IO 是系统中最昂贵的操作之一。即使是本机 Loopback 通信,开销也远超想象:
┌─────────────────────────────────────────────────────────────┐
│ 网络 IO 的真实成本 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 单次网络请求的开销分解: │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. 用户态 → 内核态 上下文切换 │ │
│ │ 2. 内核协议栈处理(TCP/IP 各层头部封装) │ │
│ │ 3. 软中断处理(ksoftirqd 介入) │ │
│ │ 4. 进程唤醒(睡眠 → 可运行状态切换) │ │
│ │ 5. 数据返回时再走一遍上述流程 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 典型案例: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 某项目 Java 服务调用本机 C/C++ SDK │ │
│ │ 原方案:TCP/UDP 网络调用 │ │
│ │ 优化后:直接函数调用 │ │
│ │ 结果:CPU 核数削减 20% │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 结论:单机内部模块通信,优先使用: │
│ - 共享内存 │
│ - 本地函数调用 │
│ - 进程间通信(IPC) │
│ 避免不必要的网络 Socket 通信 │
│ │
└─────────────────────────────────────────────────────────────┘
1.2 合并网络请求:减少 RTT
往返时间(RTT)是网络延迟的主要来源。减少请求次数是提升性能的关键:
┌─────────────────────────────────────────────────────────────┐
│ 请求合并的威力 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 场景:批量获取 Redis 中的 60 个 key │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 错误做法:循环 60 次调用 │ │
│ │ │ │
│ │ for (int i = 0; i < 60; i++) { │ │
│ │ redis->get(key[i]); // 每次一次网络往返 │ │
│ │ } │ │
│ │ │ │
│ │ 总耗时 = 60 × RTT + 处理时间 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 正确做法:批量命令或 Pipeline │ │
│ │ │ │
│ │ redis->hmget(keys); // 一次网络往返 │ │
│ │ 或 │ │
│ │ redis.pipeline() │ │
│ │ .get(key[0]) │ │
│ │ .get(key[1]) │ │
│ │ ... │ │
│ │ .execute() │ │
│ │ │ │
│ │ 总耗时 = 1 × RTT + 处理时间 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 性能提升:60 倍的 RTT 节省! │
│ │
└─────────────────────────────────────────────────────────────┘
1.3 就近部署:物理距离决定延迟
┌─────────────────────────────────────────────────────────────┐
│ 延迟与物理距离的关系 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 光速延迟参考: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 路由节点 典型延迟 │ │
│ │ ──────────────────────────────────────────────── │ │
│ │ 同机房(<1km) 0.5-1 ms │ │
│ │ 同城(100km) 2-5 ms │ │
│ │ 同区域(1000km) 10-20 ms │ │
│ │ 跨区域(5000km) 50-100 ms │ │
│ │ 跨国/洲际 100-300 ms │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 优化策略: │
│ - 客户端与服务端尽量部署在同一机房 │
│ - 核心服务优先考虑同机架部署 │
│ - CDN 加速:静态资源分发到用户就近节点 │
│ │
└─────────────────────────────────────────────────────────────┘
1.4 内网调用避免使用外网域名
┌─────────────────────────────────────────────────────────────┐
│ 内网调用外网域名的代价 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 错误场景:服务 A(内网)调用服务 B(内网),却用公网域名 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 服务 A │ │
│ │ │ │ │
│ │ │ www.xxx.com (公网域名) │ │
│ │ ▼ │ │
│ │ DNS 解析 ───────────────────────────────→ │ │
│ │ │ │ │
│ │ NAT 网关(公网出口) │ │ │
│ │ │ │ │ │
│ │ │ Hairpin NAT(NAT 回流) │ │ │
│ │ ▼ │ │
│ │ 服务 B │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 增加的开销: │
│ 1. DNS 解析延迟(通常 5-50ms) │
│ 2. NAT 网关路由跳数 │
│ 3. Hairpin NAT 处理(额外 CPU 消耗) │
│ │
│ 正确做法: │
│ - 使用内网 IP 直连(如 10.0.1.100:8080) │
│ - 使用内网私有域名(如 svc-b.internal.company.com) │
│ - 服务注册中心(Consul / etcd / Nacos)服务发现 │
│ │
└─────────────────────────────────────────────────────────────┘
第二章 发送过程优化:减少拷贝与延迟
2.1 传统方式的四次拷贝
在没有优化的情况下,发送一个文件需要经过复杂的拷贝过程:
┌─────────────────────────────────────────────────────────────┐
│ 传统 read + write 方式 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 步骤 1 步骤 2 步骤 3 步骤 4 │
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
│ │硬盘│ ───→ │内核│ ───→ │用户│ ───→ │Socket│ ──→ 网卡 │
│ └────┘ DMA └────┘ CPU └────┘ CPU └────┘ DMA │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 全程 4 次上下文切换: │ │
│ │ 1. read() 调用:用户态 → 内核态 │ │
│ │ 2. read() 返回:内核态 → 用户态 │ │
│ │ 3. write() 调用:用户态 → 内核态 │ │
│ │ 4. write() 返回:内核态 → 用户态 │ │
│ │ │ │
│ │ 全程 2 次 CPU 拷贝(不必要): │ │
│ │ - 内核缓冲区 → 用户缓冲区 │ │
│ │ - 用户缓冲区 → Socket 缓冲区 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
2.2 mmap + write:减少一次拷贝
┌─────────────────────────────────────────────────────────────┐
│ mmap + write 优化 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ mmap() 系统调用: │ │
│ │ - 将内核缓冲区映射到用户虚拟地址空间 │ │
│ │ - 用户态和内核态共享同一块物理内存 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 优化后的流程: │ │
│ │ │ │
│ │ 硬盘 ──DMA──→ 内核缓冲区(用户已映射) │ │
│ │ │ │ │
│ │ │ 共享内存(无需拷贝) │ │
│ │ ▼ │ │
│ │ Socket 缓冲区 ──DMA──→ 网卡 │ │
│ │ │ │
│ │ CPU 拷贝次数:1 次(从内核到 Socket) │ │
│ │ 上下文切换:2 次(write 调用 + 返回) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
2.3 sendfile:真正的零拷贝
┌─────────────────────────────────────────────────────────────┐
│ sendfile 零拷贝技术 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ sendfile() 的革命性设计: │ │
│ │ │ │
│ │ - 数据完全不经过用户态 │ │
│ │ - 只在两个文件描述符之间传输 │ │
│ │ - 配合网卡 SG-DMA,可实现真正零拷贝 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 优化后的流程: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 硬盘 ──DMA──→ 内核缓冲区 │ │
│ │ │ │ │
│ │ │ SG-DMA 直接传输 │ │
│ │ ▼ │ │
│ │ 网卡(直接读取) │ │
│ │ │ │
│ │ CPU 拷贝:0 次 │ │
│ │ 上下文切换:2 次(sendfile 调用 + 返回) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 适用场景: │ │
│ │ - 文件服务器(Nginx 静态文件服务) │ │
│ │ - CDN 服务 │ │
│ │ - 日志收集系统 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
2.4 TSO/GSO:推迟分片,减少 CPU 消耗
┌─────────────────────────────────────────────────────────────┐
│ 分片卸载技术 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 问题背景: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ MTU 通常为 1500 字节 │ │
│ │ 应用层发送 10KB 数据,需要协议栈分片成 7 个小包 │ │
│ │ 每个包都要: │ │
│ │ - 计算校验和 │ │
│ │ - 添加 TCP/IP 头部 │ │
│ │ - 计算分片偏移 │ │
│ │ CPU 开销巨大 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ GSO(Generic Segmentation Offload): │ │
│ │ │ │
│ │ - 软件层面优化 │ │
│ │ - 将分片推迟到协议栈最后一刻 │ │
│ │ - 减少中间处理开销 │ │
│ │ - 内核 2.6+ 版本支持 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ TSO(TCP Segmentation Offload): │ │
│ │ │ │
│ │ - 硬件层面最强优化 │ │
│ │ - 大数据包直接扔给网卡 │ │
│ │ - 网卡芯片完成分片、校验和计算 │ │
│ │ - CPU 完全不参与分片 │ │
│ │ - 需要网卡硬件支持 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 查看和配置: │
│ $ ethtool -k eth0 # 查看当前状态 │
│ $ ethtool -K eth0 tso on # 开启 TSO │
│ │
└─────────────────────────────────────────────────────────────┘
2.5 XPS:多队列绑定的艺术
┌─────────────────────────────────────────────────────────────┐
│ XPS 发送数据包转向 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 问题:多队列网卡如果没有正确绑定,会导致负载不均 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 无 XPS 优化: │ │
│ │ │ │
│ │ ┌─────────────────┐ │ │
│ │ │ CPU 0 │ ← 所有发送都来这里 │ │
│ │ └─────────────────┘ │ │
│ │ │ │ │
│ │ ┌──────┴──────┐ │ │
│ │ ▼ ▼ │ │
│ │ ┌────────┐ ┌────────┐ │ │
│ │ │ 队列 0 │ │ 队列 1 │ ← 竞争同一把锁 │ │
│ │ └────────┘ └────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ XPS 优化后: │ │
│ │ │ │
│ │ CPU 0 ─────────────────→ 队列 0 │ │
│ │ CPU 1 ─────────────────→ 队列 1 │ │
│ │ CPU 2 ─────────────────→ 队列 2 │ │
│ │ ... │ │
│ │ │ │
│ │ 利用 CPU 缓存局部性,减少锁竞争 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 配置示例: │
│ # 设置 CPU 1,3 绑定到队列 1 │
│ echo 10 > /sys/class/net/eth0/queues/tx-1/xps_cpus │
│ # (10 的二进制 1010 表示 CPU 1 和 CPU 3) │
│ │
└─────────────────────────────────────────────────────────────┘
第三章 接收过程优化:让网卡和内核配合得更好
3.1 RingBuffer 大小调整
┌─────────────────────────────────────────────────────────────┐
│ RingBuffer 溢出与丢包 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 工作原理: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 网卡 ──DMA──→ [ RingBuffer ] ──→ 内核处理 │ │
│ │ │ │ │
│ │ │ 循环使用 │ │
│ │ └──────────────┘ │ │
│ │ │ │
│ │ 如果内核处理速度 < 网卡接收速度 │ │
│ │ → RingBuffer 满 │ │
│ │ → 新数据包被丢弃(丢包) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 诊断方法: │
│ $ ifconfig eth0 │
│ rx_fifo_errors: 1234 # FIFO 溢出丢包 │
│ rx_dropped: 567 # 其他原因丢包 │
│ │
│ 或使用: │
│ $ ethtool -S eth0 | grep -E "rx_dropped|rx_fifo" │
│ │
│ 优化命令: │
│ # 查看当前 RingBuffer 大小 │
│ $ ethtool -g eth0 │
│ │
│ # 调整接收 RingBuffer 大小 │
│ $ ethtool -G eth0 rx 4096 tx 4096 │
│ │
└─────────────────────────────────────────────────────────────┘
3.2 RSS 多队列并行处理
┌─────────────────────────────────────────────────────────────┐
│ RSS 接收侧缩放 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 问题:单队列网卡的瓶颈 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 网卡(单队列) │ │
│ │ │ │ │
│ │ │ 所有包都走这里 │ │
│ │ ▼ │ │
│ │ ┌──────────┐ │ │
│ │ │ CPU 0 │ ← 软中断都在这一个核处理 │ │
│ │ └──────────┘ │ │
│ │ │ │
│ │ 其他 CPU 核心闲置 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 解决方案:RSS 多队列 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 网卡(多队列) │ │
│ │ │ │ │
│ │ ┌─────────┼─────────┐ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌────┐ ┌────┐ ┌────┐ │ │
│ │ │队列│ │队列│ │队列│ │ │
│ │ └──┬─┘ └──┬─┘ └──┬─┘ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌────┐ ┌────┐ ┌────┐ │ │
│ │ │CPU0│ │CPU1│ │CPU2│ ← 每个 CPU 处理独立队列 │ │
│ │ └────┘ └────┘ └────┘ │ │
│ │ │ │
│ │ 并行处理,吞吐量线性提升 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 配置命令: │
│ $ ethtool -l eth0 # 查看队列数 │
│ $ ethtool -L eth0 combined 4 # 设置为 4 个合并队列 │
│ │
│ 绑定中断到指定 CPU: │
│ # 查看中断亲和性 │
│ $ cat /proc/interrupts | grep eth0 │
│ │
│ # 设置中断亲和性(将 eth0-TxRx-0 绑定到 CPU 0) │
│ $ echo 1 > /proc/irq/XXX/smp_affinity │
│ │
└─────────────────────────────────────────────────────────────┘
3.3 硬中断合并
┌─────────────────────────────────────────────────────────────┐
│ 中断合并机制 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 问题:每个包都触发中断,开销巨大 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 包1 ──IRQ──→ CPU 处理 ──返回── │ │
│ │ 包2 ──IRQ──→ CPU 处理 ──返回── │ │
│ │ 包3 ──IRQ──→ CPU 处理 ──返回── │ │
│ │ ... │ │
│ │ │ │
│ │ 每个中断:保存上下文 → 处理 → 恢复上下文 ≈ 5-10μs │ │
│ │ 1Gbps 线路 = 约 8 万个包/秒 = 大量 CPU 浪费 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 解决方案:中断合并 │ │
│ │ │ │
│ │ 积累多个包后一次性触发中断: │ │
│ │ ┌────┬────┬────┬────┐ │ │
│ │ │包1 │包2 │包3 │包4 │ ──→ 一次性 IRQ │ │
│ │ └────┴────┴────┴────┘ │ │
│ │ │ │
│ │ Adaptive RX/TX: │ │
│ │ - 流量大时:延迟合并,降低 CPU 消耗 │ │
│ │ - 流量小时:立即响应,保证低延迟 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 查看和配置: │
│ $ ethtool -c eth0 # 查看中断合并参数 │
│ $ ethtool -C eth0 rx-frames 32 # 积累 32 个包触发中断 │
│ │
└─────────────────────────────────────────────────────────────┘
3.4 NAPI 软中断调优
┌─────────────────────────────────────────────────────────────┐
│ 软中断 budget 调优 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 问题:软中断处理太慢导致系统假死 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ksoftirqd 处理 NET_RX_SOFTIRQ: │ │
│ │ │ │
│ │ net_rx_action() { │ │
│ │ while (budget > 0) { │ │
│ │ process_packets(); // 处理包 │ │
│ │ budget--; │ │
│ │ } │ │
│ │ } │ │
│ │ │ │
│ │ 如果包太多 → ksoftirqd 占用 CPU 100% → 其他进程卡 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 调优参数: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ net.core.netdev_budget │ │
│ │ - 含义:每次软中断最多处理的包数 │ │
│ │ - 默认值:300 │ │
│ │ - 调大:每次处理更多包,减少软中断频率 │ │
│ │ - 调小:减少单次处理时间,更快响应其他任务 │ │
│ │ │ │
│ │ net.core.netdev_budget_usecs │ │
│ │ - 含义:每次软中断允许运行的时间(微秒) │ │
│ │ - 默认值:0(不使用) │ │
│ │ - 设置后:基于时间而非包数限制 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 查看和配置: │
│ $ sysctl net.core.netdev_budget │
│ $ sysctl -w net.core.netdev_budget=600 │
│ │
└─────────────────────────────────────────────────────────────┘
3.5 LRO/GRO 接收端合并
┌─────────────────────────────────────────────────────────────┐
│ GRO 通用接收卸载 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 发送端有 Nagle 算法合并小包,接收端也有类似机制: │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ LRO(Large Receive Offload): │ │
│ │ - 在网卡硬件层面合并 │ │
│ │ - 合并后的大包一次性传给内核 │ │
│ │ - 节省协议栈处理次数 │ │
│ │ - 但只适合相同流的包 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ GRO(Generic Receive Offload): │ │
│ │ │ │
│ │ - 在内核软件层面实现 │ │
│ │ - 兼容性更好,支持更多协议 │ │
│ │ - Linux 默认开启 │ │
│ │ - 可合并 TCP/UDP 等多种协议 │ │
│ │ │ │
│ │ 工作原理: │ │
│ │ ┌────────────────────────────────────────────┐ │ │
│ │ │ 多个小包 → GRO 合并 → 一个大包 → 协议栈 │ │ │
│ │ └────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 效果:协议栈处理包数量大幅减少 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 查看和配置: │
│ $ ethtool -k eth0 | grep gro │
│ gro_flush_timeout: 500 │
│ gro: on │
│ │
│ $ ethtool -K eth0 gro on # 开启 GRO │
│ $ ethtool -K eth0 gro off # 关闭 GRO │
│ │
└─────────────────────────────────────────────────────────────┘
第四章 内核与进程协作优化
4.1 阻塞 IO vs IO 多路复用
┌─────────────────────────────────────────────────────────────┐
│ 阻塞模式的代价 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 传统 recvfrom 的问题: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 进程调用 recvfrom() │ │
│ │ │ │ │
│ │ ├── 数据没到?→ 进程睡眠(让出 CPU) │ │
│ │ │ │ │
│ │ │ 数据到达 → 唤醒进程 │ │
│ │ │ │ │
│ │ └── 从内核拷贝数据到用户空间 │ │
│ │ │ │
│ │ 每次睡眠-唤醒切换:约 3-5 微秒 │ │
│ │ 管理 1 万连接 = 大量无效切换 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ epoll 的革命:事件驱动 │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ 单线程管理成千上万个连接: │ │ │
│ │ │ │ │ │
│ │ │ epoll_wait() ──→ 返回就绪事件列表 │ │ │
│ │ │ │ │ │ │
│ │ │ │ 只处理真正有数据的连接 │ │ │
│ │ │ ▼ │ │ │
│ │ │ 处理事件 │ │ │
│ │ │ │ │ │
│ │ │ 无数据时:线程休眠,不消耗 CPU │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
4.2 Kernel-Bypass:终极优化
┌─────────────────────────────────────────────────────────────┐
│ 内核旁路技术 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 传统内核协议栈的瓶颈: │ │
│ │ │ │
│ │ 应用 ──系统调用──→ 内核协议栈 ──驱动──→ 网卡 │ │
│ │ ↑ ↑ │ │
│ │ 上下文切换 数据拷贝 │ │
│ │ │ │
│ │ DPDK / Solarflare OpenOnload 的思路: │ │
│ │ "既然内核慢,那就绕过它!" │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Kernel-Bypass 架构: │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ │ │ │
│ │ │ 应用 ←──共享内存──→ 网卡(轮询) │ │ │
│ │ │ │ │ │
│ │ │ 完全绕过内核协议栈 │ │ │
│ │ │ 无系统调用 │ │ │
│ │ │ 无上下文切换 │ │ │
│ │ │ 微秒级甚至纳秒级延迟 │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 适用场景: │
│ - 金融高频交易系统 │
│ - 负载均衡器(如 DPDK 版 VPP) │
│ - 电信级网关 │
│ - 超低延迟数据中心 │
│ │
│ 代价: │
│ - 需要专用网卡支持 │
│ - 应用需要重新编写 │
│ - 失去了内核的流量控制、安全机制 │
│ │
└─────────────────────────────────────────────────────────────┘
第五章 TCP 握手与连接管理优化
5.1 端口范围与快速重用
┌─────────────────────────────────────────────────────────────┐
│ 端口耗尽问题 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 问题背景: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 客户端发起连接需要占用一个本地端口 │ │
│ │ │ │
│ │ 默认端口范围:约 28000-38000 ≈ 6.5 万个 │ │
│ │ │ │
│ │ 高并发爬虫/压测时轻易耗尽 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 优化方案: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. 扩大端口范围: │ │
│ │ │ │
│ │ $ sysctl -w net.ipv4.ip_local_port_range="1024 65535" │ │
│ │ │ │
│ │ 2. 快速复用 TIME_WAIT 端口: │ │
│ │ │ │
│ │ $ sysctl -w net.ipv4.tcp_tw_reuse=1 │ │
│ │ $ sysctl -w net.ipv4.tcp_timestamps=1 │ │
│ │ │ │
│ │ 效果:TIME_WAIT 状态的端口可被新连接快速复用 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
5.2 连接队列溢出
┌─────────────────────────────────────────────────────────────┐
│ 连接队列与 backlog │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ TCP 连接建立流程: │ │
│ │ │ │
│ │ 客户端 ──SYN──→ 服务器(进入半连接队列) │ │
│ │ │ │ │
│ │ │ 服务器响应 SYN+ACK │ │
│ │ │ │ │
│ │ ──ACK──→ 客户端 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 进入全连接队列(accept queue) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 应用调用 accept() 取走 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 溢出场景: │ │
│ │ │ │
│ │ 如果应用处理太慢,accept() 不及时调用: │ │
│ │ - 全连接队列满 │ │
│ │ - 新连接无法进入 │ │
│ │ - 客户端 SYN 超时重传 │ │
│ │ - 连接建立失败 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 调优参数: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 内核限制:net.core.somaxconn │ │
│ │ 应用限制:listen(fd, backlog) 参数 │ │
│ │ │ │
│ │ 实际队列长度 = min(somaxconn, backlog) │ │
│ │ │ │
│ │ 调优建议: │ │
│ │ $ sysctl -w net.core.somaxconn=65535 │ │
│ │ │ │
│ │ Nginx 配置: │ │
│ │ listen 80 backlog 65535; │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
5.3 减少握手重试
┌─────────────────────────────────────────────────────────────┐
│ SYN 重试次数调优 │
├─────────────────────────────────────────────────────────────┤
│ │
│ TCP 三次握手失败时的重试机制: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ SYN ──→ 无响应 ──→ 重试(1秒后) │ │
│ │ │ │ │
│ │ │ 无响应 ──→ 重试(2秒后) │ │
│ │ │ │ │
│ │ │ 无响应 ──→ 重试(4秒后) │ │
│ │ │ ... │ │
│ │ │ │ │
│ │ │ 总耗时可达数十秒! │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 优化策略: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ $ sysctl -w net.ipv4.tcp_syn_retries=3 │ │
│ │ │ │
│ │ 参数含义:SYN 最大重试次数 │ │
│ │ │ │
│ │ 推荐值: │ │
│ │ - 互联网服务:2-3(总超时约 3-6 秒) │ │
│ │ - 内网服务:1(总超时约 1.5 秒) │ │
│ │ - 高可用要求:保持默认 │ │
│ │ │ │
│ │ 原则:3 秒连不上,通常重试也没用,快速失败更好 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
5.4 TIME_WAIT 状态管理
┌─────────────────────────────────────────────────────────────┐
│ TIME_WAIT 状态优化 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 问题背景: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ TCP 断开连接时,主动关闭方进入 TIME_WAIT │ │
│ │ │ │
│ │ 持续时间 = 2MSL(通常约 60 秒) │ │
│ │ │ │
│ │ 短连接频繁时: │ │
│ │ - 大量连接处于 TIME_WAIT │ │
│ │ - 消耗端口资源 │ │
│ │ - 消耗内存 │ │
│ │ - 可能导致端口耗尽 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 解决方案: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. 长连接(最佳方案): │ │
│ │ - 复用连接,避免频繁断开 │ │
│ │ - HTTP Keep-Alive │ │
│ │ - 连接池 │ │
│ │ │ │
│ │ 2. 调整参数: │ │
│ │ │ │
│ │ # 限制 TIME_WAIT 最大数量 │ │
│ │ $ sysctl -w net.ipv4.tcp_max_tw_buckets=262144 │ │
│ │ │ │
│ │ # 允许复用 TIME_WAIT 端口 │ │
│ │ $ sysctl -w net.ipv4.tcp_tw_reuse=1 │ │
│ │ │ │
│ │ 3. 调整 MSL: │ │
│ │ $ sysctl -w net.ipv4.tcp_fin_timeout=30 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
5.5 TCP Fast Open (TFO)
┌─────────────────────────────────────────────────────────────┐
│ TCP 快速打开 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 传统 TCP 三次握手: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 客户端 ──── SYN ────→ 服务器 │ │
│ │ │ │ │
│ │ │ (等待 1 RTT) │ │
│ │ │ │ │
│ │ ←── SYN+ACK ── │ │
│ │ │ │ │
│ │ │ (等待 1 RTT) │ │
│ │ │ │ │
│ │ ────── ACK ─────→ │ │
│ │ │ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 数据传输 │ │
│ │ │ │
│ │ 总耗时:2 RTT + 数据传输 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ TFO 第一次握手就带数据: │ │
│ │ │ │
│ │ 客户端 ── SYN + 数据 ─→ 服务器(直接使用数据) │ │
│ │ │ │ │
│ │ │ (节省 1 RTT) │ │
│ │ │ │ │
│ │ ←── SYN+ACK + 响应 ── │ │
│ │ │ │
│ │ 总耗时:1 RTT + 数据传输 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 开启 TFO: │
│ $ sysctl -w net.ipv4.tcp_fastopen=3 │
│ # 3 = 客户端 + 服务端都开启 │
│ │
│ 前提条件: │
│ - 客户端首次连接时仍需 2 RTT 建立"Cookie" │
│ - 之后访问同一服务器可直接使用 TFO │
│ - 适用于频繁访问同一服务器的场景(如网页加载) │
│ │
└─────────────────────────────────────────────────────────────┘
第六章 优化参数速查表
┌─────────────────────────────────────────────────────────────┐
│ 网络优化参数速查表 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 分类 参数 推荐值 │ │
│ │ ──────────────────────────────────────────────── │ │
│ │ 端口范围 ip_local_port_range 1024 65535 │ │
│ │ TIME_WAIT tcp_max_tw_buckets 262144 │ │
│ │ TIME_WAIT tcp_tw_reuse 1 │ │
│ │ FIN超时 tcp_fin_timeout 30 │ │
│ │ 连接队列 somaxconn 65535 │ │
│ │ SYN重试 tcp_syn_retries 3 │ │
│ │ 软中断预算 netdev_budget 600 │ │
│ │ 文件描述符 nofile 1000000+ │ │
│ │ 内存 rmem_max/wmem_max 调整到较大 │ │
│ │ TFO tcp_fastopen 3 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
第七章 优化方向总结
┌─────────────────────────────────────────────────────────────┐
│ 性能优化全景图 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 应用层优化 │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ • 减少不必要的网络 IO │ │ │
│ │ │ • 合并请求(Pipeline/MGET) │ │ │
│ │ │ • 就近部署(同一机房) │ │ │
│ │ │ • 内网使用内网域名 │ │ │
│ │ │ • epoll 替代阻塞 IO │ │ │
│ │ │ • 使用成熟网络框架(Netty/Swoole) │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 发送过程优化 │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ • sendfile 零拷贝 │ │ │
│ │ │ • TSO/GSO 分片卸载 │ │ │
│ │ │ • XPS 多队列绑定 │ │ │
│ │ │ • Nagle 算法配置 │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 接收过程优化 │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ • RingBuffer 大小调整 │ │ │
│ │ │ • RSS 多队列与 CPU 绑定 │ │ │
│ │ │ • 中断合并(Adaptive RX) │ │ │
│ │ │ • NAPI budget 调优 │ │ │
│ │ │ • GRO/LRO 合并 │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 连接管理优化 │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ • 扩大端口范围 │ │ │
│ │ │ • TIME_WAIT 复用 │ │ │
│ │ │ • somaxconn 调大 │ │ │
│ │ │ • TCP Fast Open │ │ │
│ │ │ • 减少 SYN 重试次数 │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 终极优化 │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ • DPDK / Kernel-Bypass │ │ │
│ │ │ (超低延迟场景专用) │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘