Heim >php教程 >php手册 >PHP Session有效期的相关问题

PHP Session有效期的相关问题

WBOY
WBOYOriginal
2016-06-13 09:38:261049Durchsuche

Session处理是所有的Web应用都必须面对的问题。PHP中对session有效期的处理,和其他的解决方案有着很大的不同,这是和PHP的工作机制相关的。

在传统的client/server应用中,对于session失效的情况,可以交给网络协议自己来处理。无论是client端主动关闭连接,还是因为网络异常而导致的连接中断,server端都能够得到通知,触发连接中断的事件。只要编程响应这一事件,执行指定的操作即可。但对于web应用来说,情况却完全不一样。HTTP协议本身是无状态的,也就是说,每当client/server完成一次请求/响应的过程后,连接就会被断开。在断开连接以后,server并不知道client是否继续“在线”,还会继续发送下一次请求。换句话说,无论client端的用户已经关闭了浏览器窗口,还是用户仅仅在阅读当前网页并准备在下一秒钟继续浏览,或者用户因为Windows崩溃/停电/硬盘坏掉/网线被拔/地球爆炸而彻底无法再发送下一个请求,server都一无所知。(在HTTP 1.1中,浏览器可以通过keep-alive参数,来通知server不要在响应请求后主动断开连接,从而实现物理上的长连接。但是,这只是为了提高网络传输的性能而采取的措施,HTTP在逻辑上仍然是无状态的。)因此,只能通过某种模拟的方式来判断当前session是否有效。如果某个session在超过一段时间后没有对server端发出请求,server都会判断用户已经“离线”,当前session失效,并触发连接中断的事件。要做到这一点,server需要运行一个后台线程,定时扫描所有的session信息,判断session是否已经超时。

PHP处理session的原理也不例外,但是在具体的实现方式上,却与众不同。这是因为,由于PHP的工作机制,它并没有一个后台线程,来定时地扫描session信息并判断其是否失效。它的解决之道是,当一个有效请求发生时,PHP会根据某个概率,来决定是否调用一个GC(Garbage Collector)。GC的工作,就是扫描所有的session信息,用当前时间减去session的最后修改时间(modified date),同配置参数(configuration option)session.gc_maxlifetime的值进行比较,如果生存时间已经超过gc_maxlifetime,就把该session删除。这是很容易理解的,因为如果每次请求都要调用GC代码,那么PHP的效率就会低得令人吃不消了。这个概率取决于配置参数session.gc_probability/session.gc_divisor的值(可以通过php.ini或者ini_set()函数来修改)。默认情况下,session.gc_probability = 1,session.gc_divisor=100,也就是说有1%的可能性会启动GC。 这三个参数,session.gc_maxlifetime/session.gc_probability/session.gc_divisor都可以通过php.ini或者ini_set()函数来修改。但要记得,如果使用ini_set()函数的话,必须在每一个页面的开始处都调用ini_set()。

这又导致了另外一个问题,gc_maxlifetime只能保证session生存的最短时间,并不能够保存在超过这一时间之后session信息立即会得到删除。因为GC是按概率启动的,可能在某一个长时间内都没有被启动,那么大量的session在超过gc_maxlifetime以后仍然会有效。当然,发生这种情况的概率很小,但是如果你的应用对session的失效期要求很精确的话,这会导致很严重的问题。解决这个问题的一个方法是,把session.gc_probability/session.gc_divisor的机率提高,如果提到100%,就会彻底解决这个问题,但显然会对性能造成严重的影响。另一个方法是放弃PHP的GC,自己在代码中判断当前session的生存时间,如果超出了 gc_maxlifetime,就清空当前session。

PHP中的session有效期默认是1440秒(24分钟),也就是说,客户端超过24分钟没有刷新,当前session就会失效。要修改这个默认值,正确的解决办法是修改配置参数session.gc_maxlifetime。

