Workerman 中 session_start() 失效,因其常驻多进程模型导致原生 file-based session 无法跨进程同步;应弃用 $_SESSION,改用 Redis 手动管理 session ID 与数据,并在 HTTP/WebSocket 等场景统一传递和校验 session ID。

Workerman 本身不支持 PHP 原生 session_start(),HTTP 请求无法自动保持会话 —— 因为它不是传统 Apache/Nginx + PHP-FPM 模型,没有共享的 session 存储上下文。
为什么 session_start() 在 Workerman 里基本失效
Workerman 是常驻内存的多进程模型,每个 worker 进程独立运行,PHP 的默认 file-based session 依赖进程间共享文件锁和一致的 session.save_path,但在高并发、多 worker 场景下极易出现 session 覆盖、丢失、读不到数据等问题。更关键的是:请求可能被不同 worker 处理,而原生 session 文件没做跨进程同步机制。
常见错误现象包括:
- 登录后刷新页面就变未登录状态
-
$_SESSION有时有值、有时为空 - 同一浏览器两个标签页操作互相“挤掉”对方 session
- 错误提示:
session_start(): Failed to read session data(尤其在重启 worker 后)
用 Redis 手动接管 session 生命周期
必须绕过 PHP 默认 session 管理,自己控制 session ID 生成、存储、读取和销毁。核心是把 session 数据存在 Redis 这类外部、共享、带过期的存储中。
实操建议:
- 安装
redis扩展,并确保 Redis 服务可连通 - 不要调用
session_start(),改用自定义逻辑:从Cookie或Header提取PHPSESSID(或你自定义的 session key) - 用该 ID 作为 Redis 的 key,
GET/SET关联的 JSON 数据 - 每次响应前写回 Cookie,设置
HttpOnly和SameSite防 XSS/CSRF - 注意 session 过期时间要和 Redis TTL 一致,避免“已过期但还能读到”的脏数据
示例片段(简化):
use Redis;
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 从 Cookie 获取 sessionId
$sessionId = $_COOKIE['my_session_id'] ?? bin2hex(random_bytes(16));
// 从 Redis 读 session 数据
$data = $redis->get('sess:' . $sessionId);
$session = $data ? json_decode($data, true) : [];
// 登录成功后写入
if ($loginSuccess) {
$session['user_id'] = 123;
$redis->setex('sess:' . $sessionId, 3600, json_encode($session));
setcookie('my_session_id', $sessionId, [
'expires' => time() + 3600,
'path' => '/',
'domain' => '',
'secure' => false,
'httponly' => true,
'samesite' => 'Lax'
]);
}
别踩 $_SESSION 自动绑定这个坑
有人试图通过 session_set_save_handler() 注册 Redis handler,再调用 session_start(),看似“兼容原生”。这在 Workerman 中极不稳定:
- 多个 worker 同时调用
session_write_close()可能触发重复写入或 race condition - PHP 内部 session 状态机(如
session_status())与常驻进程生命周期错位 - 某些版本 PHP 在 CLI 模式下对 session handler 的初始化不完整,导致
session_destroy()失效 - 调试时容易误以为“session 有值”,其实是上一次请求残留的全局变量,而非真实 Redis 数据
结论:宁可完全弃用 $_SESSION,也不要半吊子 hook。手动管理 session ID + Redis 数据,逻辑清晰、可控性强、出问题好定位。
WebSocket 场景下 session 怎么复用
如果 Workerman 同时跑 HTTP 和 WebSocket 服务(比如用 TextProtocol),不能指望 WebSocket 连接自动带上 HTTP 的 Cookie —— 它们是不同协议,初始握手阶段需显式传参。
实操建议:
- 前端建立 WebSocket 连接时,在 URL query 里带上
my_session_id=xxx(例如ws://localhost:2346?my_session_id=abc123) - 在
onConnect回调里解析$connection->getRemoteIp()和 query 参数,查 Redis 加载用户身份 - 不要在 WebSocket 连接中反复查 Redis,首次认证后把用户信息挂到
$connection->session属性上(这是 Workerman 推荐做法) - 注意:query 里的 session ID 是明文传输,若需安全,应搭配 token 签名或升级 wss + 反向代理透传 header
真正难的不是存 session,而是让 session ID 在 HTTP、WebSocket、定时任务、甚至异步回调之间可靠传递。一旦路径变长、中间加了代理或跨域,ID 就容易断。所以越早统一 session ID 的生成、分发、校验逻辑越好,别等线上用户开始反复登录才想起来看 Redis TTL 设置对不对。










