prefork开启后qps不升反降,根本原因是未同步调高ulimit -n,导致fd不足,新连接被内核丢弃;必须配套调整系统限制、验证启动行为并实现信号驱动的优雅停机。

Prefork 开启后 QPS 翻三倍,但不调 ulimit 就等于白开
为什么 Prefork 开了反而更慢?
常见错误是:在 4 核机器上直接设 Prefork: true,压测发现 QPS 不升反降,CPU 利用率卡在 30% 左右。根本原因不是 Fiber 有问题,而是操作系统层面的资源没跟上。
- 每个 worker 进程都需要独立的文件描述符(FD)来 accept 连接;默认
ulimit -n是 1024,8 个 worker 就撑死 8192 连接,远低于内核能处理的并发量 - Linux 的
SO_REUSEPORT虽支持多进程监听同一端口,但若 FD 不足,新连接会被内核直接丢弃,netstat -s | grep -i "failed"可看到failed connection attempts持续上涨 - 小规模部署(≤4 核)、SSD 性能一般、连接数长期低于 2k 的场景,Prefork 的 fork 开销和进程调度成本可能超过收益
怎么安全地启用 Prefork?
必须同步完成三件事:改配置、调系统限制、验证启动行为。缺一不可。
- 初始化时显式开启:
app := fiber.New(fiber.Config{Prefork: true}),不要依赖环境变量或运行时开关 - 启动前执行
ulimit -n 65536(建议值),并确保该限制被子进程继承(systemd 服务需在[Service]段加LimitNOFILE=65536) - 观察日志:开启后会打印类似
Worker PIDs: [1234, 1235, 1236, 1237],若只看到一个 PID 或无此输出,说明 Prefork 实际未生效 - 禁用
DisableStartupMessage: true会隐藏该提示,调试阶段务必保持默认开启
Prefork 下的优雅停机怎么做?
普通 app.Shutdown() 只杀主进程,worker 子进程会残留——这是最常被忽略的生产事故点。
- 必须用信号控制:
os.Interrupt或syscall.SIGTERM触发全局 shutdown 流程 - Fiber v2 中,
app.Shutdown()需配合os.Signal监听,v3 已内置app.ListenAndServe()的信号感知,但仍建议手动注册 - 各 worker 进程内存空间完全隔离,不能靠全局变量传递状态;session、计数器等必须走 Redis 或共享存储
- 日志写入需注意:多个进程同时写同一文件易乱序,推荐用
lumberjack轮转 +os.O_APPEND,或统一打到 syslog / Loki
真正难的从来不是加一行 Prefork: true,而是确认所有 worker 进程都拿到足够 FD、能响应信号、不共享内存状态、且日志和监控能区分进程维度——这些细节漏掉一个,多核就变成多坑。











