以下内容均只针对原生PHP-SESSION
0、session的使用过程
原生php-session的存储原理,简单来说也是磁盘IO操作,把内容加密之后用hash,session_id的方法,根据php.ini
的session.save_path
存储路径,生成文件存储在指定磁盘空间中。
下面我们来看下session的生成过程:
// 开启session,该操作会生成一个0kb的session文件
session_start();
// 更新session文件内容
$_SESSION['key'] = 1;
// 清除该次会话中的所有session
session_destroy();`
从上面我们很容易发现会不会有这样的bug,如果将session_start();
定义在全局的文件,那每一次的请求,是不是就会生成一个空的session文件呢?
这样的低级错误,很容易就会造成巨大的IO消耗,为什么会造成IO消耗呢,我们接着往下面看。
1、session过期时间
原生PHP-session基本是设置不了过期时间的,原因有几个:
1、没办法设置单个session-key的过期时间,session的过期时间是全局通用的。
2、session_id默认是使用cookies存储在客户端的,当客户端失效,实际上session也就失效了。
3、session回收机制的限制,php.ini中有个session.gc_maxlifetime参数,默认为24分钟(php7)、180分钟(php5),他表示所有session的有效期,超过该参数时就清理掉该session。
在现有的PHP框架中,例如ThinkPHP、Laraval等框架中,session封装的有效过期时间都是相对的,都是使用session存储时,拼接上一个时间戳记录第一次存储时间,进而判断下一次获取时的超期时间。
原生session的真正有效期控制,只能将存储机制从file改为redis。
2、session文件回收机制
上面2节我们都有提到过GC回收机制,该机制是原生PHP用于回收过期session的PHP进程。
该参数依赖php.ini
中的3个参数:
session.gc_maxlifetime = 1440; :session过期回收时间
session.gc_probability = 1; :回收概率
session.gc_divisor = 100; :线程回收因子(除数)
GC回收机制时根据session.gc_probability
和session.gc_divisor
的配置,进行计算,公式是gc_probability / gc_divisor = 启动概率
,默认的概率是1%
,其表示,每一次调用session_start()
初始化session的时候,都有1%
的机会触发GC回收检测。
这个GC回收检测就是本文的重中之重了。
GC回收每一次被触发,都会根据session.save_path
存储路径,递归遍历整个目录,找到每个session.gc_maxlifetime
过期的session,进行删除。
问题就在这里,默认的session.save_path
是存储在单个目录下的,当我们用户量越来越强大的时候,该目录下有效的session文件可能会保持在十万级以上的数量,每一次触发GC都需要遍历整个目录,IO消耗可想而知。
为了应对这种场景,session.save_path
提供了一个配置项,为:session.save_path=N;存储根地址
,其中的N
表示需要递归存储的目录层数,正常情况下,我们只能填3
,建议不要超过这个数,因为他是根据php源码中ext/session
目录下的mod_files.sh
脚本创建的目录,该脚本是hash递归生成的目录,3层则为16 * 16 * 16
个目录,其算法可以阅读该文件源码,很简单的。
我们修改完session.save_path=N;存储根地址
为多层目录后,记得调用下mod_files.sh
,生成我们需要的目录层数,使用方法百度下就有了,很多文档。
3、session文件为空,0KB的坑
这个坑跟GC回收机制有关,上面我们说过,GC回收是递归遍历整个根目录,检测过期时间删除文档。
那问题来了session_start()
会创建一个0kb
的session文件,假设我们全局调用session_start()
那这个bug就来了,假设应用中有100
个接口,正常只有10
个会应用到session,然后假设正常session数为30分钟10W个
,那就有可能造成30分钟100W个
的情况,而GC回收的效率是有限的,那就会造成删除还不如追加来的快,最后IO被占满的严重BUG。
4、善用Session_write_close
session_write_close()
从函数名我们就可以看出,该函数是用于立即存储session文件,并关闭session话,常用在php-socket或长时间运行的PHP脚本中。
因为PHP-session默认是file存储方式,为了防止并发操作,每次session_start()
会打开一个文件占用锁,直到该次会话结束,session_write_close()
就是解决session文件占用,导致PHP文件还在运行中,另一个程序页面只能等待访问的BUG。
而且我们使用session_write_close()
函数时候,需要非常小心,千万注意别再session_start()
之后就用了该函数。