第12章 性能调优
从瓶颈到优化落地
章节导读
前面十一章我们建立了完整的性能分析能力:方法论(USE、火焰图、静态与动态分析)、工具链(perf、eBPF、各子系统观测工具)、以及对 CPU、内存、磁盘、网络四大子系统的深入理解。本章是这条学习路径的自然终点——当你已经能够准确诊断出瓶颈之后,下一步就是消除它。性能调优(tuning)是性能工作中最容易被误解的环节:外行以为调优就是"改几个内核参数让系统变快",网上也充斥着各种"一键优化脚本"和"20 条 sysctl 让服务器性能翻倍"的帖子。Gregg 在本章开篇就泼了冷水:没有诊断的调优是盲目的试错,不仅大概率无效,还可能引入新的问题。真正的调优是一个严格的工程闭环:测量(找到瓶颈及其量化占比)→ 假设(这个瓶颈为什么存在、如何消除)→ 修改(实施最小化的针对性变更)→ 验证(用同样的测量确认改善、确认没有恶化其他指标)→ 固化或回滚。
调优方法论的第一块基石是 Amdahl 定律。假设一个系统某类操作占总耗时的比例为 p,你把这类操作优化到原来的 1/s 速度,那么整体加速比是 1 / ((1-p) + p/s)。这个公式的深刻含义是:整体收益的上限由可优化部分的占比决定。当你把占比 60% 的 CPU 热点优化掉一半,整体提升 23%;而当你费尽力气把占比 2% 的磁盘 I/O 优化到零,整体也只提升 2%。更极端的情形:当 p 趋近于 0 时,无论 s 多大,加速比都趋近于 1。这直接推出调优的第一纪律——永远优先优化占比最大的瓶颈,而不是你最熟悉、最容易下手的地方。工程师天然倾向于优化自己懂的组件(写 Java 的去调 JVM 参数,管网络的去调 sysctl),结果常常是"把 5% 的开销优化掉了 50%,整体提升 2.5%",还引入了配置复杂度。正确的顺序来自诊断数据:火焰图上最宽的塔、USE 检查中最饱和的资源、延迟分解中最大的分量——那才是你的 p 最大的地方。而且 Amdahl 定律是迭代的:当你消除了当前最大瓶颈,次大瓶颈的占比自动上升,成为新的优化目标。性能调优因此是一个"剥洋葱"的过程,每一轮都重新测量、重新排序。
调优的第二个关键认知是层次与成本收益。系统性能可以在多个层次上调整,从下到上大致是:硬件与固件层(选型、BIOS 设置、电源策略、网卡多队列)、操作系统内核层(调度器、内存管理、I/O 栈、网络栈的参数与算法选择)、运行时层(JVM 的 GC 与 JIT 参数、Go 的 GOGC、Python 的解释器选择)、应用代码层(算法、数据结构、并发模型、序列化方式)、架构层(缓存、异步化、分片、读写分离)。一个普遍规律是:越往下层,调优的杠杆越小、风险越高、可移植性越差;越往上层,潜在收益越大但改造成本越高。内核参数调优(sysctl)之所以在网络上流行,是因为它成本最低——改一行配置重启即生效。但正因为如此,它的典型收益也最小:对大多数负载,默认内核参数与"精心调优"的参数差距在 5% 以内;那些声称翻倍的文章,多数是拿病态的默认值(如十年前的 TCP 窗口)与特定极端负载(高带宽长肥管道)对比的结果。相反,一个缺失的数据库索引、一个 O(n²) 的循环、一个同步阻塞的调用链,修复后带来 10 倍甚至 100 倍的改善并不罕见。所以调优的优先级应该是:先修架构与代码的结构性问题,再调运行时配置,最后才是内核参数微调——与"一键优化脚本"的顺序恰好相反。
调优还有风险面。每一个参数修改都是一次行为变更,可能在你看不到的地方产生副作用:关闭透明大页(THP)降低了数据库的延迟毛刺,却可能让某些大内存扫描型负载变慢;增大 TCP 缓冲区提升了长肥管道吞吐,却加重了突发丢包时的缓冲膨胀(bufferbloat);把 I/O 调度器从 mq-deadline 换成 none 提升了 NVMe 的峰值 IOPS,却让混合负载下的延迟公平性恶化。因此生产环境的调优必须遵守变更纪律:一次只改一个变量、改动前记录基线、改动后用基准测试与生产指标双重验证、保留回滚路径。大规模集群的参数变更应该走灰度发布(先 1% 的机器,观察 24-48 小时再扩大),这与代码发布的纪律完全一致——调优参数就是配置代码。
云与容器时代重塑了调优的版图。传统物理机时代的很多调优手段在云上失效或变形:你无法修改 Hypervisor 层的调度,宿主机的 NUMA 拓扑对你部分可见甚至不可见,云盘的 I/O 调度器由平台管理(guest 内设 none 通常是正确选择,因为真正的排队发生在后端)。与此同时出现了新的调优维度:实例类型选择(计算优化/内存优化/存储优化的机型就是云时代的"硬件调优")、弹性伸缩策略(用横向扩容替代纵向调优往往更经济)、容器的 requests/limits 配置(CPU limit 引发的 throttling 是容器环境最常见的自伤型性能问题)、托管服务的规格档位(数据库实例、消息队列的吞吐配额)。云上调优的核心思维转变是:从"榨干单机"转向"选对规格 + 配置正确 + 弹性兜底",把深度内核调优的精力留给确实运行在裸金属或专属主机上的关键负载。
本章的余下部分按资源维度组织:CPU 调优(绑核与隔离、调度策略、编译优化)、内存调优(大页、NUMA、分配器、零拷贝)、I/O 调优(调度器、预读、异步 I/O、页缓存与脏页参数)、网络调优(内核参数、拥塞控制、中断亲和、DPDK/XDP),然后是应用层调优的通用模式(连接池、缓存、批处理、异步化),最后是调优的验证与回滚工程。每一节都遵循同一个模板:先说明该维度常见的性能问题形态(对应第 5-10 章的诊断知识),再给出针对性的调优手段、适用条件与验证方法,并明确标注"什么时候不该调"。请记住贯穿全章的原则:调优是诊断的延伸,不是诊断的替代品。
核心概念
- Amdahl 定律
- CPU 调优
- 内存调优
- I/O 调优
- 网络调优
要点精析
调优方法论的核心是"测量-优化-验证"闭环,而 Amdahl 定律是这个闭环的数学基础。完整流程分五步:(1)基线测量——在动手之前,用第 2-4 章的方法建立系统的性能画像:吞吐、延迟分布(P50/P99/P99.9)、各资源的 USE 数据、关键路径的火焰图与延迟分解。基线不仅是优化的参照系,更是"是否值得优化"的判断依据——如果 P99 已满足 SLO 且资源余量充足,最好的调优是不调优。(2)瓶颈定位与量化——找到最大瓶颈并量化其占比 p(火焰图上的宽度、iostat 的 %util 与 await、锁等待时间的占比),用 Amdahl 公式估算理论收益上限,收益低于最小有意义差异(如 5%)的目标直接放弃。(3)假设与最小变更——针对瓶颈机理提出修改方案,一次只改一个变量;改动要能解释"为什么这样改会消除这个瓶颈",说不清机理的改动就是试错。(4)验证——用与基线完全相同的负载与测量方法重测,对比吞吐、延迟分布与资源画像;不仅验证目标指标改善,还要检查其他指标没有恶化(内存换 CPU 的优化可能推高 OOM 风险,批量换延迟的优化可能拉长 P99)。(5)固化或回滚——验证通过则把变更写入配置管理(Ansible/Terraform,参数即代码),记录变更理由与验证数据;不通过则回滚并记录负面结果,避免后人重蹈覆辙。这个闭环中最常被跳过的是第 1 步和第 4 步——没有基线的调优无法证明有效,没有验证的调优无法防止恶化。
调优的优先级排序是一门成本收益的艺术,Gregg 给出的经验顺序与大多数人的直觉相反。按"预期收益/实施风险"比排序:第一位是修复结构性缺陷——缺失的索引、O(n²) 算法、N+1 查询、同步阻塞调用、无上限的缓存,这类问题的收益常达数倍到数十倍,且修复本身就是纠错,风险低;第二位是应用与运行时配置——连接池大小、线程池规模、JVM 堆与 GC 选择、批处理参数,收益可达数十个百分点,需要理解应用语义但改动局部;第三位是架构级优化——加缓存层、异步化、读写分离、分片,收益大但改造成本高、周期长,需要立项而非顺手改;第四位才是操作系统与内核参数——sysctl、调度器、大页、NUMA 绑定,典型收益 5% 以内(特定负载除外,如数据库的 THP 关闭、高带宽网络的缓冲区调整),但风险面覆盖整机所有负载;第五位是硬件与固件——BIOS 电源策略、turbo 设置、网卡固件,通常在新机上架时一次性做对。这个排序的实践含义:接到"系统慢,帮忙调优"的请求时,先跑 60 秒检查清单和火焰图,如果发现问题在应用层(绝大多数情况如此),就不要碰内核参数;只有当诊断明确指向内核行为(如调度延迟、页回收、TCP 重传模式)且应用层已无优化空间时,才进入系统级调优。反过来,"性能优化周"式的集中调参活动,如果没有诊断数据引路,产出通常是一堆无法验证的 sysctl 变更和一份自我感觉良好的报告。
CPU 调优的第一组手段是绑核、隔离与调度策略,适用于延迟敏感或缓存敏感的负载。绑核(CPU affinity)用 taskset 或 sched_setaffinity 把进程/线程固定在特定 CPU 上,收益来自缓存局部性(线程不再迁移,L1/L2 缓存与 TLB 保持热态)与 NUMA 局部性(配合内存绑定避免跨节点访问)。对多线程应用,绑核策略有两种哲学:每线程一核的静态绑定(数据库、DPDK、低延迟交易系统的做法,消除迁移与争用,但负载不均时无法借用空闲核)与不绑核交给调度器(通用服务的默认,CFS 的负载均衡通常做得不错)。隔离比绑定更进一步:内核启动参数 isolcpus 或 cgroups cpuset 把若干核从调度器手中拿走,专供关键线程使用,同时把中断亲和(/proc/irq/*/smp_affinity)与内核线程(如 kworker)挪离这些核,配合 nohz_full(消除时钟中断)与 rcu_nocbs( offload RCU 回调),可以把单核的外部干扰降到微秒级——这是超低延迟系统(高频交易、5G 用户面、实时音视频转发)的标准配置。调度策略方面,SCHED_FIFO/SCHED_RR 实时策略让线程抢占所有普通线程且不受 CFS 时间片轮转影响,用 chrt 设置;但实时优先级是双刃剑:优先级设错或线程忙等会饿死系统任务(内核有 RT throttling 兜底,默认实时任务最多占 95%),容器环境中还要确认 runtime 允许设置 RT 策略。判断是否需要这类调优的信号:perf sched 或 runqlat 显示调度延迟(runqueue latency)显著、cpu-migrations 计数高、延迟毛刺与核间迁移时间吻合。普通 Web 服务几乎不需要绑核——先确认你的负载真的缓存敏感。
CPU 调优的第二组手段面向计算密集型负载:编译优化与减少上下文切换。对 C/C++/Rust 等原生代码,编译器优化选项是免费的性能:-O2/-O3 的选择(-O3 的激进向量化偶尔因缓存压力反而变慢,需基准对比)、-march=native 启用目标机器的全部指令集(AVX2/AVX-512 的向量化可让数值代码快数倍,代价是二进制不可移植到旧 CPU——发布系统按机型分发不同构建)、LTO(链接时优化,跨编译单元内联与死代码消除,典型收益 5-15%,代价是链接时间大增)、PGO(Profile-Guided Optimization,用真实负载的运行剖面反馈给编译器,指导分支布局、函数排序与内联决策,典型收益 10-30%,是大型项目如 Chrome、LLVM、数据库内核的标配流程:先 -fprofile-generate 构建、跑代表性负载、再 -fprofile-use 重建)。上下文切换调优针对的是切换开销本身:自愿切换(voluntary,等待资源)过多通常意味着锁争用或 I/O 阻塞——这是应用问题,用 pidstat -w 区分自愿/非自愿切换,非自愿切换(involuntary,被抢占)过高说明运行队列拥挤或时间片过短。可调项包括:线程池规模与核数匹配(线程数远超核数会制造无谓的抢占式切换)、减少锁持有时间或用无锁结构(把自愿切换的源头消除)、批处理系统调用(io_uring 一次提交多个 I/O,减少用户态-内核态往返)、以及 sched_min_granularity_ns 等 CFS 参数微调(加大最小时间片减少抢占频率,代价是调度响应变钝——一般不建议动)。验证工具:perf stat 看 context-switches 与 cpu-migrations 的前后对比,vmstat 的 cs 列看全局切换率,runqlat 看调度延迟分布是否收窄。
内存调优的头号手段是大页(HugePages),它对 TLB 敏感的负载收益显著。原理:x86-64 默认页大小 4KB,一个访问 100GB 内存的进程需要约 2600 万个页表项,而 CPU 的 TLB(页表缓存)只有千级条目——TLB 未命中每次要多付出 7-30 个周期的页表遍历(有 page walk cache 缓解但仍昂贵),随机访问大内存的负载(数据库缓冲池、Java 大堆、内存计算引擎)TLB 命中率可能低到 50% 以下。大页把页大小提升到 2MB(或 1GB),同样 100GB 内存只需 5 万个 2MB 页,TLB 覆盖范围扩大 512 倍。Linux 提供两种机制:显式大页(HugeTLB)需要预先分配(vm.nr_hugepages 或启动参数 hugepagesz=2M hugepages=20480),应用通过 hugetlbfs 挂载点、mmap 的 MAP_HUGETLB 标志或 shmget 的 SHM_HUGETLB 使用——数据库(Oracle、PostgreSQL 的 huge_pages=on)与 JVM(-XX:+UseLargePages)都支持显式配置,优点是可预留、不会被回收,缺点是分配失败会静默退化或启动失败,需要监控 /proc/meminfo 的 HugePages_Free 确认生效。透明大页(THP)由内核自动合成 2MB 页,always 模式对所有匿名内存激进启用,madvise 模式只对 madvise(MADV_HUGETLB) 声明的区域启用。THP 的争议:对顺序访问大内存的负载(科学计算)有稳定收益,但 always 模式下 khugepaged 的后台合成与内存规整(compaction)会造成延迟毛刺——Redis、MongoDB、Oracle 官方都建议设为 madvise 或 never。实践决策:数据库与 JVM 大堆优先显式大页 + THP 设 madvise;验证方法是 perf stat -e dTLB-load-misses 对比前后,或用 TLB 剖析确认命中率提升。
内存调优的第二大主题是 NUMA(非一致内存访问)绑定。现代服务器 CPU 由多个 socket 或 chiplet 组成,每个 NUMA 节点有本地内存控制器:访问本地内存延迟约 80-100ns,跨节点访问要经过 UPI/Infinity Fabric 互联,延迟增加 50-100%,带宽也受互联限制。默认策略下,Linux 采用"首次触碰"(first touch)分配:内存分配在哪个 CPU 上执行就落在哪个节点。这在多数情况下够用,但有两类典型病症:其一,进程在节点 0 启动并分配了全部内存,之后线程被调度到节点 1 运行——所有访问都是远程的;其二,多实例数据库(如一台机器跑 4 个 MySQL 实例)不做 NUMA 规划,内存交错分布,跨节点流量把互联打满。诊断工具:numastat -p PID 显示进程内存在各节点的分布(Other Node 列占比高即跨节点问题)、numastat 全局视图看 numa_miss/numa_foreign 计数、perf c2c 检测跨节点缓存行争用。调优手段分两级:进程级用 numactl --cpunodebind=0 --membind=0 把进程与其内存都固定在节点 0(数据库多实例的标准部署方式:每实例绑定一个节点,实现"NUMA 分片");策略级用 --interleave=all 把内存轮转分布到所有节点(牺牲单线程延迟换取总带宽与分布均匀,适用于多线程均匀访问的负载,MySQL 单实例大缓冲池常用此策略避免单节点内存耗尽)。内核参数 vm.zone_reclaim_mode 必须保持为 0——开启后内核会优先回收本地内存而非跨节点分配,在内存压力下引发灾难性的反复回收。云上注意:多数云实例(非裸金属)暴露的是单 NUMA 节点或虚拟化的伪拓扑,numactl --hardware 先确认真实性;大规格实例(如 96 vCPU 以上)通常是多节点的,此时 NUMA 调优才真正有意义。
内存调优的第三个维度是分配器选择与减少内存拷贝。glibc 默认的 ptmalloc2 是通用分配器,在多线程高频分配/释放场景下有两个弱点:arena 锁争用(多线程竞争有限数量的分配 arena,线程数超过 8×核数后争用加剧)与释放后的碎片化(空闲块难以归还操作系统,RSS 持续膨胀)。替代方案 jemalloc(Facebook 用于 Redis、RocksDB、Firefox)与 tcmalloc(Google 用于其全线服务)都采用线程本地缓存(tcache/thread cache)设计:小对象分配走无锁的线程本地空闲链表,只有缓存耗尽才回落到中心堆——多线程分配吞吐可提升 2-5 倍,且两者的碎片控制与内存归还策略(jemalloc 的 dirty page decay、tcmalloc 的 aggressive release)通常优于 ptmalloc。切换成本极低:LD_PRELOAD=libjemalloc.so 即可对闭源程序生效,自研程序改链接即可;jemalloc 还内建堆剖析(MALLOC_CONF=prof:true,lg_prof_interval:30 定期 dump 分配剖面,jeprof 生成火焰图),是排查原生程序内存泄漏的利器。验证方法:切换前后跑相同负载,对比吞吐、P99 延迟、RSS 增长曲线与 perf 火焰图中 malloc/free 路径的占比。减少内存拷贝是另一条正交的优化线:数据在网络与磁盘路径上的每次拷贝都消耗 CPU 周期与内存带宽。手段包括 sendfile(文件到 socket 的零拷贝传输,nginx 静态文件的标配)、splice/vmsplice(管道中介的零拷贝)、mmap 读文件(省去用户态拷贝,但注意缺页成本与 SIGBUS 风险)、以及应用层的缓冲区复用(Java 的 DirectByteBuffer 与对象池、Go 的 sync.Pool、C++ 的 arena 分配)。判断拷贝是否是瓶颈:perf 火焰图中 copy_user_enhanced_fast_string 或 memcpy 类函数占比超过 5-10% 即值得优化。
I/O 调优的第一组旋钮是调度器、预读与脏页参数。块层 I/O 调度器的选择规则在 NVMe 时代已经简化:单队列设备(SATA SSD、虚拟盘)用 mq-deadline(保证请求截止时间,延迟公平)或 BFQ(桌面/混合负载的带宽公平,开销略高);多队列 NVMe 设备用 none(即不调度——设备内部有强大的并行队列,内核调度只会增加延迟与 CPU 开销,这也是云盘的推荐设置,因为真正的排队发生在存储后端);只有 HDD 时代 bfq/mq-deadline 的电梯排序(合并相邻请求减少寻道)才有决定性价值。查看与设置:cat /sys/block/nvme0n1/queue/scheduler,echo none 写入即生效。预读(readahead)针对顺序读负载:内核检测到顺序访问模式后提前多读数据进页缓存,默认 128KB;大文件顺序扫描(数据仓库、视频转码)可调到 1-4MB(blockdev --setra),随机读负载则应调小甚至归零(预读的数据用不上,白白消耗 I/O 带宽与页缓存——数据库随机读场景的常见优化)。脏页参数控制写回节奏:vm.dirty_ratio(脏页占内存比例达到此值时,写进程被强制同步回写——造成延迟毛刺的元凶)与 vm.dirty_background_ratio(后台回写启动阈值)。默认值在大内存机器上过于宽松(256GB 内存的 10% 是 25GB 脏数据,一旦触发同步回写就是秒级卡顿),写密集负载建议改用绝对值参数 vm.dirty_bytes/dirty_background_bytes(如 background 256MB、上限 1GB),把回写压力平滑化。验证:iostat -x 观察调整前后 w_await 的毛刺是否消失、/proc/meminfo 的 Dirty 曲线是否平稳。文件系统层还有挂载选项可调:noatime(消除读操作产生的元数据写,几乎无副作用,推荐全局启用)、ext4 的 data=writeback(放宽日志顺序,崩溃时新数据可能损坏——仅限可重建数据)。
I/O 调优的第二组手段是异步 I/O 与页缓存策略选择。同步阻塞 I/O 的模型是"一个在途请求":应用发起 read 后线程挂起,直到数据返回——对高并发小 I/O 负载(数据库、存储服务),这意味着要么开大量线程(内存与切换开销),要么吞吐被单请求延迟锁死(QD1 的 NVMe 只有约 5 万 IOPS,而设备能力是百万级)。异步 I/O 允许单线程维持高队列深度:libaio(io_submit,仅支持 O_DIRECT,语义受限)、io_uring(Linux 5.1+ 的革命性接口:提交队列与完成队列通过共享内存环传递,批量提交、零系统调用轮询模式、支持几乎所有 I/O 类型包括网络,单线程可达百万级 IOPS——RocksDB、PostgreSQL 17+、新版 fio 都已迁移)。应用改造成本高的场景,至少确认存储客户端库已启用异步模式(如数据库连接池、对象存储 SDK 的并发上传)。页缓存策略是另一个关键决策:buffered I/O(默认)经页缓存,读缓存命中时零磁盘 I/O,写被合并回写——适合读多写少、数据集可缓存、需要 POSIX 完整语义的负载;O_DIRECT 绕过页缓存直达设备,消除双重缓冲(应用自己的缓存 + 内核页缓存存两份数据)与回写毛刺,但失去预读与写合并,要求应用自己管理对齐与缓存——数据库(自带缓冲池)的标准选择。混合策略也存在:读用 buffered 蹭预读、写用 O_DIRECT 避免脏页积累(RocksDB 的做法)。相关参数:vm.swappiness 控制匿名页与页缓存的回收倾向(数据库主机常设 1-10,优先保留缓冲池、宁可回收页缓存;设 0 在旧内核会完全禁用 swap 有 OOM 风险,新内核语义已修正)、vm.vfs_cache_pressure 控制 dentry/inode 缓存的回收力度(元数据密集负载调低到 50 保留目录缓存)。验证手段:sar -B 看页扫描与回收、iostat 对比 O_DIRECT 前后的设备 I/O 模式、应用命中率指标确认缓存策略没有误伤。
网络调优的内核参数与拥塞控制选择需要按负载画像对症下药,盲目套用"优化清单"是常见错误。先分清负载类型:短连接高并发(Web API)、长连接大吞吐(备份、视频分发)、低延迟 RPC(微服务内部)、长肥管道(跨地域/跨国传输)——参数取向完全不同。通用且安全的调整:net.core.somaxconn 与应用的 backlog 参数一起决定全连接队列上限(默认 4096 在突发流量下可能不够,nginx 的 listen backlog 要同步调大,否则内核取小值——netstat -s 的 listen queue overflow 计数是诊断依据);net.ipv4.tcp_max_syn_backlog 同理管半连接队列;net.ipv4.tcp_tw_reuse=1 允许客户端侧复用 TIME_WAIT 连接(对短连接压测与代理服务器必要,安全性已由时间戳选项保障;注意 tcp_tw_recycle 在 NAT 环境有严重缺陷,Linux 4.12 起已移除,任何还教你开它的文章都过时了);文件描述符上限(ulimit -n 与 systemd 的 LimitNOFILE)是"参数"中最常翻车的一项——连接被拒的第一排查点。缓冲区参数 net.core.rmem_max/wmem_max 与 tcp_rmem/tcp_wmem 决定单连接窗口上限:默认值对 10Gbps+ 长肥管道不够(带宽时延积 BDP = 10Gbps × 50ms = 62.5MB),但同机房低 RTT 场景调大无益且浪费内存。拥塞控制算法的选择影响更大:cubic(默认)基于丢包反馈,在浅缓冲网络中把丢包当拥塞信号导致吞吐塌陷,在深缓冲网络中又制造 bufferbloat;BBR(Linux 4.9+)基于带宽与 RTT 的模型估计,不等待丢包即收敛到可用带宽,对跨广域网、有随机丢包的链路(无线、跨国)改善显著(吞吐提升数倍、延迟更平稳),sysctl net.ipv4.tcp_congestion_control=bbr 一行切换——但 BBRv1 与 cubic 流共存时的公平性有争议(BBR 会抢占更多带宽),多租户网络需评估,BBRv2/v3 正在改善此问题。验证:ss -ti 查看每连接的 cwnd、rtt、重传与所用算法,nstat 对比重传率前后变化。
网络调优的高阶手段是中断亲和、多队列与内核旁路。现代网卡是多队列的(每 CPU 一个硬件队列),但默认的中断亲和可能把所有队列的 IRQ 都指向 CPU0——mpstat 看到单核 %irq/%softirq 100% 而其他核空闲,这是"网络吞吐上不去、单核软中断打满"的经典指纹。调优:关闭 irqbalance 的自动管理(或配置其策略),手动把各队列 IRQ 的 /proc/irq/N/smp_affinity 分散到不同 CPU(通常避开运行业务线程的核,或与 RPS 配合),并确认网卡的 RSS 哈希把流量均匀散列到队列(对称哈希配置可保证双向流落同核,提升缓存局部性)。软件侧的对应物是 RPS/RFS(Receive Packet Steering/Flow Steering):虚拟网卡或队列数少于 CPU 数时,用软件把收包处理分散到多核(/sys/class/net/eth0/queues/rx-0/rps_cpus 位图),RFS 进一步把包 steering 到应用线程所在核(提升数据缓存命中)。XPS 是发送侧的对应机制。当内核协议栈本身成为瓶颈(单核 pps 极限约 100-200 万,100Gbps 小包场景远超此值),进入内核旁路领域:DPDK 把网卡完全交给用户态驱动,轮询模式零中断、大页内存、无系统调用,单核可达千万级 pps——代价是独占 CPU 核(轮询即 100% 占用)、失去内核协议栈(需用户态 TCP/IP 如 F-Stack/Seastar)、开发运维复杂度陡增,适用于 NFV、负载均衡器、高频交易等极端场景。XDP(eXpress Data Path)是折中方案:在网卡驱动层挂载 eBPF 程序,在 skb 分配之前处理包,单核可达数百万 pps,支持丢弃(DDoS 防护)、重定向、改写转发,且保留内核生态——Cloudflare 与 Facebook 的 L4 负载均衡都基于 XDP。决策顺序:先把多队列 + 中断亲和 + GRO/GSO 做对(覆盖 95% 的场景),确认瓶颈真在内核协议栈(perf 看软中断火焰图中 ip_rcv/tcp_v4_rcv 占比)再考虑 XDP,最后才是 DPDK。
应用层调优有一组跨语言、跨框架的通用模式,其收益通常远超系统级参数。连接池是第一条:TCP 建连的三次握手(1 RTT)+ TLS 握手(1-2 RTT)+ 慢启动,使短连接的开销比长连接高一个数量级——数据库、HTTP 客户端、Redis 客户端都必须池化;池大小的设定不是越大越好,PostgreSQL 社区的经验公式是"核数×2 + 有效磁盘数"起步,过大的池把并发压力转嫁给后端引发锁争用(连接池是排队论中的服务台数量,盲目加台会让厨房更乱)。缓存是第二条:多级缓存(进程内 LRU → 分布式缓存 → 数据库)把热点数据的访问从毫秒级压到微秒级,关键工程点是失效策略(TTL vs 主动失效 vs 订阅变更)、穿透防护(空值缓存、布隆过滤器)、雪崩防护(TTL 加随机抖动)、以及缓存一致性边界的明确声明。批处理是第三条:把 N 次小操作合并为 1 次大操作,摊薄每次的固定开销(网络 RTT、系统调用、事务提交、磁盘寻道)——Kafka 生产者的 batch.size/linger.ms、数据库的批量 INSERT、HTTP 的 pipeline 与多路复用都是此模式;批大小的权衡是吞吐与延迟(等批的时间),按 SLO 设定 linger 上限。异步化是第四条:把"发起后必须等待"改为"发起后注册回调/投递队列",消除线程在等待上的浪费——I/O 异步(事件循环、协程)、任务异步(消息队列削峰)、调用链异步(非关键路径旁路化,如日志与指标发送绝不阻塞请求路径)。减少序列化开销是第五条:JSON 的文本解析在 RPC 密集服务中可占 10-30% CPU,切换到 Protobuf/FlatBuffers、启用压缩的合理档位(zstd 低档位常优于 gzip 高档位)、避免重复序列化(透传原始字节)都有直接收益。这些模式的共同点:它们改变的是工作的数量与组织方式,而不是执行工作的环境——这正是应用层调优杠杆大于系统层的根本原因。
调优的验证与回滚工程决定了调优成果能否安全落地。验证的第一原则是对比的可比性:调优前后必须用相同的负载模型、相同的测量方法、相同的环境(第 11 章的全部纪律在此适用)。生产环境的验证有三个层次:(1)离线基准验证——在预发环境用录制的生产流量或标准负载(wrk2/sysbench/fio)做 A/B 对比,统计显著性检验确认改善超出噪声;(2)金丝雀对比——把参数变更发布到 1-5% 的实例,与对照组同时承接生产流量,对比业务指标(延迟分布、错误率、转化率)与系统指标(CPU、内存、重传率),这是最可信的验证,因为它直接测量真实负载下的效果;(3)全量发布后的持续观测——变更后的 24-72 小时是观察期,覆盖日周期(高峰/低谷)与周周期(工作日/周末)的负载形态,某些参数问题只在特定负载下显形(如缓冲区调大引发的内存压力只在高峰出现)。回滚工程的要求:每个调优变更都有记录在案的"变更前值"与回滚命令(sysctl 的运行时参数可即时回滚,但内核启动参数如 isolcpus、hugepages 需要重启——这类变更的金丝雀期要更长);配置管理工具(Ansible/Terraform)中参数以声明式代码存在,回滚即 revert 提交;监控告警中为关键调优项设置护栏指标(如开启 BBR 后监控重传率与公平性投诉、调大缓冲区后监控 TCP 内存总量 tcp_mem)。最后是组织纪律:调优变更走与代码变更相同的评审流程(变更单说明机理、预期收益、验证数据、回滚方案),重大参数变更避开业务高峰发布,一次发布不捆绑多个不相关变更——当金丝雀指标异常时,你需要能立刻回答"是哪个改动干的"。调优的终点不是"参数改完了",而是"改善被验证了、变更被固化了、知识被记录了"。
代码示例
taskset -c 2,3 ./latency_sensitive_app
/* 把进程绑定到 CPU 2 和 3 */
taskset -pc 4-7 12345
/* 修改运行中进程 PID 12345 的亲和到 CPU 4-7 */
chrt -f -p 80 12345
/* 把 PID 12345 设为 SCHED_FIFO 实时策略,优先级 80 */
numactl --cpunodebind=0 --membind=0 ./database_server
/* 进程与内存都固定在 NUMA 节点 0 */
numactl --interleave=all ./mysql_server
/* 内存交错分布到所有节点:单实例大缓冲池的常用策略 */
numastat -p 12345
/* 查看进程内存在各 NUMA 节点的分布 */
cat /proc/irq/24/smp_affinity_list
echo 2 > /proc/irq/24/smp_affinity_list
/* 查看并修改 24 号中断的 CPU 亲和 */这组命令覆盖 CPU 与 NUMA 调优的日常操作。taskset -c 用 CPU 列表语法(逗号分隔与区间均可)启动绑核进程,-p 修改已运行进程——绑核后立刻用 perf stat 对比 cpu-migrations(应降为 0)与 IPC(缓存局部性改善的直接证据)。chrt 设置实时调度策略:-f 是 SCHED_FIFO(同优先级内不轮转,跑到主动让出为止),-r 是 SCHED_RR(时间片轮转),优先级 1-99 高于所有普通线程——使用前确认 /proc/sys/kernel/sched_rt_runtime_us 的 RT 限流配置(默认 950000/1000000,即实时任务最多占 95% CPU,防止忙等线程锁死系统),并在压测中验证不会饿死关键系统进程。numactl 的两种内存策略代表两个典型场景:--membind 严格绑定(多实例分片部署,每实例独占一个节点的 CPU 与内存,跨实例互不干扰),--interleave 轮转分布(单实例多线程均匀访问,避免单节点内存先耗尽、总带宽更高但单次访问延迟略升);--preferred=N 是第三种折中(优先节点 N,耗尽后溢出到其他节点)。numastat -p 是验证绑定生效的标准手段:输出按节点列出进程的 Huge/Heap/Stack/Private 内存量,绑定正确时非目标节点列应接近零。IRQ 亲和的修改要注意:多数发行版的 irqbalance 服务会周期性覆盖手动设置,先 systemctl stop irqbalance(或配置其 IRQ 黑名单)再写入 smp_affinity_list;修改后用 mpstat -P ALL 1 确认软中断负载(%irq/%soft 列)已从单核分散到目标核。grep -i huge /proc/meminfo
/* 查看当前大页状态:HugePages_Total/Free/Rsvd 与 Hugepagesize */
sysctl -w vm.nr_hugepages=20480
/* 运行时预留 20480 个 2MB 大页(共 40GB)——内存碎片化时可能分配不足 */
cat /sys/kernel/mm/transparent_hugepage/enabled
echo madvise > /sys/kernel/mm/transparent_hugepage/enabled
/* THP 从 always 改为 madvise:消除 khugepaged 与 compaction 的延迟毛刺 */
mount -t hugetlbfs none /mnt/huge -o pagesize=2M
/* 挂载 hugetlbfs 供 DPDK 或数据库使用 */
sysctl -w vm.swappiness=1
/* 数据库主机:优先回收页缓存,保护匿名页(缓冲池) */
sysctl -w vm.dirty_background_bytes=268435456
sysctl -w vm.dirty_bytes=1073741824
/* 用绝对值替代比例值控制脏页:后台回写 256MB 启动,1GB 强制同步 */
sysctl -w vm.zone_reclaim_mode=0
/* 必须为 0:禁止 NUMA 本地回收优先,避免内存压力下的反复回收风暴 */内存子系统调优的参数操作全集。大页部分:/proc/meminfo 的 HugePages_Total 是已预留数、Free 是空闲数、Rsvd 是已被应用承诺但未实际使用的数——验证大页生效的标准是应用启动后 Total 减少或 Free 下降;运行时 sysctl 预留大页受内存碎片制约(需要连续的 2MB 物理块),大内存机器建议在内核启动参数中预留(default_hugepagesz=2M hugepagesz=2M hugepages=20480),或先 echo 1 > /proc/sys/vm/compact_memory 触发规整再分配。THP 的三档选择:always(对所有匿名内存自动合成大页——顺序访问大内存的计算负载受益,但 khugepaged 后台合成与 direct compaction 会造成毫秒到百毫秒级的延迟毛刺,Redis/MongoDB/Oracle 的官方建议都是关闭)、madvise(只对显式声明的区域启用——推荐的折中,JVM 与新版数据库会自动 madvise)、never。swappiness 的语义常被误解:它不是"swap 使用倾向的开关"而是匿名页与文件页回收的相对权重(0-200,默认 60),设 1 表示几乎只在 OOM 边缘才换出匿名页——数据库主机的标准设置,因为缓冲池被换出后性能断崖;设 0 在新内核(5.8+)仍有保留意义但旧内核会完全禁用 swap,需谨慎。脏页参数用 bytes 版本替代 ratio 版本是大内存机器的必修课:256GB 内存下 dirty_ratio=20 意味着 51GB 脏数据才强制回写,触发时就是数十秒的 I/O 风暴;改为绝对值后回写行为与内存大小解耦。zone_reclaim_mode 是历史遗留陷阱:某些发行版的旧调优脚本会开启它,在多节点机器上引发"本地内存紧张时疯狂回收而非跨节点分配"的病理行为,现代内核默认已关闭,检查一遍确保没有被脚本改回。export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so
./my_cpp_service
/* 零代码切换分配器:对任意动态链接程序生效 */
export MALLOC_CONF="prof:true,prof_prefix:/tmp/jeprof,lg_prof_interval:30,lg_prof_sample:19"
./my_cpp_service
/* 启用 jemalloc 堆剖析:每分配约 512KB 采样一次,定期 dump 剖面文件 */
jeprof --show_bytes --pdf ./my_cpp_service /tmp/jeprof.*.heap > heap.pdf
/* 生成内存分配火焰图/调用图,定位分配热点与泄漏 */
perf stat -e dTLB-load-misses,dTLB-loads ./workload
/* 大页调优前后对比:dTLB 未命中率应显著下降 */
perf mem record ./workload && perf mem report
/* 采样内存访问事件:识别远程 NUMA 访问与缓存未命中的代码位置 */
perf c2c record -a sleep 30 && perf c2c report
/* 缓存行争用检测:定位跨核(尤其跨 NUMA)乒乓的缓存行与代码 */分配器切换与内存剖析的实战命令。LD_PRELOAD 方式的价值在于对闭源二进制与不便重编译的程序也能生效(Redis 官方文档即推荐 jemalloc,多数发行版的 Redis 包已静态链接);自研 C/C++ 服务建议在构建时直接链接(-ljemalloc),避免环境变量在容器/systemd 环境中丢失。MALLOC_CONF 的堆剖析参数解读:prof:true 开启剖析,lg_prof_sample:19 表示平均每 2^19=512KB 分配采样一次(采样式剖析开销约 1-2%,可生产常开),lg_prof_interval:30 表示每分配 2^30=1GB 自动 dump 一次剖面——对比两个时间点的剖面(jeprof 的 --base 选项做差分)即可定位泄漏:持续增长的分配调用栈就是嫌疑犯,这是原生服务内存泄漏排查的标准流程,与第 6 章的 memleak(eBPF)互补——memleak 看"分配后未释放"的年龄分布,jeprof 看"谁在分配"的调用栈归因。tcmalloc 的对应物是 HEAPPROFILE 环境变量与 pprof 工具,Google 系语言栈(Go 的 runtime/pprof)内建同源能力。perf stat 的 dTLB-load-misses 是验证大页收益的黄金指标:未命中率从百分之几降到千分之几、每指令的 TLB miss 成本消失,对应 IPC 上升;perf mem 与 perf c2c 是 NUMA 问题的显微镜——c2c 报告的 HITM(Hit Modified,从别的核的缓存取脏行)事件按地址聚合,直接指出哪一行数据结构、哪两行代码在跨核乒乓,这是共享数据结构伪共享(false sharing)与跨节点争用的定位利器。cat /sys/block/nvme0n1/queue/scheduler
echo none > /sys/block/nvme0n1/queue/scheduler
/* NVMe 与云盘:关闭内核调度,直通设备多队列 */
blockdev --setra 2048 /dev/sda
/* 预读设为 2048 个 512B 扇区(1MB):顺序扫描负载 */
blockdev --setra 8 /dev/sda
/* 随机读负载(数据库):预读几乎归零,避免无效 I/O */
mount -o remount,noatime /data
/* 消除读操作的 atime 元数据写 */
fio --name=verify --ioengine=io_uring --direct=1 --rw=randread --bs=4k --iodepth=64 --numjobs=1 --filename=/dev/nvme0n1 --runtime=60 --time_based
/* 验证 io_uring 单线程高队列深度的 IOPS 能力 */
iostat -xz 1
/* 调优前后对比:w_await 毛刺、%util、aqu-sz 的变化 */
sar -B 1
/* 页扫描与回收活动:pgscand(直接回收)应为零 */存储调优的操作与验证闭环。调度器设置是即时的(写 sysfs 立即生效,重启失效——持久化用 udev 规则或 tuned/irqbalance 类配置管理):决策树是"多队列 NVMe 或云盘 → none;单队列 SATA/SAS SSD → mq-deadline;HDD 或桌面混合负载 → bfq"。预读的单位是 512B 扇区(--setra 2048 即 1MB),设置后立刻可用 fincore 或自测脚本验证顺序读吞吐变化;注意预读对每个块设备独立,LVM/RAID 之上的逻辑设备与底层物理设备都要设置才完整生效。noatime 是收益/风险比最高的挂载选项变更:默认 relatime 已把 atime 更新降频(每天一次或 atime 早于 mtime 时),但 noatime 完全消除——邮件服务器(Maildir 依赖 atime 判断已读)等极少数场景除外,其余一律推荐。fio 的 io_uring 引擎验证有两个观察点:单 numjobs=1 配 iodepth=64 应能打出接近设备标称的随机读 IOPS(对比 libaio 引擎与 iodepth=1 的结果,直观展示异步与队列深度的杠杆);同时 mpstat 看该 fio 线程的 CPU 占用——io_uring 的批量提交应使每 I/O 的 CPU 成本显著低于同步方式。验证纪律:每项存储参数变更后,用相同的 fio 负载或生产流量回放对比 iostat 三指标(await 分布、%util、aqu-sz)与应用侧延迟分布,改善不显著或有恶化即回滚——存储参数的负载依赖性极强,别人的最优值不是你的最优值。sysctl -w net.core.somaxconn=65535
sysctl -w net.ipv4.tcp_max_syn_backlog=65535
sysctl -w net.ipv4.tcp_tw_reuse=1
sysctl -w net.ipv4.tcp_slow_start_after_idle=0
sysctl -w net.core.rmem_max=134217728
sysctl -w net.core.wmem_max=134217728
sysctl -w net.ipv4.tcp_rmem="4096 87380 134217728"
sysctl -w net.ipv4.tcp_wmem="4096 65536 134217728"
sysctl -w net.ipv4.tcp_congestion_control=bbr
sysctl -w net.core.default_qdisc=fq
/* BBR 配合 fq 队列规则(pacing)效果最佳 */
ss -ti dst 10.0.0.5
/* 查看到特定目标的连接详情:cwnd、rtt、retrans、拥塞算法 */
nstat -az | grep -i retrans
/* 全局重传统计:调优前后对比 TcpExtTCPLostRetransmit 等计数 */
ethtool -S eth0 | grep -iE "drop|discard|error|miss"
/* 网卡层丢包计数:ring buffer 溢出(rx_no_buffer)与错包 */
ethtool -G eth0 rx 4096 tx 4096
/* 增大网卡环形缓冲区:突发流量下减少 rx_dropped */网络调优参数的完整操作序列与验证工具。参数分四组理解:队列组(somaxconn/tcp_max_syn_backlog——防连接洪峰溢出,必须与应用 backlog 联动,nginx 需同步改 listen 的 backlog 参数);连接复用组(tcp_tw_reuse 解决客户端/代理侧的 TIME_WAIT 端口占用,tcp_slow_start_after_idle=0 关闭空闲连接的慢启动重置——长连接 RPC 服务的重要优化,否则每次空闲后的请求都要重新爬窗口);缓冲区组(rmem/wmem 的三元组语义是"最小/默认/最大",最大值决定窗口自动调优的天花板——按 BDP 计算:目标带宽 × 最大 RTT,100Gbps × 10ms = 125MB,跨洋链路要更大;同机房低 RTT 场景保持默认即可,过大缓冲只会加重 bufferbloat);拥塞控制组(bbr 需要内核 4.9+,配合 fq qdisc 的 pacing 避免突发注入——fq 把 BBR 计算的发送速率整形为平滑的包间隔)。验证工具链:ss -ti 是单连接显微镜(输出 cwnd、rtt/rttvar、retrans、pacing_rate 与所用算法——确认 bbr 真的生效,以及重传是否下降);nstat -az 读取并清零 /proc/net/netstat 与 snmp 计数(grep Retrans 看重传率的分子分母:TcpRetransSegs/TcpOutSegs,调优前后各采一次算差值);ethtool -S 是网卡硬件计数(rx_no_buffer_count/rx_missed_errors 上升说明 ring buffer 或处理速度不足,先 -G 加大 ring,再查中断亲和);配合第 9 章的 tcpretrans/tcplife(eBPF)做事件级追踪。纪律重申:这组参数不要整段复制到所有机器——逐项评估负载相关性,逐项验证,记录变更理由。实验笔记
实验十二:完成一次带完整验证闭环的系统调优。
实验目标:(1)对一个故意配置不佳的基准系统,用诊断数据驱动逐项调优,量化每一步的收益;(2)亲手实践"基线 → 单变量变更 → 验证 → 固化/回滚"的调优纪律;(3)体验 Amdahl 定律——验证"优化占比小的瓶颈收益有限"。
环境准备:一台 4 核 8GB Linux 虚拟机,部署一个混合负载作为调优对象:nginx(静态文件 + 反向代理)+ 一个 Python/Go 写的小 API 服务(含 JSON 序列化与内存计算)+ sysbench 可打压的 MySQL。故意注入"未调优"状态:ulimit -n 1024、somaxconn 保持默认、THP 为 always、MySQL 未绑 NUMA 且缓冲池偏小、预读默认、网卡单队列中断集中在 CPU0(虚拟机可用 taskset 把 qemu 进程的中断线程固定到单核模拟)。准备负载脚本:wrk2 恒定速率打 API、sysbench oltp_read_write 打 MySQL、fio 打磁盘,三者可同时运行模拟混合负载。
步骤一(基线建立):空载记录环境快照(uname -a、sysctl 全量导出、numactl --hardware、ethtool -i/-l)。运行混合负载 10 分钟,采集:wrk2 的 P50/P99/P99.9、sysbench 的 tps 与延迟百分位、fio 的 IOPS 与 await、系统侧的 60 秒清单数据 + 火焰图(perf record -a -g 60 秒)+ PSI 三项时序。所有数据存档为 baseline 目录。写一份"瓶颈清单":按火焰图宽度与 USE 饱和度排序,标注每项的占比 p 与 Amdahl 估算的收益上限。
步骤二(第一轮调优:最大瓶颈):针对清单第一名实施调优(预期是应用层:如 MySQL 缓冲池调大 + 缺失索引修复,或 API 服务的连接池配置)。一次只改一项,重跑完全相同的负载与采集,存入 round1 目录。对比:目标指标改善幅度 vs Amdahl 预测、其他指标有无恶化、火焰图差分确认机理(预期的热点塔变窄)。若改善不显著,回滚并记录负面结果,转向下一假设。
步骤三(第二轮:系统级参数):依次实施 THP 改 madvise、somaxconn 与 backlog 调大、ulimit 放开、预读按负载调整、脏页参数改绝对值。每项独立验证(重跑负载 + 对比),把"有效项"写入一个 sysctl 调优文件(含注释说明每项的机理与验证数据——这就是参数即代码),"无效项"记录后放弃。特别观察:THP 变更前后 API 服务 P99.9 的毛刺变化(用 wrk2 的延迟直方图对比长尾)。
步骤四(第三轮:CPU/NUMA/中断):把 MySQL 用 numactl 绑定节点(虚拟机若为单节点,改用 taskset 绑核模拟隔离效果)、把中断/软中断处理与业务线程分核、为延迟敏感组件尝试 chrt 提升优先级。用 perf stat 对比 cpu-migrations、context-switches、IPC 的变化,用 runqlat(bcc)对比调度延迟分布。
步骤五(Amdahl 验证实验):故意选择一个占比 <3% 的瓶颈(如某个低频系统调用路径)做深度优化,投入与步骤二相当的精力,量化最终整体收益——亲身体验"局部优化的陷阱",写 200 字反思。
步骤六(综合验收与报告):全部有效调优叠加后重跑混合负载,输出最终对比报告:每轮变更的内容、机理、验证数据、累计收益曲线;灰度发布方案设计(如果这是生产集群,如何分批发布这些参数、护栏指标是什么、回滚命令清单)。
预期结果:一份可复现的调优记录(脚本 + 数据 + 报告),累计改善通常可达 30%-数倍(取决于注入问题的严重度),以及对"诊断驱动调优"与"盲目调参"差异的切身体会。
进阶挑战:(1)给 MySQL 配置显式大页(huge_pages=ON + 预留),对比缓冲池大页化前后的 dTLB-load-misses 与 tps;(2)用 tc netem 注入 50ms 延迟模拟长肥管道,对比 cubic 与 bbr 的吞吐差异;(3)把全部调优固化为 Ansible playbook,在第二台"脏"虚拟机上一键重放并自动验证收益。
延伸阅读
- 《Systems Performance》第 2 版第 12 章 Tuning 原文(注意原书章节编号:本书第 11 章 Benchmarking、第 12 章 Tuning、第 13 章 Case Studies,与本教材的章节切分略有差异)。Gregg 的调优总论:调优层次模型、Amdahl 定律与 USL 的调优应用、各子系统调优要点的索引式综述,以及"tuning checklist"。原书强调调优必须建立在第 2 章方法论与前面各章子系统知识之上,本章的展开可视为对该章的工程化细化。
- Brendan Gregg 的博客文章《Linux perf_events / eBPF Observability》系列与《Who Wakes the Waker》《runqlat》等工具发布文(brendangregg.com/blog)。验证调度类调优(绑核、RT 策略、隔离)效果的最佳资料:runqlat/runqslower 的设计动机与判读方法、调度延迟的成因分解。每篇工具文都附带真实案例,展示"指标 → 机理 → 调优 → 验证"的完整链条。
- jemalloc 官方 Wiki(github.com/jemalloc/jemalloc/wiki)特别是 Background Thread、Profiling 与 Tuning 章节;以及 Jason Evans(jemalloc 作者)的演讲《Memory Fragmentation》。分配器调优的一手资料:arena 数量、dirty page decay 时间、tcache 参数的语义与调法,堆剖析的完整工作流。Redis 与 RocksDB 的内存问题排查记录中大量引用这些机制。
- 《NUMA 相关文档》:内核源码 Documentation/admin-guide/mm/numa_memory_policy.rst 与 Frank Denneman(VMware)的 NUMA 深度系列博文(frankdenneman.nl)。NUMA 调优最系统的第三方资料:first touch 语义、各内存策略(bind/interleave/preferred/local)的内核实现细节、虚拟化环境的 NUMA 拓扑呈现(vNUMA)配置。数据库多实例 NUMA 分片部署的方案设计依据。
- io_uring 系列资料:Jens Axboe(io_uring 作者)的 LWN 文章《Efficient IO with io_uring》与 Kernel Recipes 演讲,以及 unixism.net/loti 的 io_uring 教程站。异步 I/O 调优的必读:SQPOLL 轮询模式、注册缓冲区、与 libaio 的语义差异、各语言绑定(tokio-uring、liburing)。PostgreSQL 17 与 RocksDB 的 io_uring 集成文档展示了数据库侧的落地方式。
- Google 的 BBR 论文《BBR: Congestion-Based Congestion Control》(ACM Queue, 2016)与内核 Documentation/networking/bbr.rst。拥塞控制调优的理论基础:为什么丢包信号在浅缓冲与随机丢包网络中失效、BBR 的带宽/RTT 模型、pacing 的必要性。配合第 9 章的 tcpretrans 观测,理解"换算法前后重传率与吞吐的变化"的机理。BBRv2/v3 的演进(对公平性与 ECN 的处理)见 IETF draft。
- 各数据库与中间件官方性能指南中的调优章节:PostgreSQL《Tuning Your PostgreSQL Server》与 pgtune 工具、MySQL Performance Schema 文档、Redis 官方 latency 排查指南(redis.io/topics/latency)、nginx 官方《Tuning NGINX for Performance》。应用与运行时层调优的权威参考:连接池、缓冲、大页、THP 的具体建议都以官方文档为准——它们比通用"优化清单"更了解自身的工作负载假设。
- tuned 与 irqbalance 的项目文档(tuned 是 RHEL 系的调优配置管理框架)。生产环境参数固化的工程实践:tuned 的 profile 机制(throughput-performance、latency-performance、virtual-host 等预置模板 + 自定义 profile)把本章的散装 sysctl/启动参数组织成可管理、可切换、可审计的配置单元;配合 Ansible 的 sysctl 模块实现集群级参数即代码。