什么是Session在Web应用开发中,Session称为会话。它主要用于保存访问者的数据。由于HTTP的无状态特性,服务器不会记住客户端。对于服务器来说,每一个请求都是全新的。既然如此,服务器怎么知道是哪个访问者请求的呢?以及如何将不同的数据映射到正确的访问者?答案是给访问者一个唯一的身份来获取Session中的数据。比如:我们去超市购物,保安告诉我们不能带东西进去,必须把东西放在超市的储物箱里。我们把东西给了他,他怎么知道谁拥有这些东西,所以他给了我们不同的钥匙。当我们想要拿走自己的物品时,可以用唯一的钥匙打开相应的箱子。就像上面的例子一样,Session可以理解为一个“盒子”,用来存放我们的数据。当然,这些“盒子”都在服务器端。服务器给访问者一个唯一的“密钥”,称为session_id。访问者有了自己的session_id,就可以获得自己存储在服务器端的数据。session_id以两种方式传递给访问者(客户端):URL或cookie。详见:传输sessionIDSession和Cookie有什么关系?Cookie也是由于HTTP的无状态特性而产生的一种技术。它还用于保存访问者的身份和一些数据。客户端每次发起HTTP请求时,都会将Cookie数据添加到HTTP头中提交给服务器。这样服务器就可以根据cookie的内容得知访问者的信息。可以说Session和Cookie做的事情差不多,只不过Session是在服务端保存数据,通过客户端提交的session_id获取对应的数据;而Cookie是在客户端保存数据,每次发起请求都会将数据提交给服务器端。上面说了,session_id可以通过URL或cookie来传递。由于URL方式比cookie方式更不安全,使用起来也不方便,所以一般通过cookie来传递session_id。服务器生成一个session_id,通过HTTP报文发送给客户端(比如浏览器)。客户端收到后,根据指令创建一个cookie,存储session_id。cookie以key/value的形式保存,看起来像这样:PHPSESSID=e4tqo2ajfbqqia9prm8t83b1f2。在PHP中,保存session_id的cookie的名字默认叫做PHPSESSID。这个名称可以通过php.ini中的session.name或函数session_name()来修改。为什么不推荐使用PHP自带的文件类型的Session处理器呢?在PHP中,默认的Session处理器是文件,处理器可以由用户实现(参见:自定义会话管理器)。我知道有很多成熟的Session处理器:Redis、Memcached、MongoDB……为什么不推荐使用PHP自带的文件类型处理器呢?PHP官方手册给出了这样的说明:无论是通过调用函数session_start()手动开启session,还是使用配置项session.auto_start自动开启session。对于基于文件的会话数据存储(PHP的默认行为),会话数据文件将在会话开始时被锁定,直到PHP脚本被执行。或者显式调用session_write_close()来保存会话数据。在此期间,其他脚本无法访问同一个会话数据文件。上面的参考资料见:Session的基本用法为了证明这段话,我们创建两个文件:在同一个浏览器中,先访问http://127.0.0.1/session1.php,然后立即在当前浏览器的新标签页中访问http://127.0.0.1/session2.php。实验发现session1.php等待了5秒输出,session2.php也等待了将近5秒输出。而且单独访问session2.php是秒开的。在一个浏览器中访问session1.php,立即在另一个浏览器中访问session2.php。结果是session1.php等待5秒输出,而session2.php秒打开。分析出现这种现象的原因:在上面的例子中,session_id是默认使用Cookie传递的,Cookie的作用域是一样的。这样,在同一个浏览器访问这两个地址时,提交给服务器的session_id是相同的(这样我们就可以标记访问者,这就是我们想要的效果)。访问session1.php时,PHP根据提交的session_id在服务器保存Session文件的路径(默认为/tmp,通过session.save_path或php.ini中的session_save_path()函数修改)找到对应的Session文件,并锁定它。如果未显式调用session_write_close(),则文件锁将在当前PHP脚本执行完毕后才会释放。如果脚本中有耗时操作(如例子中的sleep(5)),那么另一个持有相同session_id的请求只能被迫等待,因为文件被锁定,所以请求被阻塞。.既然如此,使用Session后,马上调用session_write_close()就可以解决问题了吗?例如,在上面的示例中,在sleep(5)之前调用session_write_close()。确实,这样session2.php就不会被session1.php阻塞了。但是,显式调用session_write_close()意味着将数据写入文件并结束当前会话。然后,当你想在下面的代码中使用Session时,你必须再次调用session_start()。例如:官方解决方案:对于大量使用Ajax或并发请求的网站,这可能是一个严重的问题。解决这个问题最简单的方法是,如果修改了session中的变量,应该尽快调用session_write_close()来保存session数据并释放文件锁。另一种选择是使用支持并发操作的会话保存管理器而不是文件会话保存管理器。我推荐的方式是使用Redis作为Session处理器。延伸阅读:为什么memcached不能用来存储Session如何使用Redis作为PHPSessionhandlerSession数据什么时候删除?这是面试官经常问的问题。先看官方手册中的描述:session.gc_maxlifetime指定多少秒后数据会被认为是“垃圾”并被清除。垃圾收集可能在会话启动时开始(取决于session.gc_probability和session.gc_divisor)。session.gc_probability和session.gc_divisor一起用来管理gc(garbagecollection垃圾回收)进程启动的概率。这个概率是使用gc_probability/gc_divisor计算的。例如,1/100表示有1%的概率在每个请求上启动gc进程。session.gc_probability默认为1,session.gc_divisor默认为100。继续用我上面不恰当的类比:如果我们把物品放在超市的储物箱里,并且在很长一段时间后(比如一个月)没有拿走,,然后保安人员将这些储物箱中的物品清理干净。当然,并不是说过了期限,保安就一定会来清理。也许他懒惰,或者他根本就没想过。再看一下手册两段中的引述:如果使用默认的基于文件的会话处理程序,文件系统必须跟踪访问时间(atime)。WindowsFAT文件系统不起作用,因此如果您必须使用FAT文件系统或其他不跟踪时间的文件系统,您将不得不想出另一种方法来处理会话数据垃圾收集。从PHP4.2.3开始,使用mtime(修改时间)代替atime。所以对于不跟踪时间的文件系统来说很好。GC的运行时机并不精确,有一定的概率,所以这个设置项并不能保证老的session数据被删除。一些会话存储处理程序不使用此设置。我对这个删除机制存有疑虑。比如gc_probability/gc_divisor设置的比较大,或者网站的请求量比较大,那么GC进程就会启动的比较频繁。另外,GC进程启动后,需要遍历会话文件列表,比较文件的修改时间和服务器当前时间,判断文件是否过期,决定是否删除文件。这就是为什么我认为不应该使用PHP自带的文件类型Session处理器的原因。但是Redis或者Memcached天生支持key/value过期机制,非常适合作为session处理器使用。或者自己实现一个基于文件的处理器,根据session_id获取对应的单个Session文件时判断文件是否过期。为什么重启浏览器后无法检索会话数据?session.cookie_lifetime指定发送到浏览器的cookie的生命周期,以秒为单位。值0表示“直到浏览器关闭”。默认为0,其实并不是Session数据被删除了(也有可能,概率比较小,见上一节)。只是当浏览器关闭时,保存session_id的cookie就没有了。也就是说,你丢失了打开超市储物柜的钥匙(session_id)。同样,手动清除浏览器cookies或其他软件清除也会造成这种结果。为什么打开浏览器,很久没操作就退出了?这被称为“万无一失”,以保护用户账户的安全。包含这一段是因为这个功能的实现可能和session删除机制有关(之所以有可能是因为这个功能不一定要用session来实现,也可以用cookie来实现)。简单来说就是长时间没有操作,服务器上的session文件过期被删除。一件有趣的事在我的实验过程中,我发现了一件有趣的事:我将GC启动的概率设置为100%。如果只有一个访问者请求,即使访问者在很长时间后(超过过期时间)发起第二次请求,那么Session数据仍然存在('session.save_path'目录下的Session文件存在)。是的,已经超过了过期时间,但是没有被GC删除。这时,当我用另一个浏览器访问时(相对于另一个访问者),这个请求生成了一个新的Session文件,而之前浏览器请求生成的Session文件终于消失了(之前的Session文件在'session.save_path'目录下消失)。还有,发现Session文件被删除后,再次请求还是会生成一个和之前文件同名的Session文件(因为浏览器没有关闭,再次请求发送的session_id是一样的,所以文件名重新生成的会话文件仍然相同)。然而,我不明白的是:这个重新出现的文件的创建时间是第一次创建时间。难道是从回收站里回来的?(的确,我是在window下做这个实验的)我猜测原因是这样的:当session启动时,PHP根据session_id找到并打开对应的Session文件,然后启动GC进程。GC进程只检查除当前Session文件外的其他文件,过期的会kill掉。因此,即使当前Session文件已经过期,GC也没有删除它。我认为这是不合理的。既然出现这种情况,影响不大(毕竟线上请求多,当前请求的过期文件被其他请求触发的GCkill掉的可能性比较大)+我没有有信心阅读PHP源码+我不在线使用PHP自带的文件型Session处理器。因此,我没有深究这个问题。敬请谅解。
