今天我来谈谈全部的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>