Linux内核参数调优:高并发场景下必须修改的10个配置
高并发故障里最容易被误判的一类,是把连接失败、偶发 502、握手变慢都归结为“机器扛不住了”,然后把网上流传的一大段 sysctl 配置直接写进生产环境。这样做有两个问题:第一,很多参数只影响特定链路,例如出站短连接、监听队列或连接跟踪,和当前瓶颈未必有关;第二,内核阈值提高后,文件描述符、conntrack 表、socket 缓冲区都会消耗内存,错误的容量估计会把轻微拥塞变成 OOM 或全局丢包。
本文以一台运行 Nginx 和 systemd 托管业务进程的 Linux 主机为例,说明高并发场景中最常需要核对和调整的 10 个内核参数。这里的“必须修改”应理解为:当监控、日志和现场检查证明对应资源碰到上限时,必须把该项纳入变更;它不表示所有机器都应照抄同一组数值。
文中的 <服务名>、<配置备份目录>、<压测目标URL> 等尖括号内容是占位符。首次使用前,请替换成当前环境的真实值。命令以 systemd 的 Linux 发行版为例;参数是否存在、默认值及语义会随内核版本变化,先检查,再配置。
先确认:是不是内核阈值,而不是应用、数据库或外部依赖
内核调优只能解决内核层面容量、排队与回收问题。若业务线程已被数据库慢查询、下游 HTTP 超时、GC 停顿或磁盘 I/O 卡住,即使把 somaxconn 调到很大,客户端仍然会慢。变更前至少同时看四组证据:
- 应用与 Nginx 的错误日志:是否有 too many open files、connect() failed、accept4() failed、SYN flooding、nf_conntrack: table full。
- socket 状态与监听队列:是否出现大量 SYN-RECV、TIME-WAIT,或监听 socket 的接收队列持续非零。
- 系统级限制:进程 Max open files、fs.file-max、fs.nr_open、conntrack 使用量是否已接近上限。
- 压测或真实流量下的延迟、错误率与重传率:调参前后必须在同一目标、同一流量模型下比较。
先记录版本与当前值。不要只复制 /etc/sysctl.conf,因为发行版可能在 /usr/lib/sysctl.d、/run/sysctl.d、/etc/sysctl.d 里加载了多个同名参数,后加载的文件会覆盖先加载的文件。
# 代码 1:确认内核、发行版、sysctl 工具与当前加载文件
uname -r
cat /etc/os-release
sysctl --version
systemd-sysctl --cat-config 2>/dev/null || true
systemd-sysctl --cat-config 在使用 systemd 的发行版上可展示合并后的配置来源;若该命令不可用,不要假设加载顺序,改为查阅本机 systemd 文档并逐个检查目录。参数实际生效值仍要以 sysctl 的读取结果为准。
下面的快照脚本只读,不改变系统。它把常见参数、限制和连接概览写入一个带时间戳的目录,便于变更后做差异对比。<配置备份目录> 应放在受控、容量足够且不会被日志清理任务删除的位置。
#!/usr/bin/env bash
# 代码 2:调优前只读基线快照
set -euo pipefail
backup_root="<配置备份目录>"
snapshot_dir="$backup_root/kernel-tuning-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$snapshot_dir"
uname -a > "$snapshot_dir/uname.txt"
sysctl -a 2>/dev/null | sort > "$snapshot_dir/sysctl-before.txt"
cat /proc/sys/fs/file-nr > "$snapshot_dir/file-nr.txt"
cat /proc/net/sockstat > "$snapshot_dir/sockstat.txt"
cat /proc/net/sockstat6 > "$snapshot_dir/sockstat6.txt" 2>/dev/null || true
ss -s > "$snapshot_dir/ss-summary.txt"
ulimit -a > "$snapshot_dir/shell-ulimit.txt"
printf'%s\n'"$snapshot_dir"
脚本中的 sysctl -a 可能输出内核或安全相关信息,不应直接粘贴到公开工单。快照目录应限制访问权限,并随变更记录保存。若系统启用了配置管理,备份还应包含对应仓库提交号,而不是仅保留机器本地副本。
两层文件描述符上限:fs.file-max 与 fs.nr_open
每个 TCP socket、Unix socket、日志文件、缓存文件和被打开的配置文件都需要文件描述符(FD)。高并发 Web 节点首先常遇到的不是 CPU,而是 Nginx worker 或应用进程的 FD 上限。需要区分三层:
- fs.file-max:全系统可分配文件句柄的总量阈值。
- fs.nr_open:单一进程可申请的 RLIMIT_NOFILE 理论上限。
- systemd 的 LimitNOFILE 和应用自身 ulimit -n:某个服务实际继承的软、硬限制。
fs.file-max 高并不等于服务能打开更多 FD;如果 unit 仍是 1024,服务一样会在很低的连接数失败。先查看内核与服务进程现状。
# 代码 3:查看全局文件句柄与指定进程的实际 FD 使用量
SERVICE_NAME="<服务名>"
MAIN_PID="$(systemctl show -p MainPID --value "$SERVICE_NAME")"
cat /proc/sys/fs/file-max
cat /proc/sys/fs/nr_open
cat /proc/sys/fs/file-nr
grep -E 'Max open files'"/proc/$MAIN_PID/limits"
ls"/proc/$MAIN_PID/fd" | wc -l
/proc/sys/fs/file-nr 的三个数字依次是已分配文件句柄、未使用但已分配的句柄和 file-max。它反映全局趋势,不能代替某个服务的限制检查。MainPID 为 0 时说明服务未运行或 unit 类型不直接管理主进程,应先检查 unit,再执行后续排查。
单个 worker 的 FD 数与连接数也不必一一相等:反向代理会同时占用客户端与上游连接,日志、缓存、临时文件也要计数。因此先按进程查看打开对象类型,而不是依据“并发用户数”拍一个固定数值。
# 代码 4:按文件类型观察服务进程打开的对象
SERVICE_NAME="<服务名>"
MAIN_PID="$(systemctl show -p MainPID --value "$SERVICE_NAME")"
sudo lsof -nP -p "$MAIN_PID" \
| awk 'NR==1 || /IPv[46]|unix|REG|FIFO/' \
| sed -n '1,160p'
lsof 可能未安装,且对高频生产现场会有开销;它适合短时间抽样,不应放入秒级循环。若日志中已出现 EMFILE 或 too many open files,先扩大观测范围,确认是单个服务达到 limit 还是全局 file-max 逼近,再进入变更。
# 代码 5:只读检查是否存在文件句柄耗尽证据
sudo journalctl -k --since "-24h" --no-pager \
| rg -i 'file-max|too many open files|EMFILE|ENFILE' || true
sudo journalctl -u "<服务名>" --since "-24h" --no-pager \
| rg -i 'too many open files|EMFILE|ENFILE' || true
内核日志、服务日志和 /proc//limits 三者同时支持,才可以把 FD 上限作为根因候选。仅凭某次连接失败不应直接调整 fs.nr_open;大部分服务首先需要的是正确的 systemd limit,而不是增加内核理论最大值。
三个入站排队参数:somaxconn、tcp_max_syn_backlog、netdev_max_backlog
连接建立经历网卡收包、内核网络队列、SYN 半连接队列和应用 listen() socket 的 accept 队列。它们不是同一个队列:
- net.core.netdev_max_backlog 影响网卡收包来不及被协议栈处理时的 CPU 输入队列上限。
- net.ipv4.tcp_max_syn_backlog 影响尚未完成三次握手的半连接队列。
- net.core.somaxconn 限制应用传给 listen(backlog) 的 backlog 上限。
Nginx 的 listen … backlog=<值> 和应用框架的监听 backlog 也会参与最终结果。只调内核而应用仍请求很小的 backlog,不会得到预期容量;反过来,队列过大只会让用户等待更久,不能替代扩容和慢请求治理。
# 代码 6:查看 TCP 状态、监听队列与接收队列
ss -s
ss -ltn
ss -ant state syn-recv
ss -ant state established
ss -ltn 的 Recv-Q / Send-Q 含义会因 socket 状态不同而变化。对 LISTEN socket,Recv-Q 可以作为已排队未被应用 accept 的连接信号,Send-Q 通常显示 backlog 上限。应在流量高峰连续采样并关联应用处理耗时,单个瞬时数值不足以下结论。
# 代码 7:连续采样监听队列,定位是否长期排队
#!/usr/bin/env bash
set -euo pipefail
listen_port="<监听端口>"
sample_seconds=30
for _ in $(seq 1 "$sample_seconds"); do
date -Is
ss -ltn "sport = :$listen_port"
sleep 1
done
如果接收队列在高峰持续增长,而服务的 CPU、线程池、上游调用耗时也升高,优先处理应用处理能力或下游瓶颈。只有应用还能及时处理新连接、但 listen 队列被其配置或 somaxconn 截断时,提高队列才可能降低握手阶段丢弃。
检查 SYN 洪泛保护前,应先确认是否真的存在半连接堆积或相关内核日志。tcp_syncookies 是保护机制,不是提升正常吞吐的开关;保持启用通常比为了“性能”关闭更安全。
# 代码 8:读取半连接队列、SYN cookie 与软中断统计
sysctl net.core.somaxconn
sysctl net.ipv4.tcp_max_syn_backlog
sysctl net.ipv4.tcp_syncookies
command -v nstat
nstat -az TcpExtListenOverflows TcpExtListenDrops TcpExtSyncookiesSent TcpExtSyncookiesRecv
grep -E 'NET_RX|NET_TX' /proc/softirqs
ListenOverflows、ListenDrops 和 SyncookiesSent 增长需要在同一个观测窗口内判断。nstat 来自 iproute2,若本机未提供,应先按发行版基线安装或读取 /proc/net/netstat 的成对表头和值。SYN cookie 被使用不自动证明受到攻击,也可能表示正常流量的半连接队列不足;同样,增加 backlog 之前要确认负载均衡器、连接重试和应用 accept 速率。
四个连接生命周期参数:临时端口、TIME_WAIT、FIN_WAIT2 与连接跟踪
如果主机是反向代理、爬虫、调用方或 service mesh 出口,它会主动创建大量到上游的连接。此时失败常由临时端口耗尽、短连接过多或 conntrack 表满造成,而不是入站 listen 队列。以下四项分别解决不同资源:
- net.ipv4.ip_local_port_range:本机主动连接可使用的临时源端口范围。
- net.ipv4.tcp_tw_reuse:在内核允许的安全条件下复用 TIME_WAIT socket;现代内核的默认和语义应以本机文档为准。
- net.ipv4.tcp_fin_timeout:仅影响孤儿 FIN-WAIT-2 socket 的超时,不是所有连接的“空闲超时”。
- net.netfilter.nf_conntrack_max:使用 netfilter 连接跟踪时可记录的最大 flow 数。
先看连接状态分布。大量 TIME-WAIT 只说明短连接多,不自动构成故障;需要再结合 EADDRNOTAVAIL、源端口范围以及失败率判断。
# 代码 9:统计常见 TCP 状态与临时端口范围
ss -ant \
| awk 'NR>1 {state[$1]++} END {for (s in state) print s, state[s]}' \
| sort -k2,2nr
sysctl net.ipv4.ip_local_port_range
sudo journalctl -u "<服务名>" --since "-24h" --no-pager \
| rg -i 'EADDRNOTAVAIL|cannot assign requested address' || true
若出站端口不足,先确认应用是否应该启用 HTTP keepalive、连接池或上游复用。盲目扩大端口范围会扩大可用空间,但不能修复每次请求都新建连接的设计问题。端口范围还应避开静态服务端口、NAT 规划和安全策略中依赖的端口。
# 代码 10:定位进程是否在大量创建短连接
SERVICE_NAME="<服务名>"
MAIN_PID="$(systemctl show -p MainPID --value "$SERVICE_NAME")"
sudo ss -antp \
| rg "pid=$MAIN_PID," \
| awk 'NR==1 || $1 == "TIME-WAIT" || $1 == "ESTAB"' \
| sed -n '1,200p'
TIME_WAIT socket 可能已不再关联创建它的进程,因而不能把这份列表当作完整归因。还要检查 Nginx 上游 keepalive、应用 HTTP 客户端连接池与负载均衡器空闲超时是否匹配。对于长连接业务,随意缩短 tcp_fin_timeout 更可能制造异常断连。
nf_conntrack_max 只在使用 conntrack 的路径上关键,例如主机防火墙、容器网络、NAT 或负载均衡转发。裸机纯本地服务未必需要增加它。模块与统计文件在不同内核配置上可能不存在,因此脚本应先探测。
# 代码 11:检查 conntrack 上限、当前使用量与满表证据
if sysctl -n net.netfilter.nf_conntrack_max >/dev/null 2>&1; then
sysctl net.netfilter.nf_conntrack_max
cat /proc/sys/net/netfilter/nf_conntrack_count
sudo conntrack -S 2>/dev/null || true
else
echo"当前内核未暴露 nf_conntrack_max;先确认是否使用了 conntrack。"
fi
sudo journalctl -k --since "-24h" --no-pager \
| rg -i 'nf_conntrack.*table full|conntrack.*full' || true
conntrack 命令来自 conntrack-tools,未安装时不能把“无输出”解释为没有问题。nf_conntrack_count / nf_conntrack_max 连续接近上限,且内核日志有满表记录,才是扩表的直接依据。扩表会增加内存占用,必须同时评估节点剩余内存和防火墙策略。
统一配置这 10 项:先落在单独文件,再灰度生效
下表列出本文的 10 项。它们是容量与保护参数,不是性能魔法。
| 参数 | 解决的问题 | 需要什么证据 | 常见误用 |
|---|
| fs.file-max | 全局 FD 不足 | file-nr 接近上限、内核 ENFILE | 只调全局,不调服务 limit |
| fs.nr_open | 单进程可设置的上限过小 | 需要更高 LimitNOFILE 且被 nr_open 限制 | 无依据地设极大值 |
| net.core.somaxconn | listen backlog 被内核截断 | ListenOverflows、应用 backlog 证据 | 用队列掩盖慢服务 |
| net.ipv4.tcp_max_syn_backlog | 半连接队列太小 | SYN-RECV、ListenDrops | 忽略 SYN 攻击或 LB 重试 |
| net.core.netdev_max_backlog | 收包处理来不及 | 软中断、网卡丢包、CPU 证据 | 忽略 IRQ/RSS/CPU 绑定 |
| net.ipv4.ip_local_port_range | 主动连接端口不足 | EADDRNOTAVAIL 与高短连接 | 与静态端口或策略冲突 |
| net.ipv4.tcp_tw_reuse | 安全条件下复用 TIME_WAIT | 出站短连接、内核版本确认 | 使用已移除的 tcp_tw_recycle |
| net.ipv4.tcp_fin_timeout | 孤儿 FIN-WAIT-2 回收慢 | FIN-WAIT-2 长期积压 | 当作应用读超时 |
| net.netfilter.nf_conntrack_max | 跟踪表耗尽 | count 接近 max、满表日志 | 忽略内存消耗 |
| net.ipv4.tcp_syncookies | SYN 队列溢出保护 | 半连接压力、安全评估 | 为“性能”关闭保护 |
以下文件中的数字只是一个待审核的起点。<预期全局文件句柄>、<预期单进程FD上限>、<连接跟踪上限> 必须由容量估算替换;不要把尖括号原样写入 sysctl 文件。临时端口、TIME_WAIT 和 conntrack 项并非每台 Nginx 节点都需要变更。
# 代码 12:/etc/sysctl.d/90-high-concurrency.conf
# 仅在完成容量评估、灰度和变更审批后写入。
fs.file-max = <预期全局文件句柄>
fs.nr_open = <预期单进程FD上限>
net.core.somaxconn = <监听队列上限>
net.ipv4.tcp_max_syn_backlog = <半连接队列上限>
net.core.netdev_max_backlog = <网卡输入队列上限>
net.ipv4.tcp_syncookies = 1
net.ipv4.ip_local_port_range = <临时端口起始> <临时端口结束>
net.ipv4.tcp_tw_reuse = <按本机内核确认后的值>
net.ipv4.tcp_fin_timeout = <孤儿FIN-WAIT-2超时秒数>
net.netfilter.nf_conntrack_max = <连接跟踪上限>
tcp_tw_reuse 的可用值和默认行为受内核版本影响,尤其不要使用早已移除的 net.ipv4.tcp_tw_recycle。在目标节点运行 sysctl -d net.ipv4.tcp_tw_reuse 或查阅随本机内核安装的文档;若本机不支持某参数,sysctl --system 会报错,应把它从该机器的配置中移除,而不是强行忽略失败。
容量估算不要只从 QPS 推导。反向代理的一次业务请求可能同时占用客户端 socket、上游 socket、日志 FD、缓存 FD;conntrack entry 还会随协议、NAT、IPv4/IPv6 和超时状态变化。下面脚本只帮助收集输入值,不输出“最佳参数”。
#!/usr/bin/env bash
# 代码 13:收集容量估算输入,不自动修改任何参数
set -euo pipefail
SERVICE_NAME="<服务名>"
MAIN_PID="$(systemctl show -p MainPID --value "$SERVICE_NAME")"
printf'cpu_count=%s\n'"$(nproc)"
printf'mem_available_bytes=%s\n'"$(awk '/MemAvailable:/ {print $2 * 1024}' /proc/meminfo)"
printf'system_file_max=%s\n'"$(sysctl -n fs.file-max)"
printf'system_file_used=%s\n'"$(awk '{print $1}' /proc/sys/fs/file-nr)"
printf'service_pid=%s\n'"$MAIN_PID"
grep -E 'Max open files'"/proc/$MAIN_PID/limits"
printf'service_fd_open=%s\n'"$(ls "/proc/$MAIN_PID/fd" | wc -l)"
ss -s
如果业务使用容器,不能只看宿主机。容器的 cgroup 限制、运行时 ulimit、Pod 的 PID/内存限制和 sidecar 的连接数都可能成为更早的瓶颈。容器平台的节点级 sysctl 有准入与安全限制,应通过平台标准流程配置,而不是在任意 Pod 里尝试写 /proc/sys。
变更前置检查:参数存在、数值合法、配置不会覆盖
生产变更中最实用的动作不是立刻 sysctl -p,而是检查文件内容、参数名、数值语义和将被覆盖的来源。下面脚本不写入 /proc/sys,仅验证候选文件中的键是否能被本机识别,并保存相关配置来源。
#!/usr/bin/env bash
# 代码 14:校验候选 sysctl 文件,不使其生效
set -euo pipefail
candidate_file="<候选sysctl文件>"
test -r "$candidate_file"
while IFS= read -r line; do
case"$line"in
""|\#*) continue ;;
esac
key="$(printf '%s\n' "$line" | cut -d= -f1 | tr -d '[:space:]')"
if ! sysctl -n "$key" >/dev/null 2>&1; then
printf'未知或本机不可用参数:%s\n'"$key" >&2
exit 1
fi
done < "$candidate_file"
systemd-sysctl --cat-config 2>/dev/null || true
printf'候选文件已通过本机参数名检查:%s\n'"$candidate_file"
这个脚本不检查每个数值是否适合业务,只验证键名可读取。sysctl -n 可读并不表示可安全修改;例如临时端口范围必须满足起始值小于结束值,并与保留端口、服务端口和网络策略共同评估。
如需让 Nginx 或业务进程获得更高 FD 限制,应把 systemd unit drop-in 与 sysctl 一起作为一次变更管理。先备份现有 unit,再创建 drop-in,最后 daemon-reload。更改 unit limit 会影响后续重启的服务进程;滚动场景应先从一个可摘流节点开始。
# 代码 15:/etc/systemd/system/<服务名>.service.d/limits.conf
[Service]
# 值必须不高于 fs.nr_open,且经过服务连接模型评估。
LimitNOFILE=<预期单进程FD上限>
# 代码 16:备份、加载 unit drop-in,并检查服务继承的限制
set -euo pipefail
SERVICE_NAME="<服务名>"
backup_dir="<配置备份目录>/systemd-$SERVICE_NAME-$(date +%Y%m%d-%H%M%S)"
sudomkdir -p "$backup_dir"
sudo systemctl cat"$SERVICE_NAME" > "$backup_dir/unit-before.txt"
sudo systemctl daemon-reload
systemctl show "$SERVICE_NAME" -p LimitNOFILE
systemctl show 显示 unit 的计划配置;服务已运行时,进程并不会自动拿到新限制。需要结合业务发布或受控重启窗口滚动重启,并通过 /proc//limits 验证新进程。绝不能因为更新 limit 就对所有节点同时重启。
灰度生效、压测验证与观测
将候选文件写入单独的 /etc/sysctl.d/ 文件后,使用 sysctl --system 让 systemd 的加载规则统一处理。不要用 sysctl -p /etc/sysctl.conf 代替,因为那会遗漏 sysctl.d 中已有的分层配置,也无法发现后续覆盖问题。
# 代码 17:保存现有文件并在单台灰度节点加载 sysctl 配置
set -euo pipefail
config_file="/etc/sysctl.d/90-high-concurrency.conf"
backup_dir="<配置备份目录>/sysctl-$(date +%Y%m%d-%H%M%S)"
sudomkdir -p "$backup_dir"
if [ -e "$config_file" ]; then
sudocp -a "$config_file""$backup_dir/"
fi
sudo install -m 0644 "<已审核候选文件>""$config_file"
sudo sysctl --system
sysctl --system 会加载多个目录中的配置并可能打印无关参数。出现任何候选参数错误时,应停止把它当作“部分成功”,恢复灰度节点的原配置并复核。执行前后应明确记录本机内核版本、候选文件 checksum 和变更时间。
验证不能只看命令退出码。需要用与业务最接近的低风险流量,比较错误率、P95/P99、握手失败、监听队列、FD 余量与 conntrack 使用量。压测工具是否可用先检查;不要在未经批准的生产入口上施加超出容量的流量。
# 代码 18:检查压测工具并在已批准的目标上做受控验证
command -v wrk
wrk --version
wrk -t <线程数> -c <并发连接数> -d <持续时间> \
--latency "<压测目标URL>"
wrk 不是所有发行版默认安装,且它的客户端 CPU、端口范围和网络链路都可能先成为限制。压测应在隔离环境或经批准的灰度路径运行,设置明确的最大并发、持续时间和中止阈值。若压测结果变差,先停止增加参数,收集系统快照再回滚。
以下观测命令用于验证数据面,不会修改配置。将它们与应用错误率、Nginx 状态、上游延迟和主机指标放在同一时间线中,才能区分“队列更大了”和“请求真正成功了”。
# 代码 19:观察 TCP 重传、队列溢出与 socket 使用情况
watch -n 2 '
date -Is
ss -s
nstat -az TcpExtListenOverflows TcpExtListenDrops TcpExtTCPTimeouts TcpExtTCPSynRetrans
cat /proc/net/sockstat
'
watch 适合人工短时观察,不宜长期运行在生产节点。nstat 中的计数为累计值,应该观察增量而不是绝对值。若调参后 ListenDrops 降低但 P99 延迟显著上升,说明请求可能只是排队更久,仍需处理应用吞吐或扩容。
对于 conntrack 节点,再额外记录使用率。如果上限增加后 count 仍持续逼近新阈值,应查明异常连接来源、超时策略或攻击面;不断扩表只会延后下一次满表。
# 代码 20:每 5 秒采样 conntrack 使用率
whiletrue; do
max="$(sysctl -n net.netfilter.nf_conntrack_max 2>/dev/null || true)"
count="$(cat /proc/sys/net/netfilter/nf_conntrack_count 2>/dev/null || true)"
if [ -n "$max" ] && [ -n "$count" ] && [ "$max" -gt 0 ]; then
awk -v c="$count" -v m="$max"'BEGIN {printf "%s count=%d max=%d usage=%.2f%%\n", strftime(), c, m, c * 100 / m}'
else
echo"$(date -Is) conntrack 指标不可用"
fi
sleep 5
done
典型误区与可审计的回滚
有四类配置应明确避免:
- 不要使用 tcp_tw_recycle。该参数已从较新 Linux 内核移除,并且过去会在 NAT、移动网络等场景引发连接异常。
- 不要把 tcp_fin_timeout 当作业务读写超时。它针对的是孤儿 FIN-WAIT-2 socket,业务超时应在客户端、代理和服务端分别配置。
- 不要只提高 somaxconn。应用的 listen() backlog、worker 数、CPU、上游依赖和负载均衡重试都会决定是否真正改善。
- 不要把 nf_conntrack_max 无限制提高。每个 entry 占用内存,节点内存紧张时可能引发更严重的回收与抖动。
变更前生成的快照可以用于确认差异。下面命令比较部分关键参数,便于复核“哪些配置已生效”,但不判断它们是否业务正确。
# 代码 21:导出关键参数并与变更前快照比较
current_file="<配置备份目录>/sysctl-after.txt"
before_file="<配置备份目录>/kernel-tuning-<时间戳>/sysctl-before.txt"
sysctl \
fs.file-max fs.nr_open \
net.core.somaxconn net.core.netdev_max_backlog \
net.ipv4.tcp_max_syn_backlog net.ipv4.tcp_syncookies \
net.ipv4.ip_local_port_range net.ipv4.tcp_tw_reuse \
net.ipv4.tcp_fin_timeout net.netfilter.nf_conntrack_max \
| sort > "$current_file"
grep -E '^(fs.file-max|fs.nr_open|net.core.somaxconn|net.core.netdev_max_backlog|net.ipv4.tcp_max_syn_backlog|net.ipv4.tcp_syncookies|net.ipv4.ip_local_port_range|net.ipv4.tcp_tw_reuse|net.ipv4.tcp_fin_timeout|net.netfilter.nf_conntrack_max)'"$before_file" \
| sort > "$current_file.before"
diff -u "$current_file.before""$current_file" || true
回滚必须恢复配置文件和服务限制两部分,并在单台节点验证。删除 /etc/sysctl.d/90-high-concurrency.conf 属于修改系统配置的操作,执行前确认该文件确实由本次变更创建,而不是被配置管理或其他团队复用。
#!/usr/bin/env bash
# 代码 22:回滚单台灰度节点的 sysctl 文件
set -euo pipefail
config_file="/etc/sysctl.d/90-high-concurrency.conf"
backup_file="<配置备份目录>/sysctl-<时间戳>/90-high-concurrency.conf"
if [ -f "$backup_file" ]; then
sudo install -m 0644 "$backup_file""$config_file"
else
sudorm -f "$config_file"
fi
sudo sysctl --system
sysctl fs.file-max fs.nr_open net.core.somaxconn net.ipv4.tcp_max_syn_backlog
rm -f 仅适用于经确认由本次变更新建的精确文件路径;不要对 /etc/sysctl.d 使用递归删除。若同时修改了 systemd drop-in,还要恢复备份的 limits.conf,执行 systemctl daemon-reload,并按原滚动策略重启受影响服务以让进程继承旧 limit。
# 代码 23:回滚服务的 systemd 限制并验证新进程
set -euo pipefail
SERVICE_NAME="<服务名>"
dropin_file="/etc/systemd/system/$SERVICE_NAME.service.d/limits.conf"
backup_file="<配置备份目录>/systemd-<服务名>-<时间戳>/limits.conf"
if [ -f "$backup_file" ]; then
sudo install -m 0644 "$backup_file""$dropin_file"
else
sudorm -f "$dropin_file"
fi
sudo systemctl daemon-reload
systemctl show "$SERVICE_NAME" -p LimitNOFILE
最后,回滚后也要以业务指标验证,而不是只看 sysctl --system 无报错。错误率、连接建立失败、队列溢出、FD 余量、重传和 P99 应回到变更前的基线或已知正常范围;如果没有回归,根因大概率不在这组内核阈值中,应转向应用、网络设备、DNS、上游服务或存储链路继续取证。
# 代码 24:变更或回滚后的最小核验清单
SERVICE_NAME="<服务名>"
MAIN_PID="$(systemctl show -p MainPID --value "$SERVICE_NAME")"
systemctl is-active --quiet "$SERVICE_NAME"
grep -E 'Max open files'"/proc/$MAIN_PID/limits"
ss -ltn
cat /proc/sys/fs/file-nr
sysctl net.core.somaxconn net.ipv4.tcp_max_syn_backlog net.ipv4.tcp_syncookies
curl --fail --silent --show-error "<健康检查URL>" > /dev/null
这份清单只验证服务存活、限制生效和一个健康检查端点,不代表已完成容量验收。真正的验收结论必须保留调参前后同口径的流量、延迟、错误率、内核计数器和回滚演练记录。把参数、证据、执行人和恢复步骤一起纳入配置管理,才能让下一次高并发故障不再依赖临场“调大试试”。