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

PHP 多伺服器共享 Session 資料完整解決方案

     編輯:PHP 相關知識

一、問題背景:分布式架構下的登入難題

隨著網站規模擴大,單一伺服器已無法滿足流量需求。典型的架構會部署多台 Web 伺服器,各自運行不同功能模組(如論壇、商城、部落格),透過不同二級域名對外服務。雖然用戶資料可集中存放於資料庫實現共享,但「一次登入,處處通行」的 Session 共享問題卻成為開發難點:用戶在伺服器 A 登入後,跳轉至伺服器 B 卻需重新登入。

二、PHP Session 運作機制深度解析

Session ID 的生成與傳遞

當客戶端首次訪問並呼叫 session_start() 時,PHP 會生成唯一的 Session ID(可透過 session_id() 取得)。此 ID 須保留在客戶端,以便後續請求識別身份,主要有兩種方式:

  • URL 重寫:自動附加於 GET 參數或 POST 表單中,預設參數名為 PHPSESSID
  • Cookie 儲存:將 Session ID 存入 Cookie,預設 Cookie 名稱同為 PHPSESSID(主流做法)

Session 資料的伺服器端儲存

Session 資料預設以檔案形式儲存於伺服器(session.save_handler = files),路徑由 session.save_path 指定,檔案命名格式為 sess_{SessionID},內容為序列化後的資料。高流量下可啟用分級目錄(session.save_path="N;/save_path")提升 I/O 效能。

三、多伺服器共享 Session 的核心障礙與破解之道

預設情況下,各伺服器獨立生成 Session ID 且資料各自儲存在本地檔案系統,導致無法互通。解決方案需達成雙重目標:

  1. 統一 Session ID:所有伺服器對同一客戶端產生相同 Session ID,並能讀取同一 Cookie
  2. 共享儲存介質:Session 資料儲存位置需為所有伺服器可訪問的共享資源

關鍵突破:Cookie Domain 設定

預設 Cookie 僅屬當前域名。將 session.cookie_domain 設為共同根域名(如 .example.com),即可讓所有子域名伺服器共享同一 PHPSESSID Cookie。

四、資料庫儲存方案完整程式碼實作

1. 建立 Session 資料表

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;

sesskey 對應 Session ID,expiry 記錄過期時間戳,data 存放序列化資料。

2. 自定義 Session 處理器

透過 session_set_save_handler() 覆寫六大核心操作(open、close、read、write、destroy、gc),並將 session.save_handler 設為 user。

3. 物件導向實作範例(採用 ADOdb 抽象層)

<?php
define('MY_SESS_TIME', 3600); // Session 生存秒數

class My_Sess {
    static function init() {
        $domain = '.example.com'; // 修改為您的根域名
        
        ini_set('session.use_trans_sid', 0);      // 禁用 URL 重寫
        ini_set('session.gc_maxlifetime', MY_SESS_TIME);
        ini_set('session.use_cookies', 1);        // 啟用 Cookie
        ini_set('session.cookie_path', '/');      // 路徑根目錄
        ini_set('session.cookie_domain', $domain); // 關鍵:共享域
        
        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')
        );
    }

    static function open($save_path, $session_name) { return true; }

    static function close() {
        global $MY_SESS_CONN;
        if ($MY_SESS_CONN) $MY_SESS_CONN->Close();
        return true;
    }

    static function read($sesskey) {
        global $MY_SESS_CONN;
        $sql = 'SELECT data FROM sess WHERE sesskey=' . $MY_SESS_CONN->qstr($sesskey) . ' AND expiry>=' . time();
        $rs = &$MY_SESS_CONN->Execute($sql);
        if ($rs && !$rs->EOF) {
            $v = $rs->fields[0];
            $rs->Close();
            return $v;
        }
        return '';
    }

    static function write($sesskey, $data) {
        global $MY_SESS_CONN;
        $qkey = $MY_SESS_CONN->qstr($sesskey);
        $expiry = time() + MY_SESS_TIME;
        $arr = array('sesskey' => $qkey, 'expiry' => $expiry, 'data' => $data);
        $MY_SESS_CONN->Replace('sess', $arr, 'sesskey', $autoQuote = true);
        return true;
    }

    static function destroy($sesskey) {
        global $MY_SESS_CONN;
        $sql = 'DELETE FROM sess WHERE sesskey=' . $MY_SESS_CONN->qstr($sesskey);
        $MY_SESS_CONN->Execute($sql);
        return true;
    }

    static function gc($maxlifetime = null) {
        global $MY_SESS_CONN;
        $MY_SESS_CONN->Execute('DELETE FROM sess WHERE expiry<' . time());
        $MY_SESS_CONN->Execute('OPTIMIZE TABLE sess'); // 碎片整理
        return true;
    }
}

// 資料庫連線設定
require_once('adodb/adodb.inc.php');
$db_type = 'mysql';
$db_host = '192.168.1.100';
$db_user = 'sess_user';
$db_pass = 'sess_pass';
$db_name = 'sess_db';

$GLOBALS['MY_SESS_CONN'] =& ADONewConnection($db_type);
$GLOBALS['MY_SESS_CONN']->Connect($db_host, $db_user, $db_pass, $db_name);

// 必須在 session_start() 之前呼叫!
My_Sess::init();
?>

五、效能考量與進階優化建議

高併發場景下,資料庫頻繁讀寫成為瓶頸。建議考慮:

  • 記憶體儲存方案:使用 Redis、Memcached 或自建高效能 Hash 表 + Socket 服務,將 Session 保持在記憶體中
  • 負載均衡策略:配合 Sticky Session(基於 IP 或 Cookie 的會話親和性)減少跨節點存取
  • 非同步寫入:採用 Write-Behind 模式,先更新記憶體再定期持久化

選擇方案時需權衡一致性、可用性與延遲需求,資料庫方案適合中小型專案,大型系統建議採用分散式快取叢集。

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