對 PHP SESSION 的深刻認識(四)---- 緩存(memcache和redis)存儲session

本篇博客將帶着大家實現使用緩存系統來存儲 session 數據,其中會介紹兩個緩存系統 :memcache 和 redis。

一、使用 memcache:

如果大家有看過我之前的這篇博客 《memcache 和 memcached 的區別分析》,就會發現,PHP兩個擴展中的 memcached 工作的更好,因此這篇博客在使用 memcache 服務時我選擇的是 memcached 擴展。

1、使用 memcached 提供的 session 支持實現(最簡單的方法)

memcached 提供了一個自定義的 session 處理器可以被用於存儲用戶session 數據到 memcached 服務端。 一個完全獨立的 memcached 實例將會在內部使用,因此如果需要您可以設置一個不同的服務器池。

session 的 key 被存儲在前綴 memc.sess.key. 之下,因此, 如果你對session 和通常的緩存使用了同樣的服務器池,請注意這一點。 譯註:另外一個 session 和通常緩存分離的原因是當通常的緩存佔滿了memcached 服務端後,可能會導致你的 session 被從緩存中踢除,導致用戶莫名的掉線。

上面的話都是引用自 PHP 官方手冊:http://php.net/manual/zh/memcached.sessions.php

下面我們通過實例來實現將 session 數據存儲到 memcache。

第一步,設置session用memcache來存儲:

打開配置文件 php.ini ,修改以下兩項:

session.save_handler = memcached
session.save_path = "127.0.0.1:11211"

重啓服務器。

關於修改配置的話,如果你沒有修改 php.ini 的權限,或者你只想在當前應用中使用 memcache 來存儲 session 的話,可以使用局部的設置:

#在某個php文件中
ini_set('session.save_handler','memcached');
ini_set('session.save_path','127.0.0.1:11211');

配置好之後我們就可以使用了,非常簡單:

第二步,在代碼中使用會話:

#test1.php 文件

<?php

//如果你沒有修改配置文件 php.ini,則用下面兩行代碼
ini_set('session.save_handler','memcached');
ini_set('session.save_path','127.0.0.1:11211');

//開啓會話
session_start();

if(!isset($_SESSION['name'])){
    $_SESSION['name'] = 'default';
}else{
    $_SESSION['name'] = 'lsgogroup';
}
$_SESSION['age'] = 20;

echo session_id();  //獲取客戶端的sessionId,即 PHPSESSID,後面會用到

在瀏覽器中打開 test1.php,然後我們在 test2.php 中驗證是否操作成功。

#test2.php 文件

<?php

//如果你沒有修改配置文件 php.ini,則用下面兩行代碼
ini_set('session.save_handler','memcached');
ini_set('session.save_path','127.0.0.1:11211');

//開啓會話
session_start();

var_dump($_SESSION);

在瀏覽器中打開 test2.php ,返回:

array(2) { ["name"]=> string(7) "default" ["age"]=> int(20) }

我們在 test3.php 中驗證 session 是否是存儲到了 memcached 中,由於前面說了session 的 key 被存儲在前綴 memc.sess.key. 之下,因此當我們要從 memcached 中讀取 session數據,我們指定的 key 是 memc.sess.key.sessionId,其中 sessionId 我們在 test1.php 中輸出了,直接複製,或者從瀏覽器中的 cookie 中複製(我這裏輸出的是 g5ef37mnb7dstkf1kesegbajb7)。

#test3.php 文件
#這裏假設大家知道 memcached 的一些操作

<?php
$mem = new memcached();
$mem->addServer("127.0.0.1",11211);
echo $mem->get('memc.sess.key.g5ef37mnb7dstkf1kesegbajb7');

返回:

name|s:7:"default";age|i:20;

如果刷新 test1.php 頁面,test2.php 和 test3.php 的輸出如下:

#test2.php
array(2) { ["name"]=> string(9) "lsgogroup" ["age"]=> int(20) }
#test3.php
name|s:9:"lsgogroup";age|i:20;

由上面的 test2.php 和 test3.php 的輸出中我們成功的將 session 數據存儲到 memcached 中。

memcached 對於 session 的支持還有好幾個設置,可能大家都會用到,你如說前面的 key 的前綴設置默認是 memc.sess.key.,我們可以通過修改配置文件 php.ini,將配置項改成

memcached.sess_prefix = "zhongjin."

或這樣:

ini_set("memcached.sess_prefix","zhongjin.");

那麼你在 test3.php 中就可以這樣讀取 session 數據了:

echo $mem->get('zhongjin.g5ef37mnb7dstkf1kesegbajb7');

