Linux 服务器内核调优实战:从系统瓶颈到高性能运行的参数优化路径

cover

一、默认内核参数的生产隐患:那些被忽视的系统瓶颈

一台 32 核 64GB 内存的服务器,运行 Java 微服务集群。某次大促期间,服务响应延迟飙升,排查发现并非应用层问题,而是内核参数未优化:TCP 连接队列溢出导致大量 SYN 被丢弃,文件描述符耗尽导致新连接无法建立,SWAP 频繁换页导致 GC 停顿加剧。这些问题在低负载时不明显,高并发下瞬间暴露。

Linux 内核默认参数面向通用场景,不适合高并发服务器。本文将从网络、文件系统、内存三个维度,给出生产级内核调优方案。

二、Linux 内核资源管理机制与调优原理

内核调优的本质是调整资源分配策略,让系统在高负载下依然保持稳定。理解底层机制才能避免盲目调参。

flowchart TD
    subgraph 网络栈
        N1[客户端 SYN] --> N2[SYN 队列<br/>net.ipv4.tcp_max_syn_backlog]
        N2 --> N3[ACCEPT 队列<br/>net.core.somaxconn]
        N3 --> N4[应用层 accept]
        N2 -->|队列满| N5[SYN 丢弃<br/>触发重传]
        N3 -->|队列满| N6[连接被拒]
    end

    subgraph 文件描述符
        F1[系统级限制<br/>fs.file-max]
        F2[用户级限制<br/>nofile (limits.conf)]
        F3[进程级限制<br/>RLIMIT_NOFILE]
        F1 --> F2 --> F3
    end

    subgraph 内存管理
        M1[物理内存] --> M2[Page Cache<br/>文件缓存]
        M1 --> M3[SWAP 空间<br/>换页缓冲]
        M2 -->|swappiness 高| M3
        M3 -->|换页| M4[GC 停顿<br/>IO 等待]
    end

关键机制解析:

  • TCP 连接队列:Linux TCP 连接建立分两阶段。SYN 队列存储半连接(收到 SYN,未完成三次握手),ACCEPT 队列存储全连接(已完成三次握手,等待应用 accept)。两个队列满都会导致连接失败。
  • 文件描述符:Linux 中一切皆文件,网络连接也占用文件描述符。限制分三级:系统级(fs.file-max)、用户级(nofile)、进程级(RLIMIT_NOFILE)。必须三级都调大才有效。
  • SWAP 与 swappiness:swappiness 控制内核将内存页换出到 SWAP 的倾向。值越高越积极换页。对 Java 应用来说,SWAP 换页会导致 GC 停顿从毫秒级飙升到秒级,必须尽量禁用。

三、Linux 内核调优的生产级配置

3.1 网络参数优化

# /etc/sysctl.d/99-production.conf
# 网络参数优化 - 高并发 TCP 服务器

# === 连接队列 ===
# SYN 队列最大长度(半连接队列)
net.ipv4.tcp_max_syn_backlog = 65535
# ACCEPT 队列最大长度(全连接队列)
net.core.somaxconn = 65535
# 每个端口的 SYN 队列长度(覆盖 somaxconn)
net.ipv4.tcp_syncookies = 1          # 启用 SYN Cookie 防御 SYN Flood

# === TCP 缓冲区 ===
# TCP 读写缓冲区最小/默认/最大值(字节)
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
# Socket 读写缓冲区默认值
net.core.rmem_default = 262144
net.core.wmem_default = 262144
# Socket 读写缓冲区最大值
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216

# === TCP 超时与回收 ===
# FIN_WAIT-2 状态超时时间(秒),默认 60s 太长
net.ipv4.tcp_fin_timeout = 15
# TIME_WAIT 状态超时时间(秒),默认 60s
# 注意:此参数在 Linux 4.12+ 中已废弃,改用 tcp_tw_reuse
net.ipv4.tcp_tw_reuse = 1           # 允许复用 TIME_WAIT 连接
# 系统最大 TIME_WAIT 套接字数
net.ipv4.tcp_max_tw_buckets = 65535

# === TCP Keepalive ===
# TCP Keepalive 探测间隔(秒)
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5

# === 连接跟踪(Conntrack)===
# 连接跟踪表最大条目数(高并发必须调大)
net.netfilter.nf_conntrack_max = 1048576
# 连接跟踪超时(秒)
net.netfilter.nf_conntrack_tcp_timeout_established = 7200

# === 本地端口范围 ===
# 临时端口范围(用于出站连接)
net.ipv4.ip_local_port_range = 1024 65535

