今天我來談談全部的PHPer都熟悉的session。javascript
1.示例代碼中分別以files,redis儲存會話數據php
2./session/setUserFile和/session/setUserRedis設置user_name,user_id兩個key,並sleep了3shtml
3./session/setLoginFile和/session/setLoginRedis設置last_time一個keyjava
4./session/indexFile和/session/indexRedis模板中兩個ajax請求,/session/setUserFile和/session/setUserRedis當即執行,/session/setLoginFile和/session/setLoginRedis延遲300ms,是爲了模擬同一個用戶,同時在兩個頁面(請求)修改會話數據jquery
請求:/session/indexfileajax
第一次訪問:redis
第二次訪問:json
請求:/session/getsessionfilesession
array(3) { [「user_name」]=> string(10) 「xudianyang」 [「user_id」]=> string(3) 「123」 [「last_time」]=> int(1419411695) }併發
以文件保存會話數據結論:/session/setUserFile執行時間爲3.1s,/session/setLoginFile執行時間爲2.81s(若是加上ajax延遲恰好兩個請求的響應時間都是3.1s)
請求:/session/indexredis
第一次訪問:
第二次訪問:
請求:/session/getsessionredis
array(2) { [「user_name」]=> string(10) 「xudianyang」 [「user_id」]=> string(3) 「123」 }
手冊中有這樣的描述:
void session_write_close ( void )
End the current session and store session data.
Session data is usually stored after your script terminated without the need to call session_write_close(), but as session data is locked to prevent concurrent writes only one script may operate on a session at any time. When using framesets together with sessions you will experience the frames loading one by one due to this locking. You can reduce the time needed to load all the frames by ending the session as soon as all changes to session variables are done.
也就是說session是有鎖的,爲防止併發的寫會話數據。php自帶的的文件保存會話數據是加了一個互斥鎖(session_start()的時候),從而解釋了上面呈現的兩個請求響應時間相同。可是以redis保存會話數據時,第二個ajax雖然沒有阻塞,可是會話數據並無寫入到redis,那咱們追溯一下源碼就有答案了。
php-5.4.14源碼
默認的files的save_handler
php-5.4.14/ext/session/mod_files.c
redis的save_handler
phpredis中的redis_session.c中並沒有實現session讀寫鎖的機制,那上述如何解釋呢?其實若是咱們將session的源碼大體瀏覽一下,就能夠解釋了。由於會話在session_start以後,將會話數據就會讀取到$_SESSION(在擴展內部全局變量PS(http_session_vars))中,在PHP_RSHUTDOWN_FUNCTION(session)(請求釋放)時,經過php_session_flush(TSRMLS_C)將會話數據寫入儲存介質,也就是說會話的數據並非實時寫入到儲存介質的。
這就解釋了,爲何redis保存會話數據時,/session/setLoginRedis看似未將last_time寫入到redis,實質是寫了,可是當/session/setUserRedis請求釋放時(因爲最開始會話數據中並沒有last_time這個key),要將全部的會話數據寫入到儲存介質,從而覆蓋了/session/setLoginRedis請求寫的值,到此解釋了上述的問題。
Session.php
<?php final class SessionController extends \Yaf\Controller_Abstract { public function setUserFileAction() { session_start(); $_SESSION['user_name'] = 'xudianyang'; $_SESSION['user_id'] = '123'; sleep(3); echo json_encode($_SESSION); return false; } public function setLoginFileAction() { session_start(); $_SESSION['last_time'] = time(); echo json_encode($_SESSION); return false; } public function indexFileAction() { // Auto Rend View } public function getSessionFileAction() { session_start(); var_dump($_SESSION); return false; } public function setUserRedisAction() { $session = \Core\Factory::session(); $session->set('user_name', 'xudianyang'); $session->set('user_id', '123'); sleep(3); echo json_encode($_SESSION); return false; } public function setLoginRedisAction() { $session = \Core\Factory::session(); $session->set('last_time', time()); echo json_encode($_SESSION); return false; } public function indexRedisAction() { // Auto Rend View } public function getSessionRedisAction() { $session = \Core\Factory::session(); var_dump($_SESSION); return false; } }
<!DOCTYPE html> <html> <head> <title>測試session併發鎖問題</title> <meta charset="utf-8"> <script type="text/javascript" src="/assets/js/jquery-1.10.2.min.js"></script> <script type="text/javascript"> $.ajax({ url: "/session/setUserFile", type: "get", dataType: "json", success: function(response){ console.info(response.last_time); } }); setTimeout(function(){ $.ajax({ url: "/session/setLoginFile", type: "get", dataType: "json", success: function(response){ console.info(response.last_time); } }); }, 300); </script> </head> <body> 同時發起2兩個ajax請求 </body> </html>
<!DOCTYPE html> <html> <head> <title>測試session併發鎖問題</title> <meta charset="utf-8"> <script type="text/javascript" src="/assets/js/jquery-1.10.2.min.js"></script> <script type="text/javascript"> $.ajax({ url: "/session/setUserRedis", type: "get", dataType: "json", success: function(response){ console.info(response.last_time); } }); setTimeout(function(){ $.ajax({ url: "/session/setLoginRedis", type: "get", dataType: "json", success: function(response){ console.info(response.last_time); } }); }, 300); </script> </head> <body> 同時發起2兩個ajax請求 </body> </html>