我曾经在网上搜索过这个问题的解决方式,找到的结果千奇百怪。有的说要设置“session_life_time”,据我知所,PHP中没有这个参数。有的说要调用session_set_cookie_params,或者设置session.cookie_lifetime,这仅仅用于设置client端cookie的生存时间,换言之,只当client端cookie的生存时间小于server端的session生存期时,修改这个值才有效,并且最长不能超过server端的session生存期,原因很简单,当server端的session已经失效时,client端cookie的生存时间再长也是没有意义的。还有的说要调用 session_cache_expire,这个参数用于通知浏览器和proxy,当前页面的内容应该被缓存多长时间,和session的生存期并没有直接关系。

听起来,这种解决方案很完美。但是,当你在实际中尝试修改session.gc_maxlifetime的值的时候,你很可能会发现,这个参数基本不起作用,session有效期仍然保持24分钟的默认值。甚至可能出现,在开发环境下工作正常,在服务器上却无效!

为了彻底解决这个问题,需要对PHP的工作细节进行进一步的分析。

在默认情况下,PHP 中的session信息会以文本文件的形式,被保存在系统的临时文件目录中。这个路径由配置参数session.save_path指定。在Linux下,这一路径通常为tmp,在 Windows下通常为C:WindowsTemp。当服务器上有多个PHP应用时,它们会把自己的session文件都保存在同一个目录中(因为它们使用同一个session.save_path参数)。同样地,这些PHP应用也会按一定机率启动GC,扫描所有的session文件。

问题在于,GC在工作时,并不会区分不同站点的session。举例言之,站点A的gc_maxlifetime设置为2小时,站点B的 gc_maxlifetime设置为默认的24分钟。当站点B的GC启动时,它会扫描公用的临时文件目录,把所有超过24分钟的session文件全部删除掉,而不管它们来自于站点A或B。这样,站点A的gc_maxlifetime设置就形同虚设了。

找到问题所在,解决起来就很简单了。在页面的开始处调用session_save_path()函数,它能够修改session.save_path参数,把保存session的目录指向一个专用的目录,例如tmpmyapp。这样,gc_maxlifetime参数就工作正常了。

使用公用的session.save_path还会导致安全性问题,因为这意味着,同一台服务器上的其它PHP程序也可以读取你的站点的session文件,这可能被用于黑客攻击。另一个问题是效率:在一个繁忙的站点中,可能存在成千上万个session文件,而把许多不同网站的session文件都放在同一个目录下,无论是对单个文件的读写,还是遍历所有文件进行GC,都无疑会导致性能的降低。因此,如果你的PHP应用和别的PHP应用运行在同一台服务器上的话,强烈建议你使用自己的session.save_path。

严格地来说,这算是PHP的一个bug。当PHP在进行GC时,它应该区别来自不同站点的session文件,并应用不同的gc_maxlifetime值。目前,最新的PHP 5.2.X仍然存在这个问题。

上文说到,在一个繁忙的站点中,可能存在成千上万个session文件,即使区分了不同站点的session.save_path目录,单个站点的session文件数目仍然可能导致效率问题。为了解决这一问题,可行的几种方法有:

如果PHP运行在Linux系统下,使用ReiserFS文件系统取代默认的ext2/ext3文件系统。ReiserFS对于大量小文件的存取性能,比ext2/ext3有极大的提高。

将session.save_path指向一个内存路径。这意味着,session文件的读写只在内存中进行,而不执行磁盘操作。

session.save_path接受一个额外的N参数,用于指定目录的级数。例如,“5;/tmp” 将导致创建类似这样的session文件:/tmp/4/b/1/e/3/sess_4b1e384ad74619bd212e236e52a5a174If。具体的说明,请参见:http://cn.php.net/manual/en/session.configuration.php#ini.session.save-path

终极的解决方案,是放弃PHP的session处理机制,自己编码接管所有的session处理操作,通过session_set_save_handler()函数来实现。通过自己接管session处理,可以将所有的session保存在专门的数据库(往往使用内存表)中,从而彻底解决session文件带来的问题,并且可以方便地实现session的共享和复制。这也是大型的PHP应用一般会使用的方式。关于session_set_save_handler()函数的使用,网上和相关图书都有详细的说明,这里不再赘述。值得一提的是,即使在这种方式下,启动GC的概率仍然取决于session.gc_probability/session.gc_divisor。

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn