要让Swoole稳定承载百万级TCP连接,必须同时优化内核资源配额、协程调度安全边界和连接生命周期控制:先确保ulimit -n≥300000且max_coroutine≤ulimit -n×0.7;再配置多线程模式、严格限制协程总数、精简hook;接着启用TCP Keepalive与应用层心跳(基于Swoole\Table查表);最后用Swoole\Table隔离状态、io_uring异步落盘日志。

要让Swoole稳定承载百万级TCP连接,不能只靠堆配置或加机器,必须从内核资源配额、协程调度安全边界、连接生命周期控制三个硬性层面同时切入,缺一不可。
确认系统级资源上限
先查 ulimit -n 输出值,若低于 300000,直接跳过后续所有步骤——【max_coroutine 值永远不能超过 ulimit -n × 0.7】,否则必然触发 too many open files 报错并导致连接断崖式下跌。
执行命令:cat /proc/$(pgrep php)/limits | grep "Max open files",确认软限制与硬限制一致且 ≥300000。
临时提升:sudo sysctl -w fs.file-max=400000;永久生效需编辑 /etc/sysctl.conf 并追加 fs.file-max = 400000。
配置Swoole服务端核心参数
在 Server 构建前设置以下参数,顺序不可颠倒:
第一步:启用多线程模式(Swoole 6+ 必选),替代老式多进程模型:$server->set(['reactor_num' => 8, 'worker_num' => 16, 'task_worker_num' => 4, 'dispatch_mode' => 3, 'thread_num' => 8]);
第二步:严格限定协程总数:'max_coroutine' => 210000(假设 ulimit -n = 300000);这个数字必须手动计算,不能写死为 300000。
第三步:关闭非必要 hook,仅保留 swoole_hook_tcp | swoole_hook_sleep,避免 file 或 curl 等同步操作意外阻塞整个协程栈。
连接层防御式管理
方法一:启用 TCP 层 Keepalive(系统级保活)
在 $server->set() 中加入:'socket_keepalive' => true, 'socket_keepcount' => 5, 'socket_keepinterval' => 60。这能自动清理僵死连接,但无法替代应用层心跳。
方法二:应用层心跳检测(必须实现)
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
注册 tick 定时器,每 30 秒扫描一次活跃连接:$server->on('tick', function () use ($server) { ... });,检查 $server->getClientInfo($fd)['last_active'] 时间戳,超 90 秒无交互则主动 close($fd)。
注意:不要用 foreach 遍历 $server->connections 数组做全量扫描——当连接数达 80 万时,单次遍历耗时超 200ms,会拖垮 Reactor 线程。改用 Swoole\Table 建立 fd → last_active 映射表,查表时间恒定 O(1)。
内存与状态隔离策略
所有设备级上下文数据(如设备ID、认证状态、心跳序列号)禁止存入全局变量或闭包引用中,必须写入 Swoole\Table 或通过 Redis Stream 同步。
示例:初始化共享内存表:$table = new Swoole\Table(1000000); $table->column('device_id', Swoole\Table::TYPE_STRING, 32); $table->column('last_active', Swoole\Table::TYPE_INT, 4); $table->create();
每次 onReceive 时更新:$table->set("fd_{$fd}", ['device_id' => $device_id, 'last_active' => time()]);
这一步不做,连接数上到 50 万后 PHP 内存 RSS 会呈指数增长,gc_collect_cycles() 完全无效。
异步落盘与日志降噪
禁用 error_log() 和 file_put_contents(),全部替换为 io_uring 异步写入。
创建独立日志协程:go(function () { $logger = new Swoole\Coroutine\IO(); while (true) { $msg = co::pop('log_queue'); $logger->writeFile('/var/log/swoole_conn.log', $msg . "\n", FILE_APPEND | LOCK_EX); } });
主逻辑中投递日志:co::push('log_queue', "[CONN] fd={$fd} online");
这一步不改,写日志会卡住网络收发,P99 响应毛刺将突破 500ms。

