更多有用的配置項大家可以參考官方手冊 :http://php.net/manual/zh/memcached.configuration.php

第三步,必要了解過期 session 數據的清理

首先,使用 memcached,將 session 數據都存儲在內存中,一旦宕機,數據都會丟失,但對於 session 數據來說這其實並不是什麼嚴重的問題。

其次,前面官方手冊說道, session 和通常緩存分離的原因是當通常的緩存佔滿了memcached 服務端後,可能會導致你的 session 被從緩存中踢除,導致用戶莫名的掉線。怎麼理解?我們要從 memcache 淘汰數據的機制說起。

Memcached主要的cache機制是LRU(最近最少用)算法+超時失效(學過操作系統的同學應該會記憶幽深)。當您存數據到memcached中,可以指定該數據在緩存中可以呆多久。如果memcached的內存不夠用了,過期的slabs會優先被替換,接着就輪到最老的未被使用的slabs。

怎樣判斷session失效了呢?在php.ini中有個Session.cookie_lifetime的選項,這個代表SessionID在客戶端Cookie儲存的時間,默認值是“0”,代表瀏覽器一關閉,SessionID就作廢,這樣不管保存在Memcached中的Session是否還有效(保存在Memcached中的session會利用Memcached的內部機制進行處理,即使session數據沒有失效,而由於客戶端的SessionID已經失效,所以這個key基本上不會有機會使用了,利用Memcached的LRU原則,如果Memcached的內存不夠用了,新的數據就會取代過期以及最老的未被使用的數據),因爲SessionID已經失效了,所以在客戶端會重新生成一個新的SessionID。

我們再來解釋官方手冊的問題,通常的緩存是指我們在php中使用memcache來存儲數據庫查詢結果等數據,導致佔用大量的分配給 memcache 的內存,這樣可能有某個用戶的session數據會因此被淘汰掉,而此時用戶還在線,就會導致用戶意外掉線。所以官方手冊上建議將 session 和通常的緩存分離。

2、通過php提供的接口,自己改寫 session 的處理函數

通過自定義 session 的處理函數,我們能夠更加自如的控制 session 的存取。更加多的細節和思想大家參考我的上一篇博客 《對 PHP SESSION 的深刻認識(三)—- 數據庫存儲session》,在這裏我直接給出實現:

第一步,修改配置,告訴 php 引擎使用我們自己的session處理函數

打開配置文件 php.ini,修改配置項:

session.save_handler = user

同時註釋 session.save_path 項(在前面添加 ;).

或者:

#在某個php文件中 
ini_set('sesion.save_handler','user');

第二步,編寫會話函數

新建 session.inc.php(結構跟session存儲在數據庫中的代碼結構一樣),代碼如下:

#session.inc.php 文件

<?php

/**
 * Created by PhpStorm.
 * User: lsgozj
 * File: session.inc.php
 * Desc: 處理 session 的自定義類
 * Date: 16-12-13
 * Time: 下午2:45
 */
class memcachedSession implements SessionHandlerInterface
{

    private $_mem = null;   //memcached鏈接句柄
    //這些信息應該放在配置文件中。。。。
    private $_configs = array(
        'host' => '127.0.0.1',     //主機域
        'port' => 11211,           //端口
        'prefix' => '',              //key前綴
        'expire' => 0                //有效時間
    );

    public function __construct()
    {
        //默認獲取配置文件中的配置
        $this->_configs['prefix'] = ini_get('memcached.sess_prefix');
        $this->_configs['expire'] = ini_get('session.gc_maxlifetime');
    }

    //自定義session_start()函數
    public static function my_session_start()
    {
        $sess = new self;
        session_set_save_handler($sess);     //註冊自定義函數,在php5.4之後,session_set_save_handler()參數直接傳SessionHandlerInterface類型的對象即可。
        session_start();
    }

    /**
     * session_start() 開始會話後第一個調用的函數,類似於構造函數的作用
     * @param string $save_path 默認的保存路徑
     * @param string $session_name 默認的參數名(PHPSESSID)
     * @return bool
     */
    public function open($save_path, $session_name)
    {
        $mem = new memcached();
        $mem->addServer($this->_configs['host'], $this->_configs['port']);
        $this->_mem = $mem;
        return true;
    }

    /**
     * 類似於析構函數,在write()之後調用或者session_write_close()函數之調用
     * @return bool
     */
    public function close()
    {
        $this->_mem = null;
        return true;
    }

