對于大訪問量的網站來說,會有許多的客戶端和服務端建立鏈接,就會生成許多 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,代碼如下:
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 類,代碼如下:
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 的腳本,代碼如下:
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 中對雙引號做了轉義而已。