隨著網站規模擴大,單一伺服器已無法滿足流量需求,常見架構為多台 Web 伺服器分別運行不同功能模組,並使用不同的二級域名。雖然用戶資料可透過共享資料庫實現統一存取,但「一次登入,到處通行」的 Session 共享問題仍待解決。本文詳細說明 PHP Session 運作原理,並示範如何透過自定義 Session 處理器將 Session 資料寫入 MySQL,實現多伺服器間的 Session 共享。
當用戶首次訪問 PHP 頁面並呼叫 session_start() 時,PHP 會生成唯一的 Session ID(可透過 session_id() 取得)。Session ID 預設以 Cookie 形式儲存於客戶端(Cookie 名稱通常為 PHPSESSID),也可透過 URL 傳遞(僅限 Unix 系統)。Session 資料本身儲存在伺服器端,預設以檔案方式存放(session.save_handler = files),檔案名稱為 sess_ 加上 Session ID,內容為序列化後的資料。高流量時可設定分級目錄(session.save_path = "N;/save_path")提升效能。
預設情況下,各伺服器獨立生成 Session ID,且 Session 檔案分散於各自的檔案系統,導致無法共享。要實現共享,需達成兩大目標:
PHPSESSID 的 Cookie。只需設定 Cookie 的 domain 參數為共同的根域名(如 .example.com),即可讓二級域名間共享 Cookie。CREATE TABLE `sess` (
`sesskey` varchar(32) NOT NULL DEFAULT '',
`expiry` bigint(20) NOT NULL DEFAULT 0,
`data` longtext NOT NULL,
PRIMARY KEY (`sesskey`),
KEY `expiry` (`expiry`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
sesskey:Session IDexpiry:過期時間戳(Unix timestamp)data:序列化後的 Session 資料PHP 提供 session_set_save_handler() 允許自定義 Session 的開啟、關閉、讀取、寫入、銷毀與垃圾回收(GC)六個操作。首先需將 session.save_handler 設為 user(可用 session_module_name('user'))。以下採用物件導向方式封裝:
<?php
define('MY_SESS_TIME', 3600); // Session 生存秒數
class My_Sess {
static public $db; // 資料庫連線物件(靜態屬性)
public function __construct($db_conn) {
self::$db = $db_conn;
}
public function init($domain = '') {
// 禁用 URL 傳遞 Session ID
ini_set('session.use_trans_sid', 0);
// 設定 GC 最大生存時間
ini_set('session.gc_maxlifetime', MY_SESS_TIME);
// 使用 Cookie 儲存 Session ID
ini_set('session.use_cookies', 1);
ini_set('session.cookie_path', '/');
ini_set('session.cookie_domain', $domain); // 設定共享域名,如 .example.com
// 註冊自定義處理器
session_module_name('user');
session_set_save_handler(
array('My_Sess', 'open'),
array('My_Sess', 'close'),
array('My_Sess', 'read'),
array('My_Sess', 'write'),
array('My_Sess', 'destroy'),
array('My_Sess', 'gc')
);
session_start(); // 必須在 session_set_save_handler 之後呼叫
}
// 開啟 Session(此處無需實際操作,回傳 true 即可)
public static function open($save_path, $session_name) { return true; }
// 關閉 Session,釋放資料庫連線
public static function close() {
if (self::$db) { self::$db->close(); }
return true;
}
// 讀取 Session 資料
public static function read($sesskey) {
$sql = "SELECT `data` FROM `sess` WHERE `sesskey` = " . self::$db->qstr($sesskey) . " AND `expiry` >= " . time();
$rs = self::$db->execute($sql);
if ($rs && !$rs->EOF) {
$val = $rs->fields[0];
$rs->close();
return $val;
}
return '';
}
// 寫入 Session 資料(使用 REPLACE INTO 實現 upsert)
public static function write($sesskey, $data) {
$expiry = time() + MY_SESS_TIME;
$arr = array(
'sesskey' => $sesskey,
'expiry' => $expiry,
'data' => $data
);
self::$db->replace('sess', $arr, 'sesskey', true);
return true;
}
// 銷毀 Session
public static function destroy($sesskey) {
$sql = "DELETE FROM `sess` WHERE `sesskey` = " . self::$db->qstr($sesskey);
self::$db->execute($sql);
return true;
}
// 垃圾回收:刪除過期資料並優化資料表
public static function gc($maxlifetime = null) {
$sql = "DELETE FROM `sess` WHERE `expiry` < " . time();
self::$db->execute($sql);
// 定期優化資料表減少碎片
$sql = "OPTIMIZE TABLE `sess`";
self::$db->execute($sql);
return true;
}
}
?>
在所有需共享 Session 的 PHP 入口腳本(或共用設定檔)中引入上述類別並初始化:
<?php
require_once 'My_Sess.php';
// 假設 $db 為已建立的 ADOdb 或 PDO 連線物件
$sess = new My_Sess($db);
$sess->init('.example.com'); // 傳入共享的根域名
// 之後即可正常使用 $_SESSION
$_SESSION['user_id'] = 123;
?>
session.cookie_httponly 與 session.cookie_secure(HTTPS 環境)。透過設定共享 Cookie 域名與自定義 Session 處理器將資料寫入 MySQL,即可輕鬆實現多 Web 伺服器間的 Session 共享,滿足「一次登入,到處通行」的業務需求。實際部署時諈根據流量規模選擇合適的儲存後端,並做好監控與備份策略。