FrankenPHP容器重启必然断连,真正目标是用户无感恢复:需客户端指数退避重连、服务端状态外存(如Redis)、反向代理健康检查+多实例容灾、HTTP接口幂等设计及前端操作缓存续传。

FrankenPHP 容器重启时“不断线”本质上是个伪命题——容器进程终止,所有 TCP 连接(包括 WebSocket、长轮询、HTTP/2 流)必然断开。真正可落地的目标是:**让连接中断对用户不可见,或在极短时间内自动恢复**。这需要分层处理,不能只靠 restart: unless-stopped。
WebSocket 连接必须靠客户端重连 + 服务端状态重建
FrankenPHP 的 Worker 模式虽让 PHP 常驻内存,但容器重启会 kill 整个进程,所有已建立的 WebSocket 连接立即失效。此时:
- 服务端无法“保持”连接,
frankenphp_handle_request()调用链彻底中断 - 客户端会收到
close事件(通常 code 1006 或 1015),必须主动重连 - 若依赖内存中的会话状态(如在线用户列表、房间成员),需迁移到 Redis 等外部存储,否则重连后状态丢失
- 前端重连逻辑必须带指数退避,避免重启瞬间大量请求打爆新实例:
setTimeout(connect, Math.min(10000, 2 ** retryCount * 1000))
HTTP/HTTPS 请求可通过反向代理平滑过渡
如果你把 FrankenPHP 放在 Nginx 或 Traefik 后面(不推荐,但有些架构需要),可利用代理层的连接复用和健康检查实现“无感重启”:
- Nginx 配置
proxy_next_upstream error timeout http_502;,当上游 FrankenPHP 容器重启时返回 502,Nginx 自动转发请求到备用实例(需多副本) - 必须启用
health_check,例如定期 GET/health,确保 Nginx 只将流量导向存活实例 - FrankenPHP 自身不提供多实例负载能力,所以这个方案本质是“用额外代理换平滑”,增加了运维复杂度
Docker 重启策略只能保服务,不能保连接
restart: unless-stopped 确实能让容器崩溃后自动拉起,但它解决的是“服务存活”问题,不是“连接延续”问题:
立即学习“PHP免费学习笔记(深入)”;
- 容器重启过程通常耗时 1–3 秒,期间所有新连接被拒绝,旧连接已断
- 如果使用
docker-compose up --force-recreate或镜像更新,旧容器先 stop,再启动新容器,存在明确的中断窗口 - 想缩短中断时间,可优化 FrankenPHP 启动速度:精简
Caddyfile、关闭调试日志、预热 PHP opcache(通过opcache.preload) - 别依赖
depends_on做启动顺序控制——它只判断容器是否 start,不保证服务已 ready
真正降低感知中断的关键:前端兜底 + 后端幂等
后端无法阻止连接断开,但可以大幅降低用户感知。重点不在“不断”,而在“断了也像没断”:
- WebSocket 客户端保存最后一条发送消息的 ID,在重连成功后发
{type: "resend", from_id: "xxx"},服务端查漏补缺 - HTTP 接口全部设计为幂等:重复提交订单、点赞、评论,结果一致;用
Idempotency-Key头去重 - 前端本地缓存未确认的操作(如草稿、待提交表单),网络恢复后自动续传
- FrankenPHP 的
worker模式本身不解决重启问题,但它让每次重启后的首请求不再有框架加载延迟——这是快速恢复体验的基础
最常被忽略的一点:很多人以为加了 restart: always 就万事大吉,却没配健康检查,导致容器“假活”(进程在但 HTTP 服务未响应),前端持续失败重试。健康检查不是可选项,是连接韧性链条里承上启下的关键一环。



















