以下内容均只针对原生PHP-SESSION

0、session的使用过程

原生php-session的存储原理,简单来说也是磁盘IO操作,把内容加密之后用hash,session_id的方法,根据php.inisession.save_path存储路径,生成文件存储在指定磁盘空间中。

下面我们来看下session的生成过程:

  1. // 开启session,该操作会生成一个0kb的session文件
  2. session_start();
  3. // 更新session文件内容
  4. $_SESSION['key'] = 1;
  5. // 清除该次会话中的所有session
  6. session_destroy();`

从上面我们很容易发现会不会有这样的bug,如果将session_start();定义在全局的文件,那每一次的请求,是不是就会生成一个空的session文件呢?

这样的低级错误,很容易就会造成巨大的IO消耗,为什么会造成IO消耗呢,我们接着往下面看。

1、session过期时间

原生PHP-session基本是设置不了过期时间的,原因有几个:

  1. 1、没办法设置单个session-key的过期时间,session的过期时间是全局通用的。
  2. 2session_id默认是使用cookies存储在客户端的,当客户端失效,实际上session也就失效了。
  3. 3session回收机制的限制,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个参数:

  1. session.gc_maxlifetime = 1440; session过期回收时间
  2. session.gc_probability = 1; :回收概率
  3. session.gc_divisor = 100; :线程回收因子(除数)

GC回收机制时根据session.gc_probabilitysession.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()之后就用了该函数。