对会话有一定了解的都知道,http 协议本来是无状态的,为了保证用户登陆态,带出来了 session 会话的机制。
PHP 中的会话
Session 会话通常是保存在服务端的一个 ticket 票据,已证明用户是该会话的归属者,以 PHP 为例,通常我们可以在 php.ini 中设置会话的存储位置,亦或是自行定义一个会话的处理类作为会话的管理器,以下简单的介绍了 PHP 中的会话管理器以及相关函数。
Session 管理器
- session_set_save_hanler: 支持 files、DB、Memcache、Redis 等;
- 会话管理器通常需要实现:
- open(string $savePath, string $sessionName) : 会话打开的时候会被调用
- write(string $sessionId, string $data) : 在会话保存数据时会调用 write 回调函数。
- close: 在 write 回调函数调用之后调用,session_wrire_close()执行后也会被调用;
- read(string $sessionId) : 如果会话中有数据,read 回调函数必须返回将会话数据编码(序列化)后的字符串。
- destroy($sessionId) : 干掉会话,当 session_destory()或者 session_regenerate_id()执行时候生效。
- gc($lifetime): 为了清理会话中的旧数据,PHP 会不时的调用垃圾收集回调函数。
- create_sid() : 创建会话 id。
相关的会话处理函数
结束当前会话以及会话存储: session_write_close/session_commit
默认情况下,无需调用该函数,因为在 PHP 执行完后会调用 session_register_shutdown(),默认关闭会话函数就是session_write_close()
作为会话数据,在任何时间段内,仅允许一个脚本程序进行会话数据写入操作(考虑到数据一致性),此时其他脚本程序无法进行该会话数据操作,因为会话数据被 lock 住了(互斥访问),此时就出现了session锁问题
了,即一个脚本执行完才,另一个脚本才被执行,即开头遇到的问题;
Session 释放
- 会话 ID 支持 COOKIE 或者 URL 传递
- 清除会话
$_SESSION = []
,重置当前整个会话的环境变量数据,session_destory()
,清理整个服务端内部存储的话数据;- 让客户端也做对应的 Cookie 清理,通过
set_cookie()
发送 HTTP 头信息给到客户端:set_cookie(session_name(), '' , time()-3600)
session 锁模拟
通过 a.php 的 page 在处理当前会话,通过 sleep(5)模拟一个长时间操作,比如 mysql 读写(正常情况不应该有这么久)
通过访问 b.php,对统一会话进行读取操作,发现被阻塞,直至 a.php 完成才可以读取成功!
|
|
小结
出现 Session 锁的本质原因,还是由于脚本的执行时间过长,导致 session 锁长时间被占用无法被释放。比如,脚本中存在较长耗时的网络调用、大数据查询等,都会导致 session 锁被占用,若在会话锁未释放之前,存还有一些程序(比如 ajax 请求)也依赖于该会话开启,就会导致该脚本程序的阻塞,浏览器也一直处于pending
状态。
针对session锁
这块,可以考虑通过使用更快的读写缓存(比如 Redis 进行统一存储),避免使用文件存储会话信息;同时可以在会话数据设置完后或者信息读取后,主动调用session_write_close
结束会话;
若以 Redis 作为会话存储管理,在高并发的网站上面,需要充分考虑到作为 Redis 的会话管理也可能成为瓶颈(频繁的 Redis 连接、以及读写操作)
另外,理论上是不应该存在同一用户在同一时刻进行高频的进行会话操作;