如何使用ThinkPHP高效实现长连接请求处理?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1014个文字,预计阅读时间需要5分钟。
ThinkPHP 本身不支持真正意义上的长连接请求,所有长连接都需脱离 PHP-FPM 模式,改用 Swoole 或 Workerman 等常驻进程方案;在标准 Web 部署下,仅能依赖伪长轮询模拟。
为什么不能在控制器里用 sleep() 挂起请求
PHP-FPM 是同步阻塞模型,每个请求独占一个 worker 进程。写 sleep(5) 或 while (!Redis::get($key)) { usleep(100000); } 会导致该 worker 被锁死,无法处理新请求。压测时很快出现超时、502、FPM 进程耗尽。
- 即使加了
set_time_limit(0),也只延长脚本执行时间,不解决进程阻塞本质 - Nginx 默认
proxy_read_timeout是 60 秒,超时后主动断开,客户端收不到响应 - MySQL 连接可能因 wait_timeout 中断,触发
PDOException: MySQL server has gone away
扫码登录这类场景该用伪长轮询而非真长连接
客户端每 1–2 秒发一次 GET 请求,服务端查 Redis,有结果立刻返回,无结果快速返回空 JSON(如 {"code": 0, "msg": "waiting"}),不挂起、不阻塞。
- Redis key 建议用
SET scan_login:{$uuid} {"uid":123,"status":"success"} EX 300,避免 HSET 或序列化兼容问题 - 客户端轮询要加随机抖动(如
Math.random() * 600 - 300),防请求雪崩 - 服务端不要查数据库——轮询接口只读 Redis,写入由扫码确认动作原子完成
- 若用 Redis 集群,确保
Redis::connection()指向同一逻辑库,否则 key 分散查不到
要用真长连接,必须换运行模型:Swoole 或 GatewayWorker
ThinkPHP 原生 Db、Request、Session 等组件依赖 PHP-FPM 生命周期,无法直接用于 WebSocket 或 TCP 长连接上下文。必须通过适配层桥接。
立即学习“PHP免费学习笔记(深入)”;
- 选 Swoole:装
swoole扩展,用think-swoole官方扩展启动 HTTP/WebSocket 服务,Db可继续用,但需关掉break_reconnect(Swoole 下连接生命周期不同) - 选 GatewayWorker:装
workerman/gateway-worker+topthink/think-worker,启动命令是php think worker:gateway,业务逻辑写在Events.php里,不再走 MVC 流程 - 别混用:Nginx + PHP-FPM 下配
websocket://协议无效;wss 必须经 Nginx 反代,且配置Upgrade和Connection头
连接复用和断连兜底不能全信 break_reconnect
break_reconnect => true 只在单次请求内生效,且仅对非事务查询起作用。它不是后台心跳,而是“报错后重试下一条 SQL”。
- 事务中连接断开(如
Db::startTrans()后 wait_timeout 到期),TP 直接抛异常,不会重连 - 用了
mysqli驱动?break_reconnect完全不识别,必须切回pdo_mysql - Swoole 常驻进程下,连接被多个请求共享,
break_reconnect无法跨请求生效,此时应靠连接池或定期PING - 真正稳的方案是:MySQL 端设
wait_timeout = 300,应用层每次请求结束显式调用Db::close()(TP6.1+ 支持),或依赖自动析构但需确保没异常中断
复杂点在于:你以为在配“长连接”,其实是在协调 PHP 运行模型、MySQL 连接状态、框架连接管理、Redis 状态同步四者的节奏。任何一个环节错位,就会出现连接泄漏、重复创建、秒级超时或数据不一致。别只盯着配置项,先确认你用的是哪种部署方式。
本文共计1014个文字,预计阅读时间需要5分钟。
ThinkPHP 本身不支持真正意义上的长连接请求,所有长连接都需脱离 PHP-FPM 模式,改用 Swoole 或 Workerman 等常驻进程方案;在标准 Web 部署下,仅能依赖伪长轮询模拟。
为什么不能在控制器里用 sleep() 挂起请求
PHP-FPM 是同步阻塞模型,每个请求独占一个 worker 进程。写 sleep(5) 或 while (!Redis::get($key)) { usleep(100000); } 会导致该 worker 被锁死,无法处理新请求。压测时很快出现超时、502、FPM 进程耗尽。
- 即使加了
set_time_limit(0),也只延长脚本执行时间,不解决进程阻塞本质 - Nginx 默认
proxy_read_timeout是 60 秒,超时后主动断开,客户端收不到响应 - MySQL 连接可能因 wait_timeout 中断,触发
PDOException: MySQL server has gone away
扫码登录这类场景该用伪长轮询而非真长连接
客户端每 1–2 秒发一次 GET 请求,服务端查 Redis,有结果立刻返回,无结果快速返回空 JSON(如 {"code": 0, "msg": "waiting"}),不挂起、不阻塞。
- Redis key 建议用
SET scan_login:{$uuid} {"uid":123,"status":"success"} EX 300,避免 HSET 或序列化兼容问题 - 客户端轮询要加随机抖动(如
Math.random() * 600 - 300),防请求雪崩 - 服务端不要查数据库——轮询接口只读 Redis,写入由扫码确认动作原子完成
- 若用 Redis 集群,确保
Redis::connection()指向同一逻辑库,否则 key 分散查不到
要用真长连接,必须换运行模型:Swoole 或 GatewayWorker
ThinkPHP 原生 Db、Request、Session 等组件依赖 PHP-FPM 生命周期,无法直接用于 WebSocket 或 TCP 长连接上下文。必须通过适配层桥接。
立即学习“PHP免费学习笔记(深入)”;
- 选 Swoole:装
swoole扩展,用think-swoole官方扩展启动 HTTP/WebSocket 服务,Db可继续用,但需关掉break_reconnect(Swoole 下连接生命周期不同) - 选 GatewayWorker:装
workerman/gateway-worker+topthink/think-worker,启动命令是php think worker:gateway,业务逻辑写在Events.php里,不再走 MVC 流程 - 别混用:Nginx + PHP-FPM 下配
websocket://协议无效;wss 必须经 Nginx 反代,且配置Upgrade和Connection头
连接复用和断连兜底不能全信 break_reconnect
break_reconnect => true 只在单次请求内生效,且仅对非事务查询起作用。它不是后台心跳,而是“报错后重试下一条 SQL”。
- 事务中连接断开(如
Db::startTrans()后 wait_timeout 到期),TP 直接抛异常,不会重连 - 用了
mysqli驱动?break_reconnect完全不识别,必须切回pdo_mysql - Swoole 常驻进程下,连接被多个请求共享,
break_reconnect无法跨请求生效,此时应靠连接池或定期PING - 真正稳的方案是:MySQL 端设
wait_timeout = 300,应用层每次请求结束显式调用Db::close()(TP6.1+ 支持),或依赖自动析构但需确保没异常中断
复杂点在于:你以为在配“长连接”,其实是在协调 PHP 运行模型、MySQL 连接状态、框架连接管理、Redis 状态同步四者的节奏。任何一个环节错位,就会出现连接泄漏、重复创建、秒级超时或数据不一致。别只盯着配置项,先确认你用的是哪种部署方式。

