首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 多 Web 伺服器共享 Session 資料:將 Session 寫入 MySQL 資料庫

     編輯:PHP 相關知識

前言

隨著網站規模擴大,單一伺服器已無法滿足流量需求,常見架構為多台 Web 伺服器分別運行不同功能模組,並使用不同的二級域名。雖然用戶資料可透過共享資料庫實現統一存取,但「一次登入,到處通行」的 Session 共享問題仍待解決。本文詳細說明 PHP Session 運作原理,並示範如何透過自定義 Session 處理器將 Session 資料寫入 MySQL,實現多伺服器間的 Session 共享。

PHP 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 的障礙與解決方向

預設情況下,各伺服器獨立生成 Session ID,且 Session 檔案分散於各自的檔案系統,導致無法共享。要實現共享,需達成兩大目標:

  1. 統一 Session ID:所有伺服器必須能讀取同一個名為 PHPSESSID 的 Cookie。只需設定 Cookie 的 domain 參數為共同的根域名(如 .example.com),即可讓二級域名間共享 Cookie。
  2. 共享 Session 儲存:將 Session 資料集中存放於所有伺服器均可存取的儲存介質,例如 MySQL 資料庫、Redis 或共享檔案系統(NFS)。本文採用 MySQL 方案。

資料庫結構設計

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 ID
  • expiry:過期時間戳(Unix timestamp)
  • data:序列化後的 Session 資料

自定義 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;
?>

注意事項與優化建議

  • 資料庫連線:建議使用持久連線或連線池減少開銷。
  • 效能:高併發下 MySQL 可能成為瓶頸,可考慮改用 Redis 或 Memcached 作為 Session 儲存後端。
  • 安全性:確保 Session ID 足夠隨機,啟用 session.cookie_httponly 與 session.cookie_secure(HTTPS 環境)。
  • 時區一致:所有伺服器時間必須同步(NTP),否則 GC 判斷會出現偏差。

總結

透過設定共享 Cookie 域名與自定義 Session 處理器將資料寫入 MySQL,即可輕鬆實現多 Web 伺服器間的 Session 共享,滿足「一次登入,到處通行」的業務需求。實際部署時諈根據流量規模選擇合適的儲存後端,並做好監控與備份策略。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved