預設情況下 PHP 將 Session 數據存儲在文件系統中,這在分佈式集群、高併發、大數據量場景下存在性能瓶頸與一致性問題。通過實現 SessionHandlerInterface 接口並註冊 session_set_save_handler,可將 Session 透明地遷移至 MySQL/Redis 等中心化存儲,實現多服務器 Session 共享與高可用。
SessionHandler封裝數據庫連接、表結構初始化、CRUD 操作與垃圾回收 (GC) 邏輯:
class SessionHandler implements SessionHandlerInterface {
protected $maxlifetime;
protected $dbHandle;
protected $config;
public static function init(array $config): self {
return new self($config);
}
public function __construct(array $args) {
$this->config = $args;
$this->maxlifetime = (int)ini_get('session.gc_maxlifetime') ?: 1440;
session_set_save_handler(
[$this, 'open'],
[$this, 'close'],
[$this, 'read'],
[$this, 'write'],
[$this, 'destroy'],
[$this, 'gc']
);
register_shutdown_function('session_write_close'); // 確保寫入
}
public function open(): bool {
$this->dbHandle = new mysqli(
$this->config['host'],
$this->config['user'],
$this->config['password'],
$this->config['database']
);
if ($this->dbHandle->connect_errno) {
error_log("Session DB Connect Failed: " . $this->dbHandle->connect_error);
return false;
}
$this->dbHandle->set_charset('utf8mb4');
// 自動建表 (InnoDB + 索引優化)
$sql = "CREATE TABLE IF NOT EXISTS `{$this->config['table']}` (
`session_id` VARCHAR(255) NOT NULL,
`session_data` LONGBLOB NOT NULL,
`session_expires` INT UNSIGNED NOT NULL,
PRIMARY KEY (`session_id`),
KEY `expires_idx` (`session_expires`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
$this->dbHandle->query($sql);
return true;
}
public function close(): bool {
return $this->dbHandle?->close() ?? true;
}
public function read(string $id): string|false {
if (empty($id)) return '';
$stmt = $this->dbHandle->prepare(
"SELECT `session_data` FROM `{$this->config['table']}` WHERE `session_id` = ? AND `session_expires` > ?"
);
$stmt->bind_param('si', $id, time());
$stmt->execute();
$stmt->bind_result($data);
$stmt->fetch();
$stmt->close();
return $data ?? '';
}
public function write(string $id, string $data): bool {
if (empty($id)) return false;
$expires = time() + $this->maxlifetime;
$stmt = $this->dbHandle->prepare(
"REPLACE INTO `{$this->config['table']}` (`session_id`, `session_data`, `session_expires`) VALUES (?, ?, ?)"
);
$stmt->bind_param('ssi', $id, $data, $expires);
$result = $stmt->execute();
$stmt->close();
return $result;
}
public function destroy(string $id): bool {
$stmt = $this->dbHandle->prepare(
"DELETE FROM `{$this->config['table']}` WHERE `session_id` = ?"
);
$stmt->bind_param('s', $id);
$result = $stmt->execute();
$stmt->close();
return $result;
}
public function gc(int $maxlifetime): int|false {
$stmt = $this->dbHandle->prepare(
"DELETE FROM `{$this->config['table']}` WHERE `session_expires` < ?"
);
$stmt->bind_param('i', time());
$stmt->execute();
$affected = $stmt->affected_rows;
$stmt->close();
return $affected;
}
}
提供靜態調用風格,簡化業務代碼:
class Session {
public static function start(bool $clean = false, string $token = ''): void {
if ($clean) ob_end_clean();
if ($token) session_id($token);
session_start();
}
public static function id(string $id = null): string {
return $id ? session_id($id) : session_id();
}
public static function get(string $name, $default = null) {
return $_SESSION[$name] ?? $default;
}
public static function set(string $name, $value): bool {
$_SESSION[$name] = $value;
return true;
}
public static function delete(string $name): bool {
if (!isset($_SESSION[$name])) return false;
unset($_SESSION[$name]);
return true;
}
public static function destroy(): void {
session_destroy();
}
}
$config = [
'host' => '127.0.0.1',
'user' => 'session_user',
'password' => 'strong_password',
'database' => 'app_sessions',
'table' => 'user_sessions'
];
SessionHandler::init($config);
Session::start();
Session::set('profile', [
'id' => 1,
'username' => 'haowei',
'vip' => 6
]);
$user = Session::get('profile');
// 輸出: ['id'=>1, 'username'=>'haowei', 'vip'=>6]
p:host) 或 Swoole/Workerman 連接池減少握手開銷。gzcompress/igbinary 序列化減少網絡傳輸。session_id 哈希分表,避免單表過大。這套方案已在千萬級用戶系統穩定運行,配合 Nginx upstream 一致性哈希,完美解決了分佈式 Session 共享難題。