# === 网卡队列 ===
# 网卡接收队列最大长度
net.core.netdev_max_backlog = 65536
# 网卡发送队列长度
net.core.dev_weight = 64

3.2 文件系统与内存参数优化

# /etc/sysctl.d/99-production.conf(续)

# === 文件描述符 ===
# 系统级最大文件描述符数
fs.file-max = 2097152
# Inotify 最大实例数(日志采集场景需要)
fs.inotify.max_user_instances = 8192
fs.inotify.max_user_watches = 524288

# === 内存管理 ===
# SWAP 倾向:0 = 尽量不用 SWAP,100 = 积极使用 SWAP
# Java/数据库服务器建议设为 1-10
vm.swappiness = 1
# 脏页回写阈值(百分比)
# 脏页占比超过此值触发后台回写
vm.dirty_background_ratio = 5
# 脏页占比超过此值触发同步回写(阻塞写入)
vm.dirty_ratio = 10
# 脏页最大存活时间(百分之一秒)
vm.dirty_expire_centisecs = 3000

# === 内存过量分配策略 ===
# 0 = 不允许过量分配,1 = 允许过量分配,2 = 严格限制
# 数据库服务器建议设为 2,应用服务器建议设为 0
vm.overcommit_memory = 0

# === 大页内存(HugePages)===
# 适用于数据库和 JVM,减少 TLB Miss
# 预留 8GB 大页(每页 2MB,共 4096 页)
vm.nr_hugepages = 4096

# === 内核信号量 ===
# 信号量数组数 | 每数组信号量数 | 信号量总数 | | 解除信号量限制
kernel.sem = 250 32000 100 128

3.3 用户级资源限制

# /etc/security/limits.d/99-production.conf
# 用户级资源限制配置

# 文件描述符软硬限制
* soft nofile 1048576
* hard nofile 1048576

# 进程数软硬限制
* soft nproc 65535
* hard nproc 65535

# 核心转储文件大小(unlimited = 不限制)
* soft core unlimited
* hard core unlimited

# 内存锁定限制(用于 HugePages 场景)
* soft memlock unlimited
* hard memlock unlimited

# Stack 大小
* soft stack 8192
* hard stack 8192

3.4 系统调优验证脚本

#!/bin/bash
# sysctl-check.sh - 内核参数验证脚本
# 在调优前后运行,对比参数值与推荐值

set -euo pipefail

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

# 推荐参数值
declare -A RECOMMENDED=(
    ["net.core.somaxconn"]="65535"
    ["net.ipv4.tcp_max_syn_backlog"]="65535"
    ["net.ipv4.tcp_tw_reuse"]="1"
    ["net.ipv4.tcp_fin_timeout"]="15"
    ["net.ipv4.ip_local_port_range"]="1024 65535"
    ["vm.swappiness"]="1"
    ["fs.file-max"]="2097152"
    ["net.netfilter.nf_conntrack_max"]="1048576"
    ["vm.dirty_ratio"]="10"
    ["vm.dirty_background_ratio"]="5"
)

echo "========================================"
echo "  Linux 内核参数验证报告"
echo "========================================"
echo ""

PASS=0
WARN=0
FAIL=0

for param in "${!RECOMMENDED[@]}"; do
    current=$(sysctl -n "$param" 2>/dev/null || echo "NOT_FOUND")
    recommended="${RECOMMENDED[$param]}"

    if [[ "$current" == "NOT_FOUND" ]]; then
        echo -e "${YELLOW}[WARN]${NC} $param - 参数不存在"
        ((WARN++))
    elif [[ "$current" == "$recommended" ]]; then
        echo -e "${GREEN}[PASS]${NC} $param = $current"
        ((PASS++))
    else
        echo -e "${RED}[FAIL]${NC} $param = $current (推荐: $recommended)"
        ((FAIL++))
    fi
done

echo ""
echo "========================================"
echo "  验证结果: PASS=$PASS WARN=$WARN FAIL=$FAIL"
echo "========================================"

# 额外检查:文件描述符限制
echo ""
echo "--- 文件描述符限制 ---"
echo "系统级: $(sysctl -n fs.file-max)"
echo "用户级 (soft): $(ulimit -Sn)"
echo "用户级 (hard): $(ulimit -Hn)"

# 额外检查:当前连接跟踪使用率
if sysctl -n net.netfilter.nf_conntrack_count &>/dev/null; then
    current=$(sysctl -n net.netfilter.nf_conntrack_count)
    max=$(sysctl -n net.netfilter.nf_conntrack_max)
    usage=$(echo "scale=2; $current * 100 / $max" | bc)
    echo ""
    echo "--- 连接跟踪使用率 ---"
    echo "当前: $current / 最大: $max (${usage}%)"
    if (( $(echo "$usage > 80" | bc -l) )); then
        echo -e "${RED}[ALERT]${NC} 连接跟踪使用率超过 80%"
    fi
