本文目录导读:

在PHP负载均衡环境下,会话管理是必须解决的核心问题之一,因为用户的请求可能被分发到不同的服务器上,以下是处理PHP会话的几种主流方案,从简单到复杂:
粘性会话(Sticky Session)
让负载均衡器将同一用户的请求始终转发到同一台服务器。
实现方式:
- Nginx 配置
ip_hash或sticky模块 - HAProxy 配置
balance stick-table或cookie
# Nginx 基于IP哈希
upstream php_servers {
ip_hash;
server 192.168.1.10:9000;
server 192.168.1.11:9000;
}
优缺点:
- ✅ 实现简单,无需修改代码
- ❌ 服务器故障时用户会话丢失;负载可能不均衡
共享会话存储
将PHP会话数据存储在共享的外部存储中,所有服务器访问同一份会话数据。
推荐方案:
A. Redis 存储(最常用)
// php.ini 配置 session.save_handler = redis session.save_path = "tcp://redis-server:6379"
或在代码中设置:
ini_set('session.save_handler', 'redis');
ini_set('session.save_path', 'tcp://192.168.1.50:6379');
// 使用PHP Redis扩展
$redis = new Redis();
$redis->connect('redis-server', 6379);
session_set_save_handler(
function($savePath, $sessionName) { return true; },
function() { return true; },
function($sessionId) use ($redis) {
return $redis->get('session:' . $sessionId);
},
function($sessionId, $data) use ($redis) {
return $redis->set('session:' . $sessionId, $data, 3600);
},
function($sessionId) use ($redis) {
return $redis->delete('session:' . $sessionId);
},
function($lifetime) { return true; }
);
session_start();
B. Memcached 存储
ini_set('session.save_handler', 'memcached');
ini_set('session.save_path', '192.168.1.50:11211,192.168.1.51:11211');
C. MySQL 数据库存储
CREATE TABLE sessions (
session_id VARCHAR(128) PRIMARY KEY,
data TEXT,
expires TIMESTAMP,
INDEX(expires)
);
// 自定义会话处理器
class MySQLSessionHandler implements SessionHandlerInterface {
private $pdo;
public function open($savePath, $sessionName) {
$this->pdo = new PDO('mysql:host=db-host;dbname=app', 'user', 'pass');
return true;
}
public function read($sessionId) {
$stmt = $this->pdo->prepare('SELECT data FROM sessions WHERE session_id = ? AND expires > NOW()');
$stmt->execute([$sessionId]);
return $stmt->fetchColumn() ?: '';
}
// 其他方法...
}
优缺点:
- ✅ 会话数据可持久化,故障时可恢复
- ❌ 需要部署额外服务(Redis/Memcached),存在单点故障(需Redis集群)
无状态架构(Token 认证)
完全放弃传统会话,改用 Token 认证(如 JWT)。
// 生成Token
$payload = [
'user_id' => 123,
'exp' => time() + 3600
];
$token = JWT::encode($payload, SECRET_KEY);
// 存储Token到客户端(前端存储,通常是 localStorage)
// 每次请求通过 Authorization Header 携带
$token = $_SERVER['HTTP_AUTHORIZATION'];
// 验证Token
try {
$payload = JWT::decode($token, SECRET_KEY);
$userId = $payload['user_id'];
} catch (Exception $e) {
// Token无效
}
优点:
- ✅ 完全无状态,服务器无会话存储
- ✅ 水平扩展极其方便
缺点:
- ❌ Token无法在服务端主动失效(需配合黑名单)
- ❌ Token体积较大,每次请求开销增加
混合方案(推荐)
结合粘性会话与共享存储:
- 默认使用 Redis 存储会话
- 配置粘性会话减少 Redis 压力
- Redis 采用主从+哨兵机制保证高可用
# Nginx 配置示例
upstream php_servers {
sticky cookie srv_id expires=1h; # 使用cookie粘性
server 192.168.1.12:9000;
server 192.168.1.13:9000;
}
server {
listen 80;
location / {
proxy_pass http://php_servers;
proxy_set_header Host $host;
}
}
数据库驱动会话清理
无论使用哪种方案,都需要定期清理过期会话:
-- MySQL 定期删除过期会话 DELETE FROM sessions WHERE expires < NOW();
# Redis 自动过期机制已内置 redis-cli config set maxmemory-policy allkeys-lru
实践建议
| 方案 | 场景 | 成本 | 推荐度 |
|---|---|---|---|
| 粘性会话 | 小规模、开发环境 | 低 | |
| Redis 存储 | 生产环境首选 | 中 | |
| JWT 无状态 | 高并发API服务 | 中 | |
| 混合方案 | 大型生产环境 | 高 |
最终推荐组合:
- 使用 Redis 集群 + JWT认证(重要API)
- 辅以 粘性会话 减少存储压力
- 使用 Session Middleware 统一管理
// 最后使用Redis实现会话锁机制(防止并发写冲突)
$lockKey = 'session_lock_' . $sessionId;
$lock = $redis->set($lockKey, 1, ['NX', 'EX' => 5]);
if ($lock) {
session_start();
// ... 操作会话
session_write_close();
$redis->del($lockKey);
}