PHP session并发操作及session读写锁

PHPABC PHP开发 766 次浏览 , , 没有评论

今天我来谈谈所有的PHPer都熟悉的session。

Case

1.示例代码中分别以files,redis储存会话数据

2./session/setUserFile和/session/setUserRedis设置user_name,user_id两个key,并sleep了3s

3./session/setLoginFile和/session/setLoginRedis设置last_time一个key

4./session/indexFile和/session/indexRedis模板中两个ajax请求,/session/setUserFile和/session/setUserRedis立即执行,/session/setLoginFile和/session/setLoginRedis延迟300ms,是为了模拟同一个用户,同时在两个页面(请求)修改会话数据

Files执行结果

请求:/session/indexfile

第一次访问:

PHP session并发操作及session读写锁

第二次访问:

PHP session并发操作及session读写锁

请求:/session/getsessionfile

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)

Redis执行结果

请求:/session/indexredis

第一次访问:

PHP session并发操作及session读写锁

第二次访问:

PHP session并发操作及session读写锁

请求:/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

PHP session并发操作及session读写锁

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<?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;
}
}

indexfile.phtml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!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>

indexredis.phtml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!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>

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

Go