本文目录导读:

在 PHP 蓝绿部署中实现会话保持(Session Stickiness)是一个常见的挑战,因为传统的 PHP 会话默认存储在本地文件系统中,以下是几种实现方案:
共享会话存储(推荐)
Redis 方案
// php.ini 配置 session.save_handler = redis session.save_path = "tcp://redis-server:6379?auth=password&database=0"
或者使用 PHP 代码初始化:
<?php
// 在应用启动时配置
ini_set('session.save_handler', 'redis');
ini_set('session.save_path', 'tcp://redis-host:6379');
// 使用 Predis 或 phpredis 扩展
// 示例使用 phpredis
$redis = new Redis();
$redis->connect('redis-server', 6379);
session_set_save_handler(
function($savePath, $sessionName) use ($redis) {
return true;
},
function() {
return true;
},
function($sessionId) use ($redis) {
return $redis->get("session:$sessionId") ?: '';
},
function($sessionId, $data) use ($redis) {
$redis->set("session:$sessionId", $data, 3600);
return true;
},
function($sessionId) use ($redis) {
$redis->del("session:$sessionId");
return true;
},
function($maxLifetime) {
return true;
}
);
session_start();
?>
Memcached 方案
// php.ini
session.save_handler = memcached
session.save_path = "memcached-server:11211"
// 或者使用 Memcached 类
$memcached = new Memcached();
$memcached->addServer('memcached-server', 11211);
session_start();
数据库会话存储
<?php
class SessionDBHandler {
private $pdo;
public function __construct($pdo) {
$this->pdo = $pdo;
}
public function start() {
session_set_save_handler(
array($this, 'open'),
array($this, 'close'),
array($this, 'read'),
array($this, 'write'),
array($this, 'destroy'),
array($this, 'gc')
);
session_start();
}
public function open($savePath, $sessionName) {
return true;
}
public function close() {
return true;
}
public function read($sessionId) {
$stmt = $this->pdo->prepare("SELECT data FROM sessions WHERE id = ?");
$stmt->execute([$sessionId]);
return $stmt->fetchColumn() ?: '';
}
public function write($sessionId, $data) {
$stmt = $this->pdo->prepare(
"INSERT INTO sessions (id, data, last_access)
VALUES (?, ?, NOW())
ON DUPLICATE KEY UPDATE data = ?, last_access = NOW()"
);
return $stmt->execute([$sessionId, $data, $data]);
}
public function destroy($sessionId) {
$stmt = $this->pdo->prepare("DELETE FROM sessions WHERE id = ?");
return $stmt->execute([$sessionId]);
}
public function gc($maxLifetime) {
$stmt = $this->pdo->prepare(
"DELETE FROM sessions WHERE last_access < DATE_SUB(NOW(), INTERVAL ? SECOND)"
);
return $stmt->execute([$maxLifetime]);
}
}
// 使用
$pdo = new PDO('mysql:host=db-server;dbname=app', 'user', 'pass');
$handler = new SessionDBHandler($pdo);
$handler->start();
负载均衡器配置(辅助方案)
Nginx + IP Hash
upstream php_servers {
ip_hash;
server blue-server:9000;
server green-server:9000;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://php_servers;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
HAProxy Cookie 粘性
backend php_servers
balance roundrobin
cookie SERVERID insert indirect nocache
server blue-server blue:9000 cookie blue check
server green-server green:9000 cookie green check
部署切换策略
优雅切换方案
#!/bin/bash # deploy.sh # 1. 部署到 Green 环境 git pull origin main rsync -av --exclude='.git' ./ green-server:/var/www/green/ # 2. 切换到 Green(修改负载均衡配置) # 方式1:修改 Nginx 配置 cp nginx_blue.conf nginx.conf nginx -s reload # 方式2:使用 Docker 容器 docker-compose up -d green docker-compose scale blue=0 # 3. 回滚 # 如果需要,切回 Blue
完整示例:Laravel/Symfony 配置
Laravel .env 配置
SESSION_DRIVER=redis REDIS_HOST=redis-server REDIS_PORT=6379 # .env.blue APP_ENV=blue APP_URL=https://blue.example.com # .env.green APP_ENV=green APP_URL=https://green.example.com
部署脚本
<?php
// deploy.php
$blueServers = ['blue-1', 'blue-2'];
$greenServers = ['green-1', 'green-2'];
function switchTraffic($activeColor) {
// 更新负载均衡配置
$config = "upstream php_servers {\n";
if ($activeColor === 'green') {
foreach ($greenServers as $server) {
$config .= " server {$server}:9000;\n";
}
// 保留部分 blue 用于灰度
$config .= " server {$blueServers[0]}:9000 backup;\n";
} else {
foreach ($blueServers as $server) {
$config .= " server {$server}:9000;\n";
}
$config .= " server {$greenServers[0]}:9000 backup;\n";
}
$config .= "}\n";
file_put_contents('/etc/nginx/sites-available/php_upstream.conf', $config);
exec('nginx -s reload');
}
// 健康检查
function healthCheck($servers) {
foreach ($servers as $server) {
$response = @file_get_contents("http://{$server}/health");
if ($response !== 'OK') {
return false;
}
}
return true;
}
// 执行部署
switchTraffic('green');
最佳实践建议
- 会话数据序列化
// 使用自定义会话序列化器 ini_set('session.serialize_handler', 'php_serialize');
// 存储用户对象 $_SESSION['user'] = serialize($userObject);
2. **会话 ID 管理**
```php
// 更换会话 ID(防止会话固定攻击)
session_regenerate_id(true);
// 配置会话 cookie
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax'
]);
- 监控与日志
// 记录会话信息用于调试 error_log("Session ID: " . session_id() . " IP: " . $_SERVER['REMOTE_ADDR']);
- 首选方案:使用 Redis/数据库等共享存储,这样蓝绿切换时无需担心会话丢失
- 辅助措施:负载均衡器配置 cookie 粘性或 IP hash 减少会话迁移
- 部署策略:先部署新版本到空闲环境,测试通过后切换流量,保留回滚能力
- 监控告警:部署后监控会话成功率、错误率等指标
这样配置后,即使发生蓝绿切换,用户的会话也能保持,不会因为切换到新环境而丢失登录状态。