PHP使用Redis儲存Session

2020-07-16 10:05:13
對於大存取量的網站來說,會有許多的用戶端和伺服器端建立連結,就會生成許多 Session 檔案,由於 Session 檔案是儲存在硬碟上的,因此每次伺服器去讀取這些 Session 檔案都要經過許多的 I/O 操作。

PHP 中可使用 session_set_save_handle() 函數自定義 Session 儲存函數(如開啟、關閉、寫入、讀取等),其語法如下:

bool session_set_save_handler ( callable $open , callable $close , callable $read , callable $write , callable $destroy , callable $gc [, callable $create_sid ] )

如果想使用 PHP 內建的對談儲存機制之外的方式,可以使用本函數。例如,可以自定義對談儲存函數來將對談資料儲存到資料庫。

該函數的引數說明如下:

open(string$savePath,string$sessionName)

open 回撥函數類似於類別建構函式,在對談開啟的時候被呼叫。這是自動開始對談或者通過呼叫 session_start() 手動開始對談之後第一個被呼叫的回撥函數。

此回撥函數操作成功返回 true,反之返回 false。

close()

close 回撥函數類似於類的解構函式。在 write 回撥函數呼叫之後呼叫。當呼叫 session_write_close() 函數之後,也會呼叫 close 回撥函數。

此回撥函數操作成功返回 true,反之返回 false。

read(string$sessionId)

如果對談中有資料,那麼 read 回撥函數必須返回將對談資料編碼(序列化)後的字串。如果對談中沒有資料,read 回撥函數就返回空字串。

在自動開始對談或者通過呼叫 session_start() 函數手動開始對談之後,PHP 內部呼叫 read 回撥函數來獲取對談資料。在呼叫 read 之前,PHP 會呼叫 open 回撥函數。

read 回撥返回的序列化之後的字串格式必須與 write 回撥函數儲存資料時的格式完全一致。PHP 會自動反序列化返回的字串並填充 $_SESSION 超級全域性變數。雖然資料看起來和 serialize() 函數很相似,但是它們是不同的。

write(string$sessionId,string$data)

在對談儲存資料時會呼叫 write 回撥函數。此回撥函數接收當前對談 ID 以及 $_SESSION 中資料序列化之後的字串作為引數。

序列化對談資料的過程由 PHP 根據 session.serialize_handler 設定值來完成。序列化後的資料將和對談 ID 關聯在一起進行儲存。當呼叫 read 回撥函數獲取資料時,所返回的資料必須和傳入 write 回撥函數的資料完全保持一致。PHP 會在指令碼執行完畢或呼叫 session_write_close() 函數之後呼叫此回撥函數。

注意,在呼叫完此回撥函數之後,PHP 內部會呼叫 close 回撥函數。

PHP 會在輸出流寫入完畢並且關閉之後才呼叫 write 回撥函數,所以在 write 回撥函數中的偵錯資訊不會輸出到瀏覽器中。如果需要在 write 回撥函數中使用偵錯輸出,建議將偵錯輸出寫入到檔案。

destroy($sessionId)

當呼叫 session_destroy() 函數,或者呼叫 session_regenerate_id() 函數並且設定 destroy 引數為 true 時會呼叫此回撥函數。

此回撥函數操作成功返回 true,反之返回 false。

gc($lifetime)

為了清理對談中的舊資料,PHP 會不時地呼叫垃圾收集回撥函數。呼叫周期由 session.gc_probability 和 session.gc_divisor 引數控制。傳入到此回撥函數的 lifetime 引數由 session.gc_maxlifetime 設定。

此回撥函數操作成功返回 true,反之返回 false。

create_sid()

當需要新的對談 ID 時被呼叫的回撥函數。回撥函數被呼叫時無傳入引數,其返回值應該是一個字串格式的、有效的對談 ID。

PHP 使用 Redis 來儲存 Session

下面舉一個關於使用 Redis 代替檔案儲存 Session 的例子。

首先編寫一個管理 Session 的類 sessionmanager,程式碼如下:
<?php
class sessionmanager{
    private $redis;
    private $sessionsavepath;
    private $sessionname;
    public function __construct(){
        $this->redis = new Redis();
        $this->redis->connect('10.116.19.14',6400);
        $reval = 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($patn,$name){
        return true;
    }
    public function close(){
        return true;
    }
    public function read($id){
        $value = $this->redis->get($id);
        if($value) {
            return $value;
        } else {
            return false;
        }
    }
    public function write($id,$data){
        if($this->redis->set($id,$data)) {
            $this->redis->expire($id,60);
                return true;
        } else {
                return false;
        }
    }
    public function destroy($id) {
        if($this->redis->delete($id)) {
            return true;
        }
        return false;
    }
    public function gc($maxlifetime){
        return true;
    }
    public function __destruct(){
        session_write_close();
        //TODO: Implement __destruct() method.
    }
}
?>
將以上程式碼儲存為 sessionmanager.php 檔案。

在該類別建構函式中,使用 session_set_save_handler() 設定 Session 的處理常式,範例化該類時便完成了用指定函數接管系統處理 Session 的工作。

在 write 回撥函數中,以傳入的 sessionID 作為 key,以 Session 的值作為 redis 中 key 的值存入 Redis,並設定過期時間為 60 秒;read 方法以傳入的 sessionID 為 key 從 Redis 取出相應的 Session 值。destroy 可根據傳入的 sessionID 刪除 Redis 中的 Session。

我們編寫另外一個設定 Session 的指令碼,並引入 sessionmanager.php 檔案,範例化 sessionmanager 類,程式碼如下:
<?php
include 'sessionmanager.php';
new sessionmanager();
$_SESSION['namehaha'] = 'lixiaolong';
$_SESSION['namehah'] = 'lixiaolong';
$_SESSION['namehaa'] = 'lixiaolong';
$_SESSION['namhaha'] = 'lixiaolong';
$_SESSION['namhaha'] = array('a'=>1,2,3,4,4);
?>
儲存以上程式碼為 set.php,另外編寫一個可存取 Session 的指令碼,程式碼如下:
<?php 
include 'sessionmanager.php';
new sessionmanager();
var_dump($_SESSION);
?>
儲存以上程式碼為 get.php 檔案。測試時先存取 set.php,再存取 get.php,會在瀏覽器中輸出以下結果:

array(4)
{
    ["namehaha"]=> string(10) "lixiaolong"
    ["namehah"]=> string(10) "lixiaolong"
    ["namehaa"]=> string(10) "lixiaolong"
    ["namhaha"]=> array(5)
    {
        ["a"]=> int(1)
        [0]=> int(2)
        [1]=> int(3)
        [2]=> int(4)
        [3]=> int(4)
    }
}

可見已經成功地設定並獲得了 Session。檢視 redis 中儲存的 Session 資訊:

redis 127.0.0.1:6400> get ruevh62hlm809d1p2lg2o0fbv7
"namehaha|s:10:"lixiaolong";namehah|s:10:"lixiaolong"; namehaa|s:10:"lixiaolong";namhaha|a:5:{s:1:"a";i:1;i:0;i:2;i:1;i:3;i:2;i:4;i:3;i:4;}"

redis 中是以 string 的資料型別儲存 Session 的,其 key 便是 sessionID,也是 HTTP Request 中的 cookie 名為 PHPSESSID 的值。Session 在 redis 和檔案中的儲存形式是一樣的,只不過在 redis 中對雙引號做了跳脫而已。