香港服务器运行 CentOS 7,如何把 TCP 缓冲区“扯开口子”承载海量并发播放请求
技术教程 2025-09-21 10:17 195


凌晨 00:03,香港荃湾机房的空调风声像海潮。隔壁机柜的 Link 灯开始像圣诞树一样狂闪——某平台的电影首发,HLS 切片从边缘节点被全网“抽空”。我盯着 Zabbix 的连线数从 7 万一路蹿到 18 万,/proc/net/netstat 里 ListenOverflows 开始计数。短信响了三次:播放超时上升、回源 QPS 抖动、SYN backlog overflow。

这一晚,我把 CentOS 7 的 TCP 叠层从底到顶翻了个底朝天。下面是那次值夜实操的“配方”和坑位,希望对你有用。

一、现场环境与目标

硬件/网络(单节点)

  • 机型:Supermicro 1U(X11 系列)
  • CPU:Xeon Silver 4214R(12C/24T)
  • 内存:128 GB DDR4-2666
  • 系统盘:Intel S4610 480 GB(SATA)
  • 内容盘:Samsung PM983 1.92 TB(NVMe,ext4,noatime,nodiratime)
  • 网卡:Mellanox ConnectX-4 Lx 25 GbE(直上交换机,双上联 LACP)
  • BMC 带外:独立管理

软件栈

  • OS:CentOS 7.9(3.10.0-1160 系列内核)
  • Nginx 1.22(编译启用 reuseport 与 TFO,epoll)
  • OpenSSL 1.1.1(TLS 1.3)
  • LVS 四层 + Nginx 七层(本机为边缘/边缘回源混合节点)
  • 监控:Zabbix + nstat + sar + dropwatch

业务特征

  • 主体为 HLS/DASH 静态切片(.ts/fMP4),短连接占比不低(爬虫与弱网)
  • 目标:单机同时承载 ≥ 20 万并发连接,SYN 丢弃为 0,p99 首包 < 120ms,重传率 < 0.3%

二、压测前的“原味”基线(问题画像)

指标(无调优) 数值
并发连接峰值 ~78k
ListenOverflows/ListenDrops 迅速增长
SYN 重传 1.8%
accept queue 周期性满,netstat -s 可见
p99 首包 260–320ms
CPU softirq > 35%(单核热)
网卡 rx_missed_errors 偶发递增

定位要点

  • ss -ltn sport = :80 与 /proc/net/netstat:监听队列溢出
  • sar -n TCP,DEV 1:active/s passive/s 波动大
  • dropwatch/nstat:队列与软中断热点明显
  • 典型瓶颈:监听队列长度、netdev backlog、NIC 队列与中断亲和、TCP 缓冲区上限、进程 fd/nofile 限制

三、先把“门和路”修宽:内核参数总览(CentOS 7)

说明:以下参数在 CentOS 7.9 + 3.10.0-1160 实测合理;不同内核/网卡/业务可在此基础上微调。

执行次序:limits → sysctl → NIC/IRQ → 应用层(nginx/systemd)→ 观测与回滚。

1)文件描述符与进程上限

/etc/security/limits.conf

# 对运行 nginx 的用户(如 nginx)和 root 都放开
root soft nofile 1048576
root hard nofile 1048576
nginx soft nofile 1048576
nginx hard nofile 1048576

/etc/systemd/system/nginx.service.d/override.conf

[Service]
LimitNOFILE=1048576
LimitNPROC=65536
TasksMax=infinity

修改后 systemctl daemon-reload && systemctl restart nginx

2)/etc/sysctl.d/99-tcp-tuning.conf

# —— 队列与 SYN 路径 —— 
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 250000
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_syncookies = 1          # 防 SYN flood,正常高并发也建议开
net.ipv4.tcp_syn_retries = 3
net.ipv4.tcp_synack_retries = 3
net.ipv4.tcp_abort_on_overflow = 0   # 不要因应用层 accept 慢而直接 RST

# —— 端口/连接寿命 —— 
net.ipv4.ip_local_port_range = 10240 65535
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_keepalive_time = 120
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 3

# —— TCP 缓冲窗口(r/wmem)——
net.core.rmem_default = 262144
net.core.wmem_default = 262144
net.core.rmem_max = 67108864           # 64MB
net.core.wmem_max = 67108864
net.ipv4.tcp_rmem = 4096 87380 33554432
net.ipv4.tcp_wmem = 4096 65536 33554432
net.ipv4.tcp_mtu_probing = 1           # 避免路径 MTU 黑洞

# —— 高并发稳定性 —— 
net.ipv4.tcp_timestamps = 1            # RTT 估计更稳;CPU 忙可评估关
net.ipv4.tcp_sack = 1
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_slow_start_after_idle = 0

# —— Fast Open(需要内核与应用均支持)——
net.ipv4.tcp_fastopen = 3              # 1:客户端 2:服务端 3:双端

# —— orphan/tw 观念说明 —— 
# CentOS 7 的 tcp_tw_reuse 主要影响“作为客户端”的 TIME_WAIT 复用,
# 对“服务端接受连接”的 TIME_WAIT 堆积帮助有限,且可能与 NAT/时钟导致怪异。
# 保守起见不启: 
net.ipv4.tcp_tw_reuse = 0

# —— 路由缓存刷新,reload 时方便生效 —— 
net.ipv4.route.flush = 1

应用:

sysctl --system

参数要点

  • r/wmem 与 tcp_{r,w}mem:把上限拉高(不是一味默认放最大),让拥塞控制有伸缩空间,避免长肥管道被 64KB 上限“卡死”。
  • somaxconn / tcp_max_syn_backlog / backlog 三者要匹配,取其最小生效(详见 Nginx 章节)。
  • syncookies 在高并发与轻度攻击环境里是“止血带”,副作用是丢失部分 TCP 扩展,但收益>代价。

四、把“网卡的胃口”调大:NIC & IRQ 实操

1)中断亲和与 RPS/RFS

# 查看队列
ethtool -l eth0
ethtool -S eth0 | egrep 'rx|tx|missed|dropped'

# 打开 RPS:为每个 RX 队列分配 CPU 掩码(示例 16 核:0xFFFF)
for q in /sys/class/net/eth0/queues/rx-*; do
  echo ffff > $q/rps_cpus
  echo 32768 > $q/rps_flow_cnt
done

# 关闭 irqbalance,手动绑核(按队列散列)
systemctl stop irqbalance
# 查询 IRQ 号
grep eth0 /proc/interrupts
# 绑定(示例把不同队列分散到 0..15 号核)
echo 1    > /proc/irq/XX/smp_affinity
echo 2    > /proc/irq/YY/smp_affinity
# ...

2)Ring 缓冲与队列长度

# 放大 ring
ethtool -g eth0
ethtool -G eth0 rx 4096 tx 4096

# 提高 TX 队列长度(短包风暴时很有用)
ip link set dev eth0 txqueuelen 20000

3)GRO/LRO/TSO/GSO

边缘服务器(非路由/转发):gro on、tso on、gso on 通常 收益明显(降低 PPS 压力)。

做转发/防火墙/隧道:谨慎开 LRO(可能影响报文合并与上层可见性)。

ethtool -k eth0
ethtool -K eth0 gro on gso on tso on lro on

4)CPU 频率与内存

# 性能模式
tuned-adm profile throughput-performance
# 或:
cpupower frequency-set -g performance

五、Nginx 层的“接客”能力:监听队列、TFO、复用端口

/etc/nginx/nginx.conf(关键片段)

user  nginx;
worker_processes auto;
worker_rlimit_nofile 1048576;

events {
    use epoll;
    worker_connections 65535;     # 每 worker 可接连接数
    multi_accept on;
}

http {
    sendfile on;
    aio threads;                  # CentOS 7 的 aio threads 实用
    tcp_nodelay on;
    tcp_nopush on;

    keepalive_timeout 15;
    keepalive_requests 1000;
    reset_timedout_connection on;

    open_file_cache max=200000 inactive=30s;
    open_file_cache_valid 60s;
    open_file_cache_min_uses 2;

    # Gzip 对视频无意义,禁用以省 CPU
    gzip off;

    # 边缘监听
    server {
        listen 80 backlog=65535 reuseport fastopen=4096;
        # HTTPS 同理: listen 443 ssl http2 backlog=65535 reuseport fastopen=4096;

        location /hls/ {
            root /data/cdn;
            # 适配 HLS 小文件,避免过度缓存
            sendfile_max_chunk 512k;
            directio 4m;          # 仅对大文件直 I/O,切片小可不配
        }
    }
}

要点解释

  • backlog 受 net.core.somaxconn 限制,最终取两者 较小值。
  • reuseport 让每个 worker 拥有独立监听队列,多核下可显著缓解 accept 竞争与队列溢出。
  • fastopen 服务端需 tcp_fastopen=2/3,客户端支持 TFO 才能走零 RTT SYN 数据路径。
  • aio threads 在 NVMe + 小文件混合场景更稳,避免阻塞 worker。

六、conntrack 与防火墙

如果这台边缘 启用了 netfilter/conntrack(比如做 NAT 或七层回源要做状态跟踪),必须把表开口子:

# 临时查看与设置
sysctl net.netfilter.nf_conntrack_max
sysctl -w net.netfilter.nf_conntrack_max=2097152
# hash buckets(需重启模块才能生效,按内存定)
echo 524288 > /sys/module/nf_conntrack/parameters/hashsize

观测 cat /proc/slabinfo | grep conntrack 与 nstat | grep conntrack,避免因表满掉连接。

无需 conntrack 的纯四层转发,建议 直通 或 raw 表绕过 以减负。

七、观测清单(上线前必须会看)

  • ss -s:TCP: inuse/orphan/tw
  • watch 'cat /proc/net/netstat | egrep "ListenOverflows|ListenDrops"'
  • sar -n TCP,DEV 1:active/s、passive/s、rexmits/s
  • nstat -az:TcpExtSyncookies*、Listen*、TCPFastOpen*
  • netstat -s:TCP 统计全览
  • dropwatch:定位落包点(队列/驱动/协议栈)

八、调优结果(对比数据)

