根本原因是filesession将session存于各服务器本地磁盘,负载均衡导致请求分散后无法共享;必须改用redis或数据库存储,并完整配置组件、类替换及连接参数。

集群环境下 Yii2 的 session 失效,根本原因不是代码写错了,而是默认的 yii\web\FileSession 把数据写在每台服务器自己的磁盘上——请求被负载均衡分到 A 机,登录态存在 A 的 /tmp;下次请求落到 B 机,session_start() 在 B 的 /tmp 里找不到对应文件,直接新建一个空 session,isGuest 自然还是 true。
为什么 FileSession 在集群中必然失效
PHP 原生 session 机制依赖服务端本地存储,Yii2 的 FileSession 只是封装了它。只要没改 session.save_path 或组件类,就始终走文件路径。关键点在于:
- 负载均衡(如 Nginx)不保证同一用户连续请求落到同一台机器,
ip_hash只是权宜之计,无法应对 IP 池复用、代理出口 NAT 等场景 -
FileSession不做跨机同步,A 机写的 session 文件,B 机完全不可见 - 即使手动 rsync 同步
/tmp,也会因并发写入、文件锁、过期清理不同步导致数据错乱或丢失
用 Redis 替换 FileSession 的三处硬性配置
只改 class 为 yii\redis\Session 不够,漏掉任一参数都会报 Connection refused 或静默降级回文件存储:
-
'class' => 'yii\redis\Session':必须显式指定,不能只靠别名自动加载 -
'redis' => 'redis':指向已定义的redis组件名(不是 host/port),确保该组件已正确配置hostname和port -
'keyPrefix' => 'yii2session:':必须设,否则多应用共用 Redis 时 key 冲突(默认用Application::id生成前缀,不同实例前缀不同)
示例片段:
'session' => [
'class' => 'yii\redis\Session',
'redis' => 'redis', // 对应 components 中的 redis 配置
'keyPrefix' => 'yii2session:',
'timeout' => 86400,
],
跨子域名共享 session 必须配对的三个地方
比如主站 example.com 登录后,希望 api.example.com 能读取 session,光 Redis 共享不够,cookie 本身必须能跨域传递:
-
'cookieParams' => ['domain' => '.example.com']:注意开头的点,缺了就只限当前主机名 -
'name' => 'PHPSESSID':所有子域名必须用同一个 session 名,不能让 Yii 自动生成带随机后缀的 name -
'cookieValidationKey':在request组件中统一设置,否则子域名解密 cookie 失败,$_COOKIE['PHPSESSID']会被丢弃
前端 fetch 请求还要加 credentials: 'include',否则浏览器压根不发这个 cookie。
数据库存 session 的性能陷阱
yii\web\DbSession 虽能解决共享问题,但每次请求都触发一次 DB 查询(甚至两次:读 + 更新 timeout),在高并发下容易成为瓶颈:
- 必须确保
sessionTable有PRIMARY KEY(session_id)和INDEX(expire),否则GC清理慢,表膨胀 - MySQL 的
READ-COMMITTED隔离级别下,并发请求可能因行锁阻塞,出现短暂 session 读取超时 - 不如 Redis 的原子操作高效,尤其当 session 数据较大(如存了用户权限树)时,网络+序列化开销更明显
真正需要强持久化(如审计日志绑定 session)才选 DB,日常登录态优先用 Redis。
最容易被忽略的是:Redis 连接池没配或超时时间短于 session timeout,导致连接断开后 Yii 自动 fallback 到 FileSession,现象就是“有时好有时坏”,查日志会发现 Failed to write session data 但没报致命错误。











