资讯详情

RHEL 9.7系统性能优化实战:从内核参数到应用侧调优

📅 2026/10/8 2:50:19 | 华诺云谱 👁 阅读
RHEL 9.7系统性能优化实战:从内核参数到应用侧调优
上周我刚把一批承载Java网关的服务器从RHEL 8.10原地升级到RHEL 9.7升级过程顺利得有点意外业务方也没来找麻烦。结果压测报告一出来对面直接甩了句“P99比之前高了一截”。我第一反应是怀疑内核调度策略变了可真把数据拉出来看发现事情没那么简单——RHEL 9.x默认的调优思路和8.x不完全一样而我们那套部署长期吃的是8.x时代攒下来的“参数红利”。升级之后一部分红利失效了另一部分被新机制接管问题就这么暴露出来了。这篇文章不打算写成参数堆砌清单而是把我在RHEL 9.7上做系统优化的一整条链路拆开来讲从内核引导参数、tuned配置集到网络栈、存储、内存NUMA、进程调度再到应用侧编译和性能剖析最后是效果度量与回归验证。适合正在从8.x往9.x迁移、或者新装RHEL 9.7准备承载生产负载的运维、SRE、后端性能优化同学参考。我会把每一步为什么这么做、踩过哪些坑都写清楚你可以按章节直接落地。1. 升级后的第一件事先看默认配置把系统带到了哪里1.1 升级后先看这四样东西拿到一台9.7之后我习惯先检查四件事而不是急着改参数/proc/cmdline内核命令行实际生效的参数很多优化都从这里开始。/sys/kernel/mm/transparent_hugepage/enabled透明大页当前策略。/sys/devices/system/cpu/cpu0/cpufreq/scaling_governorCPU频率调节器是performance还是powersave。tuned-adm active_profile当前生效的tuned配置集。这四项决定了CPU、内存、电源管理的基础行为它们没摸清楚后面所有优化都是把楼盖在沙子上。RHEL 9.7的内核基于5.14系列持续迭代调度器、内存管理、网络栈都在不断变化默认值不一定差但一定不是为了你的业务定制的。升级之后第一步不是“加参数”而是确认系统现在处于什么状态再决定往哪个方向调整。举个例子RHEL默认透明大页策略是always对绝大多数Java、数据库类应用来说并不友好。khugepaged后台线程会持续扫描内存并尝试合并大页合并过程中可能造成额外的CPU占用和内存碎片甚至导致个别时候出现让人摸不着头脑的延迟毛刺。这个默认策略不是9.7的错但如果你从8.x带来的服务没有显式处理THP升级后问题就会一直在那里。1.2 内核命令行参数该动的和不该动的通过grubby修改内核命令行参数而不是直接改/etc/default/grub再跑grub2-mkconfig在RHEL 9.x上更安全、也更直观。比如我这样操作grubby --update-kernel/boot/vmlinuz-$(uname -r) --argstransparent_hugepagenever nmi_watchdog0改完以后用grubby --infoALL确认重启后看/proc/cmdline。这里说几个我实际使用中认为值得考虑的选项transparent_hugepagenever或madvise前者彻底关闭THP后者只对显式调用madvise(MADV_HUGEPAGE)的应用启用。数据库、Java服务我一般用madvise或never把内存合并的决策权还给应用。nmi_watchdog0关闭NMI看门狗周期性中断能减少一点系统噪声。对延迟敏感的服务有帮助但如果是需要硬件故障快速发现的关键节点保留它更稳妥。intel_idle.max_cstate1 processor.max_cstate1限制CPU进入深度空闲状态降低从C state唤醒的延迟。适用于在线低延迟场景代价是空闲功耗上升。纯计算型批处理反而没必要。loglevel3减少控制台日志输出对性能影响非常小但可以让启动阶段和运行期的中断噪声少一点。也有几个参数我明确不建议动isolcpus只在确定要做CPU隔离、并且有配套中断绑定和进程绑核方案时才碰否则容易把业务线程“关在笼子里”却没人喂食audit0能减少一点auditd开销但会牺牲审计能力等保和合规场景直接禁用numaoff这种暴力参数更是想都不要想代价远大于收益。1.3 内核参数不是跑分工具经常有人问我“这些参数能不能直接抄到生产”我的回答是抄可以但必须理解参数之间的联动。比如transparent_hugepagenever在某些大内存读多写少的场景反而会降低TLB命中率因为我关掉了大页映射。这时候正确做法是用madvise让应用自己决定。同样nmi_watchdog0对大多数机器影响很小但在特定硬件平台上有用换一批机器可能毫无感知。所以内核命令行调整的核心原则是一次只改一个目标重启验证记录前后差异。别一口气塞五六个参数进去出了问题你根本不知道是谁的锅。2. tuned配置集把调优沉淀成配置而不是临时命令2.1 为什么我强烈建议用tuned而不是堆sysctl每个团队里几乎都有一个“老运维”的服务器上躺着一份几十行的/etc/sysctl.conf每行还带着注释“某年某月为了某事故加的”。这种做法的最大问题是不可验证、不可按负载切换。systemd-sysctl只在开机时加载一次运行期你改动还得手动执行sysctl --system而且不同机器之间配置漂移严重。RHEL 9.7携带的tuned已经是2.x系列本身是一个systemd守护进程通过profile的方式管理一组调优参数。它不仅仅能改sysctl还能调整CPU governor、透明大页、内核命令行参数、磁盘设备的读取ahead大小甚至可以根据网卡流量动态切换参数。这才是我理解的“调优沉淀成代码”一个profile就是一个可版本管理、可回滚、可复制的调优单位。2.2 按负载选型默认profile怎么挑RHEL 9.7自带了一组profile我简单整理一下使用场景Profile适用场景我的一般用法balanced默认均衡配置开发测试机、低负载混合服务throughput-performance吞吐优先关闭省电批处理、大数据分析、文件传输latency-performance低延迟优先在线接口、网关、数据库主节点network-latency网络低延迟增强高频交易、实时音视频信令virtual-guest虚拟机guest侧云上虚拟机实例选型建议是在线服务优先latency-performance或以其为基础做自定义离线吞吐型任务用throughput-performance云上虚拟机用virtual-guest。注意network-latency不要无脑启用它会把busy_poll等参数开得比较大某些网卡驱动不支持时反而增加CPU占用。2.3 自定义profile实战一个Java在线服务的例子通常我不会直接套默认profile而是在它基础上include一层自己的配置。下面是我在9.7上给一个Java在线服务做的profile[main] summaryLatency optimized profile for Java online services includelatency-performance [cpu] governorperformance force_latency1 [vm] transparent_hugepagesmadvise [sysctl] vm.swappiness10 net.core.somaxconn4096 net.ipv4.tcp_fin_timeout15把这段配置放到/etc/tuned/latency-java/tuned.conf然后执行tuned-adm profile latency-java tuned-adm active重点看三个验证命令cat /sys/kernel/mm/transparent_hugepage/enabled cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor sysctl vm.swappiness net.core.somaxconn这个profile的运行逻辑是先继承latency-performance的默认调优再把CPU锁定在performance governorTHP改为madvise最后按业务需求调整swap和连接队列参数。整条链路清晰哪天不想用了tuned-adm profile balanced一条命令回滚。2.4 tuned使用中的两个坑第一修改了/etc/tuned/下的profile之后必须重启tuned或者切换再切回profile才会重新加载只改文件不会热生效。我一般用tuned-adm off tuned-adm profile latency-java来强制重载避免因为缓存导致配置不生效。第二include只是把父profile的参数作为基础同一参数的子profile覆盖关系要梳理清楚。如果发现改完没生效先去看tuned-adm active确认当前profile路径再确认是否存在两个profile同时指向同一个sysctl参数。tuned不是银弹但它是RHEL体系中管理调优的最佳载体。我的习惯是所有运行期需要持久化的参数能进tuned就进tuned不进/etc/sysctl.conf。3. 网络与存储把中断和队列安排明白3.1 网卡多队列、ring buffer与中断处理网络优化里最容易立竿见影的是网卡队列。很多机器默认网卡队列数等于CPU核数但ring buffer通常是自动协商的较小值。先用ethtool -l eth0看当前最大值和实际值再决定要不要调ethtool -L eth0 combined 8 ethtool -G eth0 rx 4096 tx 4096ethtool -L调整队列数量ethtool -G调整ring buffer大小。注意ring buffer调大会占用内存并且有些网卡驱动对数值有上限要求先看ethtool -l的输出再动手。中断绑定方面RHEL 9.7默认运行irqbalance服务通常不需要手动绑中断让系统动态分配应对突发流量更合理。但如果你的业务要求极低延迟、或者发现单核softirq占用接近100%才考虑关闭irqbalance手动把队列中断绑定到指定CPU。手动绑定的代价是后续扩缩容和硬件变更都要手动维护我一般只在数据库和实时音视频场景做。判断是否需要调整网卡队列主要看mpstat -P ALL 1里有没有某个核的softirq占比明显高于其他核以及ethtool -S eth0 | grep -i rx里有没有rx_missed或rx_dropped持续增长。3.2 TCP栈参数先调对再调大RHEL 9.7的内核对TCP默认参数已经比较合理盲目把缓冲区调大反而会导致内存占用飙升、丢包后恢复变慢。我优先关注这几个参数net.core.somaxconn 4096 net.ipv4.tcp_fin_timeout 15 net.ipv4.tcp_max_syn_backlog 8192如果服务端短连接特别多TIME_WAIT堆积明显优先调tcp_fin_timeout和应用层连接复用而不是依赖tcp_tw_reuse。在RHEL 9.x的内核版本下TIME_WAIT快速回收机制已经变化简单开tcp_tw_reuse可能造成连接混淆我的经验是不依赖它。拥塞控制算法方面默认cubic在绝大多数数据中心内网已经足够。BBR在跨地域、高丢包链路上有优势但在同机房低延迟网络中收益不大还可能改变重传行为。如果一定要试BBR先确认内核有tcp_bbr模块modprobe tcp_bbr sysctl -w net.ipv4.tcp_congestion_controlbbr如果内核模块本身没编译进去这条路就到此为止别折腾。另外RPS/XPS在网卡不支持多队列时可以用但9.7上手动配置/sys/class/net/eth0/queues/rx-0/rps_cpus后要注意它和irqbalance的协作关系两者目标相同但机制不同容易互相干扰。3.3 块设备与XFS挂载选项RHEL 9.7的默认文件系统是XFS新一代内核搭配默认挂载参数已经对SSD优化得不错。我极少改动底层调度器NVMe保持none机械盘保持mq-deadline这是RHEL 9的默认选择没必要再叠加udev规则去改。真正能在挂载层面做的是这几点加noatime,nodiratime避免每次读文件都更新atime减少不必要的写IO。NVMe SSD可以加discardasync在写入时异步执行TRIM减少后台fstrim周期性扫描带来的毛刺。XFS的largeio和allocsize针对特定IO模式有效但需要压测验证别迷信。验证存储优化效果用fio跑一轮基准fio --namerandread --ioenginelibaio --direct1 --bs4k --rwrandread --size4G --numjobs4 --runtime30 --group_reporting重点对比IOPS和延迟分布而不是只看平均值。我在实际项目中见过有人把SSD的调度器从none改成mq-deadline后4K随机读延迟分布明显变宽因为压缩和合并逻辑在NVMe上根本没收益反而增加路径开销。4. 内存、NUMA与进程调度别让数据在CPU之间乱跑4.1 跨NUMA节点的隐形代价现代多路服务器的内存访问不是等价的每个CPU访问本地内存和远端内存的延迟差异可能达到30%以上。RHEL 9.7默认启用了NUMA感知调度但应用本身如果不做任何设置线程还是会比较自由地在各个核之间流动内存也会因为“就近分配”原则散布到多个节点。这个问题的严重性在线服务上会被放大——一次简单的远端内存访问可能在每次数据库查询里反复出现。用numactl --hardware看拓扑重点看节点数量和距离矩阵。然后对关键进程做绑定以Java服务为例numactl --cpunodebind0 --membind0 java -jar app.jar这条命令把进程的CPU和内存都限制在node 0保证线程调度和内存分配都在同一个节点内。用numastat -p pid验证执行效果观察进程在remote node内存占比是否降到5%以下。4.2 THP、脏页回收与swap的联动透明大页在第四章又出现了一次因为内存层面的优化往往是联动关系THP策略决定大页分配方式脏页阈值决定写回频率swappiness决定匿名内存和文件缓存的回收倾向。任何单独调一项都可能打破平衡。RHEL 9.7默认的vm.swappiness是30对在线服务我一般调到10左右。vm.dirty_ratio和vm.dirty_background_ratio默认值在大内存机器上容易导致脏页积累到较高水位后一次性刷盘出现IO毛刺。我通常把background ratio调低到5ratio调低到15让写回更平缓。注意这两个参数必须配合业务写入模式验证纯读场景调它们没有意义。THP的取舍再展开一点Java应用通常维护大堆内存THP导致的耗时波动有时比想象中明显。我在一台PostgreSQL 16主机上做过对比从always改为madvise并重启后pgbench读取延迟的P99从原先偶发几十毫秒尖峰变成了相对平稳的几毫秒级别原因就是后台khugepaged的扫描和拆页不再频繁干扰业务线程。4.3 cgroup v2下的CPU限额与隔离RHEL 9.7默认启用cgroup v2这改变了很多人从cgroup v1继承的习惯。现在CPU配额的表达方式变成cpu.max文件格式是“配额值 period值”。比如限制一个服务最多用2个CPU核写入200000 100000。systemd-run --unitlimit-app --sliceworkload.slice -p CPUQuota200% -p MemoryMax8G ./app这种做法的好处是限制和释放都是动态的不需要重启服务也不需要手动编辑/sys/fs/cgroup下的文件。对于混部场景把在线业务和离线任务放进不同slice再配合memory.high做内存水位预警可以避免离线任务把在线服务的CPU和内存争抢到不可控。4.4 一个真实的NUMA案例还是上面那台PostgreSQL主机升级后让我困惑了两天。pgbench跑同一份基准脚本RHEL 8.10时代读写混合TPS稳定在某个区间升级到9.7后整体低了一截。mpstat看CPU利用率并不高vmstat也没有明显si/so后来用numastat -p一看才发现PostgreSQL主进程有接近45%的内存落在remote node。原因是我们用了systemd启动PG而systemd在9.x的cgroup v2环境下对NUMA亲和性的处理与预期不同。用numactl包裹启动命令之后remote内存占比降到5%TPS回到了升级前的水平。这个案例告诉我升级不只是内核版本号变了关联组件的行为也在变调优必须从实际观测出发。5. 应用侧优化从编译到热点的完整链路5.1 编译器优化别只盯着-O3如果业务里包含自研的C/C模块RHEL 9.7自带的GCC 11够用但想要更激进的优化可以用Software Collection里的gcc-toolset-13或更新的工具链版本安装后启用dnf install gcc-toolset-13 scl enable gcc-toolset-13 bash编译选项上-O2和-O3的区别对不同应用差异很大。计算密集型的代码可以用-O3 -marchx86-64-v3但如果程序要分发到不同代的CPU混跑-marchnative反而是给自己埋雷目标机器缺某个指令集就直接崩。LTO链接时优化和PGOprofile-guided optimization收益通常比无脑加-O3更稳。PGO的基本流程是gcc -O3 -fprofile-generate -o app app.c ./app -i workload_data # 运行代表性负载 gcc -O3 -fprofile-use -o app_final app.c我做过的一个图像处理模块-O3基线加上LTO和PGO之后核心函数耗时减少了大约12%而且没有引入兼容性问题。注意PGO的profile-data依赖输入负载的特征换了一类数据效果可能打折扣。5.2 用perf和bpftrace找到真正的热点调优最忌讳的就是“我觉得这里慢”。RHEL 9.7自带的工具链足够做完整的剖析。先把perf的用户态采样权限放开sysctl -w kernel.perf_event_paranoid1然后对目标进程做CPU热点采样perf record -g -p pid -- sleep 30 perf report如果服务是Java应用perf看到的是JVM的native栈配合-XX:PreserveFramePointer和JIT符号映射会有更好的效果。但快速定位阶段直接用perf top看哪个符号占比最高就够了。除了CPU热点还要关注off-CPU时间——进程在等待什么。bpftrace是4.x内核时代就常用的工具在RHEL 9.7上可以这样统计任务切换时的等待原因bpftrace -e k:finish_task_switch { [comm] count(); }实际使用中我经常组合这两类工具先用perf看CPU上有哪些函数在烧火再用bpftrace看线程到底在什么内核路径上排队。比如一次高延迟排查perf显示cgroup相关的函数占用异常高顺着这个方向才发现是cgroup v2的cpu.max配置导致线程频繁被throttle跟应用代码一点关系都没有。5.3 一个快速的诊断判断顺序面对一个“变慢了”的服务器我通常按照这个顺序走每一步都能排除一个大类看uptime负载与CPU核数的比例判断是过载还是正常。看vmstat 1的r队列、us/sy占比、si/so情况区分CPU忙、内核忙还是内存回收忙。如果sys占用高用perf top看内核热点如果wa高用iostat -x 1看块设备队列。如果一切正常但业务依然慢再考虑跨NUMA访问、锁竞争、网络重传这些深水区。这个顺序帮我避开了一个常见的坑直接在sysctl上做文章结果最后发现是网络重传导致接收窗口收缩业务端的延迟只是表象。6. 优化不是改完就结束全套验证与回归6.1 用PCP搭一套可回溯的监控基线RHEL 9.7自带Performance Co-PilotPCP这是我在优化后必搭的监控层。安装并启动采集dnf install pcp pcp-system-tools systemctl enable --now pmcd pmlogger默认情况下它就在后台持续采集CPU、内存、网络、文件系统等关键指标按时间归档。等下次需要复盘某次变更前后差异时直接用pmchart或pmrep拉历史数据不用临时抱佛脚去找监控。性能优化最怕的是“当时忘了记录”PCP的存在就是为了解决这个痛点。6.2 基准测试要做横向对比优化前和优化后必须用同一套基准脚本、同一批硬件、同一个时间段跑才有可比性。我这里给出一个模板场景指标优化前优化后备注网络TCP单流吞吐量12.1 Gbps12.6 Gbpsnetperf TCP_STREAM网络TCP并发连接建连速率8.4k/s9.1k/swrk压测4K随机读IOPS98k104kfio libaioPostgreSQL读写混合TPS63206940pgbench scale500注意这里的数据只是示例不是给你做预期参考。真正重要的是“优化前后”的差异以及差异是否稳定。一次跑出来的结果可能是噪声至少要三轮取中位数。6.3 变更回滚与长期回归每一次优化都需要能回滚。tuned的profile切换是天然的回滚机制内核命令行参数通过grubby移除sysctl直接恢复默认值。但回滚的前提是有记录我建议每个参数都记录三行信息为什么改、改之前的值、怎么验证。长期观察方面要防止“局部优化导致全局劣化”。比如把vm.swappiness调成0确实短期内减少了swap IO但长时间运行后Java堆内存不再被主动回收最终触发OOM。这种问题在基准测试两小时内看不出来必须让业务跑上几天再回看趋势。我惯用的做法是每周拉一次PCP归档的周报对比CPU idle、内存回收、软中断、IO等待趋势出现异常波动及时回滚到上一版profile。优化的终点从来不是“参数调完的那一刻”而是业务稳定运行一段时间后仍然成立。RHEL 9.7这批机器如今已经在生产环境跑了一阵每个参数的来龙去脉我都写进了团队的runbook下次再遇到类似问题不用重新从内核命令行开始查一遍。希望这套流程能帮你在自己的9.7优化路上少踩几个坑。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