压测方法:wrk -t48 -c200000 -d300s http://edge/hls/seg.ts(多机发压+限速模拟弱网),配合真实客户端回放。

回源通过本地缓存命中率 95%+。

指标 调优前 调优后(稳定 30 分钟)
并发连接峰值 ~78k 212k
ListenOverflows/ListenDrops 持续增长 0
SYN 重传率 1.8% 0.22%
p99 首包 (ms) 260–320 88–112
CPU softirq > 35% 单核热 各核 < 12%,分布均衡
rx_missed_errors 偶发增 0
NIC PPS ~1.3 Mpps 2.7–3.1 Mpps
重传率 1.2% 0.26%

九、容易踩的坑(以及我当晚怎么补)

只改了 somaxconn,没改 Nginx backlog

现象:仍然 ListenOverflows。

解:应用层 backlog、somaxconn、tcp_max_syn_backlog 三者齐头并进。

盲目打开 tcp_tw_reuse 指望解决 TIME_WAIT

解释:CentOS 7 的 tcp_tw_reuse 对主动发起方连接更有效,做服务端被动接入帮助有限;且配合 NAT/时钟可能有副作用。

做法:保持关闭,针对 TIME_WAIT 用 长 keepalive + 复用,并观察端口耗尽。

开了 TFO 但 Nginx 没 fastopen=

现象:nstat 的 TCPFastOpenPassive 一直是 0。

解:同时在 listen 写 fastopen=4096,并确认客户端支持。

GRO/LRO 在转发场景导致抓包看不到原始小包

解:仅在边缘终结连接的机器开;路由/防火墙/隧道关闭 LRO。

IRQ 没绑核,irqbalance 把热点“摇来摇去”

现象:softirq 偶尔飙到单核 90%。

解:停 irqbalance,按 RX 队列散列绑核 + 开 RPS/RFS。

conntrack 表没放大

现象:dmesg 提示 nf_conntrack: table full 丢连接。

解:把 nf_conntrack_max/hashsize 拉高,或业务旁路 conntrack。

默认队列规则 pfifo_fast 在大带宽抖

备注:3.10 并不总有 fq qdisc;若你换了新内核(见下一节),建议 default_qdisc=fq 配合 BBR。

十、(可选)换内核上 BBR:进一步把长肥管道跑满

如果你允许在 CentOS 7 上换 ELRepo kernel-ml(≥ 5.4),可以开启 BBR,把长距离回源/海外链路的吞吐再抻一截。

# 安装新内核(略),重启后:
modprobe tcp_bbr
echo "tcp_bbr" > /etc/modules-load.d/bbr.conf

# /etc/sysctl.d/99-bbr.conf
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr

收益与注意

  • 长 RTT(>80ms)链路下 带宽利用率与收敛速度更好。
  • 需观察 CPU 占用 与网卡硬件分片/分段的配合;对弱网丢包场景往往更友好。
  • 如果业务侧已有复杂中间盒(防火墙/代理),请灰度。

十一、灰度与回滚策略(我用的 SOP)

单机开启 → 观察 30 分钟:Listen*、rexmits/s、p99、CPU、PPS。

同机房小批量(1/8 节点)→ 压测窗对齐内容平台大流量档。

全量推广,保留 sysctl 与 nginx 的 pre/post 文件,回滚只需:

mv /etc/sysctl.d/99-tcp-tuning.conf{,.bak} && sysctl --system
# 恢复 nginx 原 conf,reload

十二、完整操作清单(拷贝即用)

  1. 放开 nofile / systemd 限制(limits.conf + override.conf)
  2. 写入 /etc/sysctl.d/99-tcp-tuning.conf 并 sysctl --system
  3. NIC:扩大 ring、开 GRO/GSO/TSO、设 txqueuelen
  4. IRQ 绑核 + RPS/RFS
  5. Nginx:reuseport、fastopen、backlog=65535、worker_connections
  6. (如用)放大 conntrack 表
  7. 观测与数据记录(ss/netstat/nstat/sar/dropwatch)
  8. 灰度上线,保留回滚点
  9. (可选)新内核 + BBR(default_qdisc=fq)

十三、附:关键命令速查

# 队列溢出观测
watch -n1 'cat /proc/net/netstat | egrep "ListenOverflows|ListenDrops"'

# TCP 概览
ss -s; netstat -s; nstat -az

# 网卡与中断
ethtool -S eth0 | egrep 'rx|tx|drop|miss'
grep eth0 /proc/interrupts

# 性能曲线
sar -n TCP,DEV 1

01:20,ListenOverflows 定格在 0,p99 终于压到 100ms 下方,回源带宽像被梳了头发一样顺滑。屏幕前的我捧着已经凉透的美式,看着机房天花板反光里跳动的 Link 灯,心里那根弦慢慢松了下来。
几百行配置、几次 reload、几次滚动绑核——不是“玄学”,是能复现、可回滚、可量化的工程活。如果你的香港边缘也要扛一次“海啸”,就按这篇文章把 TCP 的“筋骨”一节一节拉开。
当你看到 ListenDrops = 0 的那一秒,你会懂的。