fi

3.5 网卡中断亲和性优化

#!/bin/bash
# irq-affinity.sh - 网卡中断亲和性优化
# 将网卡中断分散到不同 CPU 核心,避免单核瓶颈

INTERFACE="eth0"

# 获取网卡队列数
QUEUE_COUNT=$(ethtool -l "$INTERFACE" 2>/dev/null | grep -A1 "Combined" | tail -1 | awk '{print $2}')
if [[ -z "$QUEUE_COUNT" || "$QUEUE_COUNT" -eq 0 ]]; then
    echo "无法获取网卡队列数,使用默认值 4"
    QUEUE_COUNT=4
fi

# 设置网卡队列数(需要网卡支持)
ethtool -L "$INTERFACE" combined "$QUEUE_COUNT" 2>/dev/null || true

echo "网卡 $INTERFACE 队列数: $QUEUE_COUNT"

# 获取 CPU 核心数
CPU_COUNT=$(nproc)
echo "CPU 核心数: $CPU_COUNT"

# 设置中断亲和性
for ((i=0; i<QUEUE_COUNT; i++)); do
    # 计算目标 CPU 核心(分散到不同核心)
    TARGET_CPU=$((i % CPU_COUNT))
    # 计算 CPU 亲和性掩码
    CPU_MASK=$((1 << TARGET_CPU))

    # 查找对应的中断号
    IRQS=$(grep "$INTERFACE-TxRx-$i" /proc/interrupts 2>/dev/null | awk -F: '{print $1}' | tr -d ' ')
    if [[ -z "$IRQS" ]]; then
        IRQS=$(grep "$INTERFACE-$i" /proc/interrupts 2>/dev/null | awk -F: '{print $1}' | tr -d ' ')
    fi

    for irq in $IRQS; do
        printf "%x" "$CPU_MASK" > "/proc/irq/$irq/smp_affinity" 2>/dev/null
        echo "队列 $i (IRQ $irq) → CPU $TARGET_CPU"
    done
done

echo "中断亲和性配置完成"

四、内核调优的风险与适用边界

SWAP 禁用的 OOM 风险:将 swappiness 设为 0 或 1 可以避免 SWAP 换页,但当内存真正耗尽时,内核会触发 OOM Killer 直接杀进程,而不是换页延缓。建议配合监控,在内存使用率超过 85% 时提前告警,预留扩容时间。

TCP 参数的兼容性:tcp_tw_reuse=1 允许复用 TIME_WAIT 连接,但在 NAT 环境下可能导致连接混乱。如果服务器在 NAT 后面或使用短连接大量访问外部服务,需要谨慎启用。

HugePages 的内存锁定:HugePages 在启动时预分配,分配后不可释放给其他用途。如果分配过多,会导致普通内存不足。建议根据实际需求计算,预留 10% 的普通内存余量。

文件描述符的三级限制:只调大 fs.file-max 不够,用户级(limits.conf)和进程级(容器内 ulimit)也必须同步调整。K8s 环境中,容器的 ulimit 继承自 docker daemon 配置,需要在 containerd/docker 配置中统一设置。

中断亲和性与 CPU 热点:将网卡中断绑定到特定 CPU 可以减少缓存抖动,但如果绑定的 CPU 还运行其他高负载任务,反而会造成 CPU 热点。建议将网卡中断绑定到隔离的 CPU 核心(通过 isolcpus 参数隔离)。

五、总结

Linux 内核调优是高并发服务器的必修课,核心原则是"让内核为你的场景服务,而不是为通用场景服务"。三个优化维度按影响排序:网络参数 > 内存参数 > 文件系统参数。网络参数直接影响连接处理能力,内存参数影响 GC 和 IO 性能,文件系统参数影响连接数上限。

落地路线建议:第一步,运行 sysctl-check.sh 评估当前参数与推荐值的差距;第二步,按优先级调整网络参数,重点关注连接队列和 TIME_WAIT 回收;第三步,调整内存参数,禁用 SWAP、优化脏页回写;第四步,调整文件描述符限制,确保三级限制一致;第五步,优化网卡中断亲和性,分散中断处理负载。每次调优后都要进行压力测试,验证效果并观察是否有副作用。调优不是一次性工作,随着业务增长需要定期复评。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