在 PHP 面试中,一旦聊到分布式、负载均衡或者多台服务器,Session 共享几乎是一个必问的知识点。很多候选人能说出“用 Redis 存 Session”,但再往下追问“具体怎么实现”“有哪些坑”,往往就答不上来了。这篇文章从面试实战角度出发,把 Session 共享的三种主流方案讲透,并重点分析分布式场景下容易踩的坑。
为什么会有 Session 共享问题
PHP 默认的 Session 存储方式是文件(session.save_handler = files),Session 文件保存在本机磁盘上(通常是 /tmp 或 session.save_path 指定的目录)。
单机环境下这没有任何问题。但一旦上了负载均衡,用户第一次请求被转发到 A 服务器,Session 写在 A 的磁盘上;第二次请求被转发到 B 服务器,B 上找不到这个 Session 文件,用户就被判定为未登录。
这就是 Session 共享问题的根源:Session 数据没有跟随用户的请求在服务器之间同步。
解决思路无非三类:把 Session 存到一个所有服务器都能访问的地方、让同一用户的请求固定落到同一台服务器、或者干脆不用服务端 Session。
方案一:Session 存入 Redis(或 Memcached)
这是目前最主流、也是面试中最常被问到的方案。
实现方式
PHP 提供了 session.save_handler 配置项,可以直接把 Session 交给 Redis 管理。有两种做法:
做法一:改 php.ini 或运行时配置
session.save_handler = redis
session.save_path = "tcp://127.0.0.1:6379?auth=password&database=0"
或者在代码中动态设置:
ini_set('session.save_handler', 'redis');
ini_set('session.save_path', 'tcp://127.0.0.1:6379');
session_start();
做法二:自定义 SessionHandler
实现 SessionHandlerInterface,自己控制读写逻辑,灵活性更高,适合需要加前缀、做序列化定制或加密的场景。
class RedisSessionHandler implements SessionHandlerInterface
{
private $redis;
public function __construct($redis)
{
$this->redis = $redis;
}
public function open($savePath, $sessionName): bool
{
return true;
}
public function close(): bool
{
return true;
}
public function read($id): string
{
return (string)$this->redis->get("session:$id");
}
public function write($id, $data): bool
{
return $this->redis->setex("session:$id", 1800, $data);
}
public function destroy($id): bool
{
return $this->redis->del("session:$id") > 0;
}
public function gc($maxLifetime): int|false
{
return 0; // Redis 的过期机制自动处理
}
}
$handler = new RedisSessionHandler(new Redis());
$handler->connect('127.0.0.1', 6379);
session_set_save_handler($handler, true);
session_start();
优点
- 读写速度快,性能远优于文件和数据库
- 天然支持过期淘汰,不需要自己写 GC
- 多台服务器共享同一份数据,扩展性好
注意点
- Redis 需要设置合理的
maxmemory-policy,避免 Session 被 LRU 策略提前淘汰 - 如果 Redis 挂了,Session 全部失效,需要考虑高可用(哨兵或集群)
- Session 数据建议设置 TTL(如 30 分钟),与
session.gc_maxlifetime保持一致
方案二:Session 存入数据库
把 Session 数据写进 MySQL 等关系型数据库,所有服务器连接同一个库。
实现方式
同样可以通过 SessionHandlerInterface 实现,核心就是一张 session 表:
CREATE TABLE sessions (
id VARCHAR(128) PRIMARY KEY,
data TEXT,
last_access INT
);
然后实现 read/write/destroy/gc 方法,gc 中删除 last_access 超过阈值的记录。
优点
- 数据持久化,服务器重启不丢
- 方便做统计和审计
缺点
- 每次请求都要读写数据库,并发高时数据库压力大
- 性能明显不如 Redis
面试建议:这个方案一般作为“知道即可”的备选答案,实际生产中很少单独使用。如果面试官追问“还有别的方式吗”,说出来即可,但不要把它当作首选推荐。
方案三:JWT / 无状态 Token
严格来说,这不是“Session 共享”,而是“消灭 Session”。
思路
服务端不再保存会话状态,用户登录后签发一个 Token(通常是 JWT),客户端每次请求携带这个 Token,服务端验证签名即可确认身份。
// 签发
$payload = ['uid' => 123, 'exp' => time() + 3600];
$token = JWT::encode($payload, $secretKey, 'HS256');
// 验证
$decoded = JWT::decode($token, new Key($secretKey, 'HS256'));
优点
- 服务端完全无状态,天然支持水平扩展
- 适合前后端分离、多端(App/Web/小程序)场景
缺点(面试高频追问)
- 无法主动失效:Token 签发后在过期前一直有效,除非引入黑名单机制,而黑名单又回到了“有状态”
- 续签麻烦:需要配合 refresh token 机制
- Payload 不宜过大:JWT 放在 Header 里,太大会增加每次请求的开销
- 密钥泄露风险:签名密钥一旦泄露,攻击者可伪造任意用户
分布式场景下的坑
这部分是面试拉开差距的地方,光知道三种方案不够,还要知道实际落地时会遇到什么问题。
坑一:Session 并发写覆盖
同一个用户的多个请求同时到达不同服务器,都在写同一个 Session key,后写的会覆盖先写的。典型场景是 Ajax 并发请求。
应对:对 Session 加锁,或者业务上避免依赖 Session 做频繁写入。Redis 可以用 SETNX 做简单锁,但要注意死锁和性能。
坑二:Session ID 被固定(Session Fixation)
攻击者诱导用户使用一个已知的 Session ID 登录,登录后该 ID 仍然有效,攻击者就能劫持会话。
应对:用户登录成功后必须调用 session_regenerate_id(true) 重新生成 Session ID。
坑三:负载均衡的会话保持与共享冲突
有些架构既配置了 Nginx 的 ip_hash 做会话保持,又上了 Redis 做 Session 共享,两者逻辑打架,排查问题时会非常困惑。
建议:要么纯共享(Redis),要么纯保持(ip_hash),不要混用。
坑四:多项目共用 Redis 的 key 冲突
多个项目共用一个 Redis 实例,Session key 前缀没有区分,导致 A 项目读到了 B 项目的 Session。
应对:自定义 Handler 时加项目前缀,如 projectA:session:{id}。
坑五:序列化不一致
不同服务器上 PHP 版本或 session.serialize_handler 配置不一致,导致反序列化失败,Session 读出来是空的。部署时务必保证环境一致。
面试回答模板
如果面试官问“你们线上 Session 是怎么共享的”,可以这样组织回答:
我们用的是 Redis 方案,通过
session_set_save_handler自定义了 Handler,key 加了项目前缀,TTL 设置 30 分钟。选 Redis 是因为读写快、支持自动过期。同时我们注意了几个点:登录后session_regenerate_id防固定攻击,Redis 做了哨兵保证高可用,maxmemory-policy用的是volatile-lru避免 Session 被误淘汰。另外因为是无状态 API 场景,部分接口已经迁移到 JWT 了。
这样的回答既有方案,又有细节,还有权衡,基本能覆盖面试官的考察点。
总结
| 方案 | 性能 | 持久化 | 主动失效 | 适用场景 |
|---|---|---|---|---|
| Redis | 高 | 可配置 | 支持 | 大多数分布式场景 |
| 数据库 | 低 | 强 | 支持 | 低并发、需审计 |
| JWT | 高 | 无状态 | 不支持 | 前后端分离、API |
Session 共享没有银弹,选型要结合并发量、一致性要求和团队运维能力。面试时把原理讲清楚、把坑点说出来,比背一个“标准答案”更有说服力。
未经允许不得转载:任鹏个人博客 » PHP 面试精讲:Session 共享的三种方案与分布式场景下的坑