    /**
     * 讀取session信息
     * @param string $sessionId 通過該ID(客戶端的PHPSESSID)唯一確定對應的session數據
     * @return session信息或者空串(沒有存儲session信息)
     */
    public function read($sessionId)
    {
        //根據配置文件獲取前綴(當然也可以自定義)
        return $this->_mem->get($this->_configs['prefix'] . $sessionId);
    }

    /**
     * 寫入或修改session數據
     * @param string $sessionId 要寫入數據的session對應的id(PHPSESSID)
     * @param string $sessionData 要寫入的是數據,已經序列化過的
     * @return bool
     */
    public function write($sessionId, $sessionData)
    {
        return $this->_mem->set($this->_configs['prefix'] . $sessionId, $sessionData, $this->_configs['expire']);
    }

    /**
     * 主動銷燬session會話
     * @param string $sessionId 要銷燬的會話的唯一ID
     * @return bool
     */
    public function destroy($sessionId)
    {
        return $this->_mem->delete($this->_configs['prefix'] . $sessionId);
    }

    /**
     * 清理會話中的過期數據
     * @param int $maxlifetime 有效期(自動讀取配置文件 php.ini 中的 session.gc_maxlifetime 配置項)
     * @return bool
     */
    public function gc($maxlifetime)
    {
        return true;
    }
}

對以上代碼的必要解釋:

1、存儲時使用的 key 前綴和存儲的生命週期讀取的是配置文件 php.ini 的設置,如果需要自定義,在構造函數中修改或者直接在 _configs 數組中定義,然後刪除構造函數。
2、由於我們在write()函數中設置緩存的時候已經指定生命週期,過期的數據會由 memcache 自動清理,所以後面的 gc() 函數已經沒什麼意義了,直接 return true 即可。
3、在使用 memcached 存儲session數據的時候,有效時間不能超過30天。

我們在 test.php 文件中測試一下是否可用:

#test.php 文件

<?php
require_once('./session.inc.php');
memcachedSession::my_session_start();     //開啓會話

$_SESSION['name'] = 'LSGOZJ';
$_SESSION['age'] = 22;

var_dump($_SESSION);
echo '<br>'.session_id();   //獲取SESSIONID,我們後面測試會用到,我現在測試得到的是 g5ef37mnb7dstkf1kesegbajb7

在瀏覽器中訪問 test.php,然後我們在 test1.php 中看看memcached 中是否已經存儲了 session 數據:

#test1.php 文件

<?php
$mem = new memcached();
$mem->addServer('127.0.0.1',11211);
$prefix = ini_get("memcached.sess_prefix");
echo $mem->get($prefix."g5ef37mnb7dstkf1kesegbajb7");

在瀏覽器打開 test1.php 返回:

name|s:6:"LSGOZJ";age|i:22;

這個結果也能證明,我們已經成功的將 session 數據存儲到 memcached 中了。

第三步、簡單談談session的清理

在上面我們爲我們的每一條 session 設置了生命週期,如果到達這個時間該條session還沒有被訪問的話,那麼 memcached 視之爲垃圾數據,但是,並不是到期了就把該條 session 數據刪除,而是在下一次訪問該條 session 數據的時候,檢測是否到了有效期,過了有效期才把它從內存中刪除。(如果用輪詢的辦法每時每刻的檢測是否到期,那得多耗內存。。。。)

二、使用 redis:

由於使用 redis 和使用 memcache 是差不多的,因此這部分就快速的帶過了。

1、使用 redis 提供的 session 支持實現(最簡單的方法)

修改配置文件 php.ini:

session.save_handler = redis
session.save_path = 'tcp://127.0.0.1:6379'

或是:

#某個php文件
ini_set('session.save_handler','redis');
ini_set('session.save_path','tcp://127.0.0.1:6379');

2、通過php提供的接口,自己改寫 session 的處理函數

關於這一部分,跟上面 memcached 的內容非常像,就只有個別函數比如設置有效時間,redis使用setex()函數,等等跟memcached 不一樣,我就不再舉例子了。

總結:

1、“對 PHP SESSION 的深刻認識”這個小系列已經算是完成了。如果大家想了解 session 的原理等內容,可以回去看看我前面的幾篇文章《對 PHP SESSION 的深刻認識(一)》《對 PHP SESSION 的深刻認識(二)》 《對 PHP SESSION 的深刻認識(三)—- 數據庫存儲session》

2、在寫本篇博客和上一篇博客《memcache 和 memcached 的區別分析》之前,我對 memcached、redis 僅停留在簡單使用的層面上,只知道這兩個東西是個很強大的工具。在查閱了很多資料之後,我對這兩個東西有了更深刻的瞭解。

發佈了75 篇原創文章 · 獲贊 171 · 訪問量 43萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章