Linux内核参数优化清单:照着配,性能提升30%
“把这份 sysctl.conf 复制到服务器,性能提升 30%”是运维现场最危险的承诺之一。内核参数改变的是资源分配、排队、回收和失败策略,不会凭空增加 CPU、内存、磁盘 IOPS 或链路带宽。同一个参数,对短连接网关可能有效,对数据库可能制造延迟尖峰;在 128 GB 主机上合理的脏页上限,原样放到 4 GB 虚拟机上可能直接触发长时间写回。
本文保留标题中的“30%”作为问题背景,但不把它当作可交付结果。是否提升、提升多少,只能由同一业务模型下的吞吐、延迟分位数、错误率和资源水位共同证明。真正可复用的不是一张万能清单,而是“建立基线—提出假设—灰度修改—观测副作用—保留或回滚”的工程闭环。
文中的尖括号内容都是占位符:<网卡名>、<服务名>、<监听端口>、<测试目标>、<变更单号> 分别替换为 ip link 查到的接口名、systemd 单元名、实际端口、压测目标以及组织内部变更编号;后文的 <IP>、<端口>、<PID>、<原RX值> 和 <文件> 分别替换为实际地址、端口、进程号、变更前 RX ring 值和配置文件路径。示例以使用 systemd 的现代 Linux 发行版为主;不同内核和发行版可能没有某些参数,必须以本机 uname -r、sysctl 返回值及内核文档为准。
先把优化目标写成可证伪的假设
一次合格的调优至少要回答四个问题:业务瓶颈是什么;哪个内核机制限制了它;修改后看哪些指标;副作用达到什么阈值就回滚。例如“监听队列持续溢出,因此提高应用 backlog 和 net.core.somaxconn 后,连接丢弃增量应下降,同时 CPU、内存和 P99 延迟不得恶化”。只写“提升并发能力”没有可验证性。
建议把验收条件固定为一组相互制约的指标:
| 维度 | 常用指标 | 判断原则 |
|---|---|---|
| 业务 | 吞吐量、成功率、P50/P95/P99 | 不能只看平均响应时间 |
| CPU | 利用率、运行队列、软中断 | 吞吐上涨若伴随软中断打满,余量可能更差 |
| 内存 | MemAvailable、swap、PSI、回收 |
缓存减少不等于泄漏,持续直接回收才值得关注 |
| 网络 | 丢包、重传、listen overflow、队列丢弃 | 区分主机、链路、对端和应用层失败 |
| 存储 | await、队列、吞吐、脏页写回 | 高吞吐不能掩盖延迟长尾 |
先确认运行环境。容器内看到的部分参数属于宿主机或网络命名空间,普通容器即使能读取,也未必有权安全修改。
uname -r
cat /etc/os-release
systemd-detect-virt || true
systemd-detect-virt --container || true
getconf PAGESIZE
nproc
systemd-detect-virt --container 返回成功时,先确认参数到底由 Kubernetes、容器运行时还是宿主机管理。不要在 Pod 启动脚本里反复写宿主机级 sysctl。
建立变更前基线
采样必须覆盖真实负载周期。一次 top 截图不能说明趋势,空闲时跑出的基准也不能代表高峰。下面脚本每 10 秒保存一次关键状态,共采样 30 次;生产使用前把输出目录接入变更单,并确认不会写满磁盘。
#!/usr/bin/env bash
set -euo pipefail
OUT_DIR="/var/tmp/kernel-tuning-baseline-$(date +%Y%m%d-%H%M%S)"
INTERVAL=10
COUNT=30
mkdir -p "${OUT_DIR}"
uname -a >"${OUT_DIR}/uname.txt"
sysctl -a >"${OUT_DIR}/sysctl-before.txt" 2>"${OUT_DIR}/sysctl-errors.txt" || true
for ((i=1; i<=COUNT; i++)); do
stamp="$(date --iso-8601=seconds)"
{
echo "timestamp=${stamp}"
cat /proc/loadavg
cat /proc/meminfo
cat /proc/pressure/cpu
cat /proc/pressure/memory
cat /proc/pressure/io
} >"${OUT_DIR}/sample-${i}.txt"
ss -s >"${OUT_DIR}/ss-${i}.txt"
sleep "${INTERVAL}"
done
echo "baseline_dir=${OUT_DIR}"
脚本中的 PSI 文件要求内核启用 Pressure Stall Information;文件不存在时要从脚本中删除对应行,不能把缺失误判为零压力。some 表示至少一个任务受阻,full 表示所有非空闲任务同时受阻。应关注 avg10/avg60/avg300 的持续趋势,而不是单个瞬时值。
安装了 sysstat 时,用 sar 分解 CPU、内存、换页、块设备和网络错误。采样间隔不要短到显著增加观测开销。
sar -u ALL 1 10
sar -q 1 10
sar -r ALL 1 10
sar -W 1 10
sar -d -p 1 10
sar -n DEV,EDEV,TCP,ETCP 1 10
关注 %iowait 时要谨慎:它表示 CPU 空闲且有未完成 I/O,并不直接等于“磁盘利用率”。磁盘瓶颈应结合 sar -d 的等待、队列和吞吐,以及应用 I/O 延迟确认。
内核网络计数器是定位“队列不够”还是“链路不稳”的关键证据。记录绝对值不够,变更前后应比较增量。
nstat -az > /var/tmp/nstat-before.txt
ip -s link show dev <网卡名>
ss -lntp
ss -s
cat /proc/net/sockstat
cat /proc/net/netstat
TcpExtListenOverflows 与 TcpExtListenDrops 增长,才支持监听队列溢出的判断;TcpRetransSegs 增长只能证明重传,不能单凭它断言本机丢包。还需看接口错误、抓包、交换设备或云网络指标。
搞清楚 sysctl 的生效层级
sysctl -w 修改运行时值,重启后消失;/etc/sysctl.conf 和 sysctl.d 文件用于持久化。使用 systemd 的系统通常由 systemd-sysctl.service 按字典序读取多个目录,后出现的同名键覆盖前值。发行版提供的默认文件可能在 /usr/lib/sysctl.d,本地策略应放在 /etc/sysctl.d,不要直接改包管理器维护的文件。
先查看合并后的顺序和当前来源:
systemd-sysctl --cat-config
systemctl status systemd-sysctl.service --no-pager
grep -RnsE '^[[:space:]]*(vm|net|fs)\.' \
/etc/sysctl.conf /etc/sysctl.d /run/sysctl.d /usr/local/lib/sysctl.d /usr/lib/sysctl.d \
2>/dev/null
如果同一个键在多处出现,必须先消除配置漂移。仅把新值写进某个文件而不知道最后谁覆盖它,会造成“手工验证有效、重启后失效”的假象。
变更前保存当前有效值和配置文件。下面脚本只做备份,不修改参数;<变更单号> 应替换为可追溯标识,且不要包含斜杠。
#!/usr/bin/env bash
set -euo pipefail
CHANGE_ID="<变更单号>"
BACKUP_DIR="/var/backups/sysctl/${CHANGE_ID}-$(date +%Y%m%d-%H%M%S)"
install -d -m 0700 "${BACKUP_DIR}"
sysctl -a >"${BACKUP_DIR}/runtime-sysctl.txt" 2>"${BACKUP_DIR}/read-errors.txt" || true
cp -a /etc/sysctl.conf "${BACKUP_DIR}/" 2>/dev/null || true
cp -a /etc/sysctl.d "${BACKUP_DIR}/sysctl.d" 2>/dev/null || true
systemd-sysctl --cat-config >"${BACKUP_DIR}/merged-config.txt"
printf 'backup_dir=%s\n' "${BACKUP_DIR}"
sysctl -a 在受限容器中可能对部分键报权限错误,因此脚本保留错误清单并继续。备份涉及安全参数,目录权限设为 0700。
内存参数:控制回收与写回,不是“让内存变多”
vm.swappiness:它不是 swap 使用百分比
vm.swappiness 表示内核在匿名页和文件页回收之间的相对倾向,数值范围和语义应以当前内核文档为准。数据库主机常降低它以减少匿名页被换出,但设为 0 也不保证永不 swap;当内存压力严重时,系统仍可能换页或直接 OOM。无 swap 的主机更要关注内存余量和 OOM 风险,不能把“关闭 swap”当作调优万能项。
先用证据确认是否存在持续换入换出与回收停顿:
sysctl vm.swappiness
free -h
swapon --show --bytes
vmstat 1 10
grep -E '^(pswpin|pswpout|pgscan|pgsteal|allocstall|oom_kill) ' /proc/vmstat
cat /proc/pressure/memory
vmstat 的 si、so 持续非零才表示采样期发生换入换出;swap 已使用但 si/so 为零,可能只是历史冷页,不能据此判定当前抖动。allocstall 增长与 memory PSI 上升说明分配路径受回收影响。
脏页:百分比在大内存机器上可能过大
vm.dirty_background_ratio 决定后台写回开始的相对阈值,vm.dirty_ratio 控制产生脏页的进程何时被迫参与写回。对应的 _bytes 参数与 _ratio 互斥:写一个非零值会使另一种表示失效。大内存主机更适合按存储能力计算字节上限,但数值必须由可接受的写回时间反推。
假设存储可持续写入为 B 字节/秒,希望最坏脏数据排空时间不超过 T 秒,那么脏页上限的初始估算不应高于 B × T,还要给业务 I/O 留余量。这只是起点,必须压测验证。
查看当前配置和实时写回:
sysctl vm.dirty_background_bytes vm.dirty_bytes \
vm.dirty_background_ratio vm.dirty_ratio \
vm.dirty_writeback_centisecs vm.dirty_expire_centisecs
grep -E '^(Dirty|Writeback|MemAvailable):' /proc/meminfo
grep -E '^(nr_dirty|nr_writeback|nr_dirtied|nr_written|nr_throttled_written) ' /proc/vmstat
iostat -xz 1 10
iostat 来自 sysstat。设备 %util 在并行设备、RAID、虚拟盘和现代 NVMe 上不能单独代表饱和,应结合 await、队列、吞吐和应用写延迟。
下面给出一个示例候选配置,不是所有服务器的推荐值。它假设已经按存储压测算出 256 MiB 后台阈值和 1 GiB 强制阈值,且主机内存显著大于这些值。写入前先替换文件名中的变更号。
# /etc/sysctl.d/60-<变更单号>-memory.conf
# 使用 bytes 时,明确将 ratio 设为 0,避免含义不清。
vm.dirty_background_bytes = 268435456
vm.dirty_bytes = 1073741824
vm.dirty_background_ratio = 0
vm.dirty_ratio = 0
# 仅在换页证据支持时调整;不是“越低越好”。
vm.swappiness = 10
配置先做解析检查,再按单文件加载。sysctl --system 会重新加载全部配置,可能顺带应用无关改动,因此变更窗口中优先 sysctl -p <文件>。
CONF="/etc/sysctl.d/60-<变更单号>-memory.conf"
sysctl -p "${CONF}" --dry-run
sysctl -p "${CONF}"
sysctl vm.dirty_background_bytes vm.dirty_bytes vm.swappiness
某些较旧版本的 procps-ng sysctl 不支持 --dry-run。先执行 sysctl --help 确认;不支持时,可在同版本测试机验证配置,或逐键核对后在灰度节点加载。不要因为语法检查工具版本差异而直接全量应用。
验证不能只看“值已经写进去”,还要重放同类 I/O 负载,并观察脏页峰值、写入延迟和 PSI:
watch -n 2 '
grep -E "^(Dirty|Writeback|MemAvailable):" /proc/meminfo
grep -E "^(some|full)" /proc/pressure/io
grep -E "^(nr_dirty|nr_writeback|nr_throttled_written) " /proc/vmstat
'
若应用 P99 写延迟、nr_throttled_written 增速或 IO PSI 明显恶化,应恢复备份值,而不是继续加大脏页上限掩盖存储瓶颈。
vm.overcommit_memory:决定承诺策略,不决定实际内存
模式 0 是启发式检查,模式 1 总是允许承诺,模式 2 按严格承诺上限检查;严格模式还受 vm.overcommit_ratio 或 vm.overcommit_kbytes 影响。Redis、科学计算、数据库 fork 快照等工作负载可能有不同要求。修改前要核对应用厂商文档,并区分“虚拟地址承诺失败”与“物理内存不足被 OOM kill”。
sysctl vm.overcommit_memory vm.overcommit_ratio vm.overcommit_kbytes
grep -E '^(CommitLimit|Committed_AS|MemAvailable|SwapTotal|SwapFree):' /proc/meminfo
journalctl -k -g 'Out of memory|oom-kill|Killed process' --since '-24 hours' --no-pager
只有 Committed_AS 与 CommitLimit、应用分配失败日志、OOM 记录能支持对应结论。生产数据库不要凭通用清单切换 overcommit 模式。
Transparent Huge Pages:延迟与吞吐的取舍
THP 可减少页表和 TLB 压力,但内存整理可能造成延迟尖峰。不同发行版路径和可选模式略有差异。先查看当前模式、应用 RSS/匿名大页和内存整理计数:
cat /sys/kernel/mm/transparent_hugepage/enabled
cat /sys/kernel/mm/transparent_hugepage/defrag
grep -E '^(AnonHugePages|ShmemHugePages|FileHugePages):' /proc/meminfo
grep -E '^(thp_|compact_|allocstall)' /proc/vmstat
方括号中的值才是当前模式。THP 的持久化通常通过内核启动参数或专门的 systemd 单元完成,不属于 sysctl;不要把 echo never 塞进 /etc/sysctl.conf。数据库明确要求关闭时,应在灰度节点按其文档实施,并观察 CPU 与延迟是否反向恶化。
网络参数:先找到丢在哪一层
监听队列是多个上限的最小值
TCP 服务的有效 accept 队列受应用 listen(backlog)、net.core.somaxconn 等共同约束;只提高内核值而应用仍请求 128,没有收益。半连接队列又涉及 net.ipv4.tcp_max_syn_backlog。SYN flood 场景优先启用并验证 SYN cookies,不应盲目把队列撑到占用大量内存。
sysctl net.core.somaxconn net.ipv4.tcp_max_syn_backlog net.ipv4.tcp_syncookies
ss -lntp
nstat -az | grep -E 'ListenOverflows|ListenDrops|SyncookiesSent|SyncookiesFailed'
ss -lnt 中监听套接字的 Send-Q 通常反映配置的 backlog 上限,Recv-Q 表示当前排队的未 accept 连接;具体显示语义需结合 iproute2 版本确认。应用线程阻塞、文件描述符耗尽也会让 accept 变慢,不能只调内核队列。
存在持续 overflow 且应用已提高 backlog 时,可以灰度使用如下候选值:
# /etc/sysctl.d/60-<变更单号>-listen.conf
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_syncookies = 1
这些数值不是容量承诺。更大队列会增加排队等待和内存占用,可能把快速失败变成长尾超时。验收时既看 drop 增量,也看排队时间、P99 和进程 accept 速率。
网卡 backlog 与软中断
net.core.netdev_max_backlog 是协议栈来不及处理时的输入包队列上限。只有 softnet_stat 中丢弃或 time squeeze 增长,并且网卡 RX ring、CPU 软中断、RPS/RSS 证据吻合时,才应考虑调整。
ip -s link show dev <网卡名>
ethtool -S <网卡名> | grep -Ei 'drop|miss|error|timeout|buffer'
ethtool -g <网卡名>
awk '{printf "cpu=%d processed=%d dropped=%d time_squeeze=%d\n", NR-1, "0x"$1, "0x"$2, "0x"$3}' \
/proc/net/softnet_stat
mpstat -P ALL 1 10
ethtool -S 的统计项由驱动定义,名称不统一;以实际驱动暴露为准。softnet_stat 是十六进制。某些 awk 实现不支持直接将 0x... 字符串转数值,可改用 strtonum 的 GNU awk,或保留原始十六进制比较增量。
如果 RX ring 在突发流量下溢出,可先查询驱动允许范围,再灰度增加。这个操作会改变网卡运行参数,部分驱动可能短暂影响收包;应在冗余节点逐台执行,并准备恢复原值。
#!/usr/bin/env bash
set -euo pipefail
IFACE="<网卡名>"
NEW_RX=4096
STATE_FILE="/var/tmp/${IFACE}-ring-before.txt"
ethtool -g "${IFACE}" | tee "${STATE_FILE}"
ip link show dev "${IFACE}" >/dev/null
# 确认 Pre-set maximums 支持 NEW_RX 后再执行。
ethtool -G "${IFACE}" rx "${NEW_RX}"
ethtool -g "${IFACE}"
ip -s link show dev "${IFACE}"
这里无法自动从文本安全解析所有驱动的 RX 上限,所以保留了人工检查。回滚时从 STATE_FILE 读取变更前的 Current hardware settings,显式执行 ethtool -G <网卡名> rx <原RX值>,并复核接口丢包增量。不要假设重启一定恢复,因为 NetworkManager 或 udev 规则可能持久化设置。
确认 CPU 能及时消费网络包后,才考虑提高 backlog:
# /etc/sysctl.d/60-<变更单号>-netdev.conf
net.core.netdev_max_backlog = 8192
如果所有软中断集中在单个 CPU,更大的队列只是延后丢包。此时应先检查 IRQ affinity、RSS 队列数和虚拟化网络模型,而不是继续加大 netdev_max_backlog。
Socket 缓冲区:上限不是每个连接的预分配量
net.core.rmem_max、wmem_max 是应用可请求的上限;TCP 自动调优还受 tcp_rmem、tcp_wmem 三元组约束。增大上限有助于高带宽时延积链路,但大量连接都使用大缓冲时会放大内存压力。不要把高 BDP 广域网参数照搬到同机房 RPC。
sysctl net.core.rmem_default net.core.rmem_max \
net.core.wmem_default net.core.wmem_max \
net.ipv4.tcp_rmem net.ipv4.tcp_wmem \
net.ipv4.tcp_moderate_rcvbuf
ss -tmni dst <测试目标>
cat /proc/net/sockstat
ss -m 可观察 socket 内存,-i 显示 TCP 内部信息。<测试目标> 替换为目标 IP;若需要端口过滤,使用 dst <IP> dport = :<端口> 并按本机 ss --help 验证过滤语法。
临时端口与 TIME_WAIT:不要用过时“神参数”
客户端短连接量大时,源 IP、源端口、目的 IP、目的端口构成的四元组可能耗尽。证据包括 EADDRNOTAVAIL 应用日志、临时端口范围、连接状态分布和 NAT/负载均衡设备限制。
sysctl net.ipv4.ip_local_port_range net.ipv4.ip_local_reserved_ports
ss -tan state time-wait | wc -l
ss -tan state established | wc -l
ss -tan state syn-sent | wc -l
journalctl -u <服务名> --since '-30 minutes' --no-pager | \
grep -Ei 'address.*not available|EADDRNOTAVAIL|cannot assign requested address'
扩展端口范围前要避开本机监听端口和组织保留端口。ip_local_reserved_ports 是逗号分隔的累积列表,误覆盖会删除旧保留项。应先读取旧值,合并后再写。更根本的优化通常是连接池、HTTP keep-alive、多源 IP 或减少不必要的短连接。
不要使用已经从新内核移除的 net.ipv4.tcp_tw_recycle;它曾在 NAT 场景造成连接异常。tcp_tw_reuse 也不是清空 TIME_WAIT 的按钮,必须依据当前内核文档、连接方向与时间戳行为评估。任何建议通过 rm 删除 /proc 内容的做法都是错误的。
conntrack:只在经过状态防火墙/NAT 时相关
连接跟踪表接近上限会造成新连接被丢弃,并在内核日志出现 nf_conntrack: table full。未加载 conntrack 的纯路由或普通主机可能不存在相关 sysctl 文件。
if sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max; then
journalctl -k -g 'nf_conntrack.*table full' --since '-24 hours' --no-pager
conntrack -S 2>/dev/null || true
else
echo 'conntrack sysctl is not available on this host'
fi
提高 nf_conntrack_max 会增加内存消耗,不能在日志出现后无限翻倍。应先识别连接来源、超时设置、NAT 架构和攻击流量。容量估算还依赖内核版本、条目扩展和网络命名空间,不能用固定“每条多少字节”作精确承诺。
拥塞控制:BBR 不是所有网络的加速开关
Linux 是否支持 BBR 取决于内核配置和模块。先确认可用算法和当前队列规则:
sysctl net.ipv4.tcp_available_congestion_control
sysctl net.ipv4.tcp_congestion_control
sysctl net.core.default_qdisc
modinfo tcp_bbr 2>/dev/null | head
tc qdisc show
只有目标是受拥塞与 RTT 影响的 TCP 吞吐,且能够做对照压测时,才值得评估 BBR。它不会改善 DNS 慢、应用处理慢、服务器 CPU 满或物理丢包。加载模块和切换算法会影响新建连接的拥塞行为,应先灰度,保留原算法与 qdisc 值,验证公平性、重传、吞吐和延迟后再推广。
文件句柄与 inotify:系统上限、服务上限、进程使用量要一起看
fs.file-max 是系统范围文件句柄上限;进程还受 RLIMIT_NOFILE、systemd LimitNOFILE 和应用自身限制。只提高 fs.file-max,服务的 ulimit -n 不变,仍会报 Too many open files。
sysctl fs.file-max fs.file-nr
systemctl show <服务名> -p LimitNOFILE -p TasksMax
pid="$(systemctl show <服务名> -p MainPID --value)"
cat "/proc/${pid}/limits"
find "/proc/${pid}/fd" -maxdepth 1 -type l 2>/dev/null | wc -l
journalctl -u <服务名> -g 'Too many open files|EMFILE|ENFILE' --since '-24 hours' --no-pager
fs.file-nr 的三个字段在现代内核中通常表示已分配、未使用但已分配、系统最大值,具体解释以当前内核文档为准。进程 FD 持续增长且不回落,优先查泄漏,而不是抬高上限掩盖问题。
若应用确实需要更多 FD,systemd drop-in 与内核上限要配套。修改服务限制通常需要重启服务才能进入新进程;这是有影响操作,必须先确认冗余实例、摘流方案和会话保持,再逐台重启。
# /etc/systemd/system/<服务名>.service.d/limits.conf
[Service]
LimitNOFILE=262144
systemd-analyze verify <服务名>.service
systemctl daemon-reload
systemctl show <服务名> -p LimitNOFILE
# 仅在已摘流且健康实例足够时执行:
systemctl restart <服务名>
systemctl is-active <服务名>
systemctl show 在重启前显示的是单元目标限制,但旧进程的 /proc/<PID>/limits 不会改变。回滚时恢复 drop-in 备份、daemon-reload,再按同样灰度流程重启。
大量文件监控程序还可能触及 inotify 上限。先统计实例和 watch 使用者,再决定是否提高:
sysctl fs.inotify.max_user_instances fs.inotify.max_user_watches fs.inotify.max_queued_events
for fd in /proc/[0-9]*/fd/*; do
target="$(readlink "${fd}" 2>/dev/null || true)"
if [[ "${target}" == anon_inode:inotify ]]; then
printf '%s\n' "${fd}"
fi
done | cut -d/ -f3 | sort | uniq -c | sort -nr | head
此遍历在进程很多的主机上有开销,适合短时诊断,不应高频巡检。队列溢出意味着事件可能丢失;提高上限同时要评估每用户内存消耗和应用是否递归监控了不必要目录。
不建议放进“通用清单”的参数
以下参数经常出现在复制粘贴式文章中,但没有场景证据时不应修改:
vm.drop_caches:写入它会主动回收缓存,适合受控测试,不是日常“释放内存”。频繁执行通常让后续 I/O 更慢。vm.min_free_kbytes:影响低水位和保留页,设得过高浪费内存,过低又可能在高阶分配时失败,应交给内核按内存规模计算,除非有明确碎片和分配证据。net.ipv4.tcp_fin_timeout:只影响特定 FIN-WAIT-2 情况,不能通用解决 TIME_WAIT。net.ipv4.tcp_max_tw_buckets:过小会让系统在压力下提前销毁 TIME_WAIT 状态,可能产生协议层副作用。kernel.pid_max、kernel.threads-max:进程创建失败还可能来自 cgrouppids.max、systemdTasksMax或用户限制。net.ipv4.ip_forward:这是路由功能开关,不是网络性能参数;误开会扩大攻击面,误关会中断网关或容器网络。net.ipv4.conf.*.rp_filter:反向路径过滤涉及多宿主、策略路由和非对称路由,修改可能直接断流,必须结合路由表和安全模型评估。
检查路由和反向路径过滤时,先做只读核对:
ip -br address
ip rule show
ip route show table all
sysctl net.ipv4.conf.all.rp_filter net.ipv4.conf.default.rp_filter
for f in /proc/sys/net/ipv4/conf/*/rp_filter; do printf '%s=%s\n' "${f}" "$(cat "${f}")"; done
如果多条策略路由或非对称入口存在,不能仅凭“安全基线要求设为 1”直接全量执行。应先在同路径灰度主机验证回程,并准备通过带外管理恢复。
一套可审计的实施流程
1. 生成候选文件,不直接改全局配置
把本次变更放在独立文件中,文件名包含排序前缀和变更号。下面脚本检查键是否存在、记录旧值、安装候选文件并只加载该文件。它不会自动重启业务。
#!/usr/bin/env bash
set -euo pipefail
CHANGE_ID="<变更单号>"
SOURCE_FILE="/var/tmp/${CHANGE_ID}-candidate.conf"
TARGET_FILE="/etc/sysctl.d/60-${CHANGE_ID}.conf"
BACKUP_FILE="/var/tmp/${CHANGE_ID}-runtime-before.tsv"
[[ -f "${SOURCE_FILE}" ]] || { echo "missing ${SOURCE_FILE}" >&2; exit 1; }
: >"${BACKUP_FILE}"
while IFS='=' read -r raw_key raw_value; do
key="$(printf '%s' "${raw_key}" | xargs)"
[[ -z "${key}" || "${key}" == \#* ]] && continue
sysctl -n "${key}" >/dev/null
printf '%s\t%s\n' "${key}" "$(sysctl -n "${key}")" >>"${BACKUP_FILE}"
done <"${SOURCE_FILE}"
install -m 0644 "${SOURCE_FILE}" "${TARGET_FILE}"
sysctl -p "${TARGET_FILE}"
echo "applied=${TARGET_FILE} backup=${BACKUP_FILE}"
脚本使用 xargs 去除键两侧空白,因此候选值仍由 sysctl -p 解析。正式执行前应在同发行版测试机运行 shellcheck,并确保候选文件中没有 shell 语法或重复键。
2. 灰度,而不是一次推满
至少选择一台负载和硬件有代表性的实例。先从负载均衡摘流,做无业务验证;再引入少量流量,对照未变更实例。网络与内存参数可能改变失败模式,观察期应覆盖峰值和后台任务周期。
#!/usr/bin/env bash
set -euo pipefail
SERVICE_NAME="<服务名>"
LISTEN_PORT="<监听端口>"
systemctl is-active --quiet "${SERVICE_NAME}"
ss -lnt "sport = :${LISTEN_PORT}" | grep -q LISTEN
systemctl --failed --no-legend
journalctl -u "${SERVICE_NAME}" --since '-10 minutes' -p warning --no-pager
nstat -az | grep -E 'ListenOverflows|ListenDrops|TcpRetransSegs'
cat /proc/pressure/memory
cat /proc/pressure/io
systemctl --failed 可能显示与本服务无关的历史失败,需记录变更前基线做差异比较。端口过滤语法适用于常见 iproute2;旧版本先用 ss --help 验证。
3. 用增量判断,不拿累计值下结论
内核计数器大多从启动后累计。下面脚本在一个观测窗口前后采集 nstat,通过 nstat 自身的零化显示观察当前增量;它不清空内核计数器。
#!/usr/bin/env bash
set -euo pipefail
DURATION=60
OUT_DIR="/var/tmp/net-window-$(date +%Y%m%d-%H%M%S)"
mkdir -p "${OUT_DIR}"
nstat -az >"${OUT_DIR}/start.txt"
nstat >/dev/null
sleep "${DURATION}"
nstat >"${OUT_DIR}/delta.txt"
grep -E 'TcpRetransSegs|ListenOverflows|ListenDrops|TCPSynRetrans|IpInDiscards' \
"${OUT_DIR}/delta.txt" || true
echo "evidence_dir=${OUT_DIR}"
nstat 的历史缓存通常位于当前用户状态目录;为了避免不同会话相互干扰,可用两次 nstat -az 文件做数值差分。生产自动化应实现明确的数值相减并处理计数器回绕与主机重启。
4. 明确回滚触发器
回滚触发器应在变更前确定,例如:P99 连续两个窗口高于基线 20%、错误率超过既定 SLO、memory full PSI 持续上升、重传率或接口丢弃显著恶化。阈值由业务基线决定,不能套用本文数字。
下面脚本按变更时生成的 TSV 恢复运行时值,并移走持久化文件。它是高影响变更的一部分,执行前需确认 BACKUP_FILE 与 TARGET_FILE 对应同一次变更。
#!/usr/bin/env bash
set -euo pipefail
CHANGE_ID="<变更单号>"
BACKUP_FILE="/var/tmp/${CHANGE_ID}-runtime-before.tsv"
TARGET_FILE="/etc/sysctl.d/60-${CHANGE_ID}.conf"
DISABLED_FILE="/var/tmp/$(basename "${TARGET_FILE}").rolled-back"
[[ -s "${BACKUP_FILE}" ]] || { echo "invalid backup" >&2; exit 1; }
while IFS=$'\t' read -r key value; do
sysctl -w "${key}=${value}"
done <"${BACKUP_FILE}"
if [[ -f "${TARGET_FILE}" ]]; then
mv "${TARGET_FILE}" "${DISABLED_FILE}"
fi
echo "rolled_back=true disabled_file=${DISABLED_FILE}"
这里用移动而非删除保留审计证据。回滚后逐键 sysctl -n 验证,再观察业务指标;若参数已触发不可逆的应用行为(例如连接已经失败),恢复数值也不会自动恢复请求,需要应用层重试或摘流处理。
重启一致性验证
运行时成功不代表重启后仍一致。不要为了测试 sysctl 主动重启单机生产节点;应在已有维护窗口或滚动重启中验证。重启前检查冗余、会话、存储复制和带外管理,重启后比较期望文件与运行值。
#!/usr/bin/env bash
set -euo pipefail
CONF="/etc/sysctl.d/60-<变更单号>.conf"
FAILED=0
while IFS='=' read -r raw_key raw_value; do
key="$(printf '%s' "${raw_key}" | xargs)"
expected="$(printf '%s' "${raw_value}" | xargs)"
[[ -z "${key}" || "${key}" == \#* ]] && continue
actual="$(sysctl -n "${key}")"
if [[ "${actual}" != "${expected}" ]]; then
printf 'MISMATCH key=%s expected=%s actual=%s\n' "${key}" "${expected}" "${actual}" >&2
FAILED=1
fi
done <"${CONF}"
exit "${FAILED}"
数组型 sysctl 的空格格式可能被规范化,简单字符串比较会误报;对这类值应拆分字段后比较。脚本适合本文示例中的标量参数。
监控和告警要覆盖副作用
若使用 Prometheus,以下查询可帮助观察主机趋势;指标名以实际 exporter 暴露的指标为准,常见来源是 node_exporter。先在 Prometheus 的指标浏览器确认名称与标签,不能把不存在的指标直接写进告警。
# 内存可用比例
node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes
# 每秒 TCP 重传段数
rate(node_netstat_Tcp_RetransSegs[5m])
# 监听队列溢出增速
rate(node_netstat_TcpExt_ListenOverflows[5m])
# 网络接收丢弃速率,按设备查看
rate(node_network_receive_drop_total{device!~"lo"}[5m])
# 内存 PSI;具体指标名取决于 exporter 版本和采集器配置
rate(node_pressure_memory_waiting_seconds_total[5m])
不要对单个速率设置脱离流量的固定阈值。重传应结合发送段数计算比例,接口 drop 要排除虚拟设备噪声,PSI 要结合业务延迟和持续时间。
可以用 textfile collector 暴露期望值与实际值的漂移,但指标文件必须原子替换,避免采集到半文件:
#!/usr/bin/env bash
set -euo pipefail
CONF="/etc/sysctl.d/60-<变更单号>.conf"
TEXTFILE_DIR="/var/lib/node_exporter/textfile_collector"
TMP_FILE="$(mktemp "${TEXTFILE_DIR}/sysctl_drift.prom.XXXXXX")"
trap 'rm -f "${TMP_FILE}"' EXIT
drift=0
while IFS='=' read -r raw_key raw_value; do
key="$(printf '%s' "${raw_key}" | xargs)"
expected="$(printf '%s' "${raw_value}" | xargs)"
[[ -z "${key}" || "${key}" == \#* ]] && continue
actual="$(sysctl -n "${key}" 2>/dev/null || echo unavailable)"
[[ "${actual}" == "${expected}" ]] || drift=$((drift + 1))
done <"${CONF}"
printf '# HELP node_sysctl_drift_keys Number of managed sysctl keys with drift\n' >"${TMP_FILE}"
printf '# TYPE node_sysctl_drift_keys gauge\n' >>"${TMP_FILE}"
printf 'node_sysctl_drift_keys %d\n' "${drift}" >>"${TMP_FILE}"
chmod 0644 "${TMP_FILE}"
mv "${TMP_FILE}" "${TEXTFILE_DIR}/sysctl_drift.prom"
trap - EXIT
该脚本只暴露漂移数量,避免把参数值作为高基数标签。目录和运行用户应按 node_exporter 的真实配置调整。
三类典型场景的取舍
高并发反向代理
优先证据链是:应用端连接错误或上游超时;ListenOverflows 增长;监听 Recv-Q 接近上限;进程 FD 接近限制;CPU softirq 和接口 drop 情况。只有队列确实溢出,才提高应用 backlog、somaxconn 与可能的 SYN backlog。若 FD 已耗尽,先修复 FD 限制或连接泄漏;若 worker 被上游阻塞,增加队列只会累积等待。
写密集型日志或对象服务
优先证据链是:应用 fsync/写入 P99;块设备 await 和队列;Dirty/Writeback;IO PSI;nr_throttled_written。合理的 bytes 阈值可以让写回更平滑,但它不能突破存储带宽。如果后台阈值太低造成频繁小写回,也要用对照测试证明,而不是直接加到数十 GiB。
内存型数据库
优先证据链是:应用内存模型和厂商要求;MemAvailable;swap in/out;memory PSI;OOM 日志;THP 与内存整理计数。可能涉及 swappiness、overcommit 和 THP,但三者作用完全不同。数据库页缓存、Linux page cache、cgroup 内存限制和宿主机余量必须一起计算。
技术审校清单
正式提交变更前,逐项核对:
- 参数在目标内核上存在,值域与语义已通过本机文档或发行版文档确认。
- 找到了当前值的配置来源,没有被更晚的
sysctl.d文件覆盖。 - 基线覆盖了高峰或可重复压测,包含吞吐、延迟分位、错误率和资源指标。
- 每个候选值都有瓶颈证据,不含与场景无关的“顺手优化”。
- 持久化文件、运行时旧值、应用配置和网卡设置都已备份。
- 语法检查与灰度加载通过,没有用
sysctl --system顺带应用无关配置。 - 高风险网络、重启或网卡操作有摘流、冗余、带外访问和逐台方案。
- 验证看的是计数器增量和业务结果,不是仅确认
sysctl回显。 - 回滚脚本、触发阈值、责任人和观察时间已经写入变更单。
- 重启一致性在维护窗口得到验证,配置管理系统也同步了最终值。
真正可靠的“优化清单”最终会变成一份很短的、只属于当前工作负载的参数差异。没有证据的参数越少,变更越容易解释;能被基线和对照验证的改动,才有资格被称为性能优化。
版权申明:内容来源网络,版权归原创者所有,如有侵权请联系删除
想了解更多干货,可通过下方扫码关注

可扫码添加上智启元官方客服微信👇

17认证网








