ThinkPHP默认Session文件驱动在高并发下因flock()文件锁导致请求阻塞,必须切换Redis驱动并启用phpredis的session_locking=1,同时登录后调用session_reset()更新ID以防范会话固定攻击。

ThinkPHP 默认的 Session 文件驱动在并发场景下会触发文件锁,导致请求排队、响应延迟甚至超时;这不是代码写得不对,而是默认配置和存储方式天然不适应高并发。必须换掉 file 驱动、禁用 session 文件锁、启用安全传输,并在登录后重置会话 ID。
为什么并发时 session_start() 会卡住
同一用户的两个并发请求,第二个会阻塞在 session_start() 等待第一个释放文件锁——Linux 下 file 驱动靠 flock() 实现互斥,串行化所有对该会话 ID 的读写操作。错误日志里常出现 Fatal error: Maximum execution time of 30 seconds exceeded in thinkphplibrarythinkSession.php on line 534,本质是锁等待超时。
- 现象:压测时响应时间阶梯式上涨,
504 Gateway Timeout或upstream timed out频发 - 根源:不是 PHP 执行慢,而是 I/O 级别阻塞,
strace -p $(pgrep php-fpm) -e trace=open,stat可观察到大量重复open("/runtime/session/sess_xxx", O_RDWR) - 临时缓解:控制器中完成读写后立即调用
session_write_close();但注意,之后不能再写$_SESSION或调用session()写操作
必须切换为 Redis 驱动并启用 session_locking
Redis 是唯一能真正解决 ThinkPHP 并发会话锁问题的方案,但仅改 'type' => 'redis' 不够,还必须确保底层支持无锁读写。
- 确认 phpredis 扩展版本 ≥ 5.3.2(低版本不支持
session_locking) - 在
php.ini中显式开启:redis.session_locking = 1(默认关闭) -
config/session.php配置示例:
return [
'type' => 'redis',
'host' => '127.0.0.1',
'port' => 6379,
'prefix' => 'sess:',
'expire' => 3600,
'timeout' => 0,
];
注意:timeout 设为 0 表示永不超时连接,避免 Redis 连接池抖动引发会话中断;若用 predis 库则不支持 session_locking,必须用 phpredis。
立即学习“PHP免费学习笔记(深入)”;
登录成功后必须调用 session_reset()
ThinkPHP 提供了 session_reset(),它不是简单清空数据,而是销毁旧会话、生成全新 session_id 并同步更新 Cookie,这是防御会话固定(Session Fixation)攻击的强制动作。
- 只在登录验证通过后执行一次,不能放在中间件或前置钩子里——未登录状态调用会破坏初始会话
- 执行后,原
session_id失效,后续请求必须携带新 ID,旧 Cookie 自动作废 - 搭配
'httponly' => true和'secure' => true(HTTPS 环境),才能构成完整防护链
哪些地方容易忽略导致并发+安全双失效
最常被跳过的点不是配置本身,而是上下文一致性:比如用了 Redis 存 Session,却忘了关掉 app_debug = true,debug 模式下 runtime 目录持续写入仍会拖垮 I/O;又或者设置了 secure => true,但 Nginx 没配好 HTTPS 重定向,导致 Cookie 被浏览器拒绝发送。
-
session_reset()后没刷新客户端 Cookie,前端仍发旧 ID —— 检查响应头是否含Set-Cookie且Secure; HttpOnly; Path=/ - 多个子应用共用 Redis 时,
prefix冲突导致会话互相覆盖 —— 建议按模块设前缀,如'admin_sess:'、'api_sess:' - Redis 内存不足时,
maxmemory-policy若设为noeviction,会直接拒绝写入,表现为用户无法登录 —— 推荐设为allkeys-lru
文章来自机圈观察员网,发布者:,转载请注明出处:https://www.jqgcy.com/xitongjiaocheng/124107.html