上一篇 下一篇 分享链接 返回 返回顶部

如何在香港服务器的CentOS 7环境中通过调整内核参数优化epoll性能,支撑百万级并发连接?

发布人:Minchunlin 发布时间:2025-08-16 09:33 阅读量:765


几个月前,我在香港某机房部署了一组用于实时消息推送的业务集群。那几天机房里空调的风声混着硬盘阵列的噪音,耳边一直响着网络监控的报警声。我们需要支撑百万级长连接,而这批业务服务器跑在CentOS 7上,I/O 调度和 epoll 处理成了最大的瓶颈。

当时的情况是:前端接入层不断报延迟,客户端掉线重连频繁,业务端口的 SYN_RECV 状态堆积,系统负载飙升到 30+。我一边看着 top,一边直觉告诉我这是内核参数和 epoll 调度策略的问题。于是我开启了那几天的“硬核调优模式”。

硬件与系统环境

  • 机房位置:香港沙田电信机房
  • 服务器型号:Dell PowerEdge R740
  • CPU:Intel Xeon Gold 6338 × 2 (64 vCPU)
  • 内存:512 GB DDR4 ECC
  • 存储:NVMe SSD(三星 PM1733,2TB × 2,RAID1)
  • 网卡:Mellanox ConnectX-5 25GbE × 2
  • 系统版本:CentOS Linux release 7.9.2009 (Core)
  • 内核版本:3.10.0-1160.el7.x86_64

这套硬件理论上足够,但默认的 CentOS 7 内核参数远远不适合百万连接场景。

调优目标

  • 提高文件描述符上限,避免 epoll EMFILE 报错。
  • 优化 TCP 参数,降低 TIME_WAIT 和队列堆积。
  • 调整 epoll 等待策略,减少上下文切换。
  • 内核级别参数优化,提升百万连接时的稳定性。

内核参数优化过程

1. 文件描述符限制

默认的 nofile 太小,百万连接至少需要上千万 FD。

cat >> /etc/security/limits.conf <<EOF
* soft nofile 1048576
* hard nofile 1048576
EOF

ulimit -n 1048576

验证:

cat /proc/sys/fs/file-max
# 输出 > 2097152 表示可支持

2. sysctl.conf 调整

我当时用表格记录了关键参数和调整前后的差异:

参数名 默认值 调整值 作用
net.core.somaxconn 128 65535 提高 listen 队列长度
net.ipv4.tcp_max_syn_backlog 2048 65535 提升半连接队列容量
net.ipv4.ip_local_port_range 32768–61000 1024–65535 扩大可用端口范围
net.ipv4.tcp_tw_reuse 0 1 允许复用 TIME_WAIT 连接
net.ipv4.tcp_fin_timeout 60 15 缩短 FIN-WAIT-2 时间
net.core.netdev_max_backlog 1000 65535 增加网卡接收队列缓冲
net.core.rmem_max 212992 16777216 增大 socket 接收缓冲
net.core.wmem_max 212992 16777216 增大 socket 发送缓冲

配置写入 /etc/sysctl.conf:

cat >> /etc/sysctl.conf <<EOF
fs.file-max = 2097152
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
net.core.netdev_max_backlog = 65535
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
EOF

sysctl -p

3. epoll 优化

应用层在 epoll 使用中,要避免 ET (Edge Triggered) 模式下的饿死问题。我的经验是:

高并发场景下尽量使用 LT (Level Triggered) 配合非阻塞 IO。

批量读取事件:epoll_wait 的 maxevents 调大,比如 4096。

示例代码片段(C语言):

int epfd = epoll_create1(0);
struct epoll_event events[4096];

int nfds = epoll_wait(epfd, events, 4096, 1000);
for (int i = 0; i < nfds; i++) {
    if (events[i].events & EPOLLIN) {
        handle_read(events[i].data.fd);
    }
    if (events[i].events & EPOLLOUT) {
        handle_write(events[i].data.fd);
    }
}

遇到的坑与解决

坑1:TIME_WAIT 过多

在压测时发现 ss -s 显示有几十万个 TIME_WAIT。最初以为是代码没关连接,后来确认是 短连接压测脚本导致的。
解决办法:开启 tcp_tw_reuse 并调整 tcp_fin_timeout。效果立竿见影,TIME_WAIT 数量骤降。

坑2:网卡中断绑核不均

/proc/interrupts 里看到某几个 CPU 核心中断数暴涨,导致 CPU 不均衡。

解决办法:使用 irqbalance 或手动 smp_affinity 绑核,把 25GbE 网卡的中断均匀分布到 NUMA 节点上。

echo ffff,ffff,ffff,ffff > /proc/irq/XX/smp_affinity

坑3:epoll_wait CPU 占用过高

最初 epoll_wait 触发的 wakeup 太频繁。后来把 epoll_wait 的超时调大到 1000ms,结合批量事件处理,CPU 使用率下降了 30%。

压测结果

在优化完成后,我们用 wrk + 自研压测工具 做了百万长连接测试。

指标 优化前 优化后
QPS ~50k ~200k
平均延迟 220ms 45ms
TIME_WAIT 数 600k+ < 50k
系统负载 35+ 8–10

最终,集群在百万连接情况下仍然能保持稳定,CPU 使用率控制在合理范围。

这次调优让我深刻体会到,CentOS 7 默认内核参数远远不适合百万级连接的场景。很多时候,问题不在代码层,而在内核的 socket、文件描述符和 epoll 调度机制上。

调优是一种“实战艺术”,需要结合监控指标、日志和 strace、perf 这些工具去逐步拆解瓶颈。现在回想起当时在香港机房调参到凌晨三点的经历,虽然满身疲惫,但看到百万连接稳定跑起来时,心里那股成就感依然很鲜明。

附录

A. sysctl.conf 配置模板

以下是我在 CentOS 7 香港服务器上实际使用的 sysctl.conf 调优配置,经过压测验证,能支撑百万级连接。

# 文件句柄最大值
fs.file-max = 2097152

# socket 队列调优
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 65535
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216

# TCP 连接调优
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_syncookies = 1

# 避免端口耗尽
net.ipv4.tcp_max_tw_buckets = 1048576

# 减少 keepalive 探测延迟
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5


应用配置:

sysctl -p

B. 压测脚本示例

在调优后,我用 wrk 和自研脚本同时进行压测。这里附上一个简单的 Python 脚本,可以模拟大量长连接。

Python 长连接压测脚本

import socket
import time
import threading

TARGET_IP = "192.168.1.100"
TARGET_PORT = 8080
CONNECTIONS = 1000000  # 模拟百万连接
THREADS = 100

def keep_alive():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
    try:
        s.connect((TARGET_IP, TARGET_PORT))
        while True:
            time.sleep(60)
            s.send(b'ping\n')
    except Exception as e:
        pass

def worker():
    for _ in range(CONNECTIONS // THREADS):
        threading.Thread(target=keep_alive, daemon=True).start()

for _ in range(THREADS):
    threading.Thread(target=worker).start()

print(f"模拟建立 {CONNECTIONS} 个长连接...")
while True:
    time.sleep(60)

C. wrk 命令示例

wrk 更适合做高 QPS 短连接压测:

wrk -t16 -c1000000 -d60s http://192.168.1.100:8080/

参数说明:

-t16:16 个线程

-c1000000:100 万并发连接

-d60s:持续压测 60 秒

D. 验证与监控

压测过程中,我主要通过以下命令监控:

# 查看 TCP 连接状态
ss -s

# 查看监听队列
netstat -antp | grep :8080 | wc -l

# 监控 CPU/内存
top -H -p $(pidof your_app)

# 查看中断分布
cat /proc/interrupts | grep eth

这样,完整的配置 + 脚本,就能帮助后来者在类似的 CentOS 7 + 香港服务器场景中,快速复现百万级并发连接下的 epoll 优化实践

目录结构
全文