本篇博客將帶着大家實現使用緩存系統來存儲 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 僅停留在簡單使用的層面上,只知道這兩個東西是個很強大的工具。在查閱了很多資料之後,我對這兩個東西有了更深刻的瞭解。