【发布时间】:2011-03-23 05:42:05
【问题描述】:
在使用默认会话处理程序时,有什么方法可以禁用 PHP 中的会话锁定?
[编辑:] 或者至少有办法在调用session_write_close() 后重新启动会话?如果任何输出已经发送到浏览器,session_start() 将不起作用。
【问题讨论】:
-
有趣的文章解释会话锁的背景:ma.ttias.be/…
在使用默认会话处理程序时,有什么方法可以禁用 PHP 中的会话锁定?
[编辑:] 或者至少有办法在调用session_write_close() 后重新启动会话?如果任何输出已经发送到浏览器,session_start() 将不起作用。
【问题讨论】:
您不想禁用它...如果您这样做,您可能会遇到各种奇怪的问题,您在一个窗口上登录,在另一个窗口上注销,然后以不一致的状态结束。 .. 锁定是有原因的...
相反,如果您知道不会在该请求中写入会话,请尽早关闭会话。一旦你启动它,你就可以从它中读取整个请求(除非你重新启动它,或者做一些其他特殊的事情),即使你调用了session_write_close。因此,您要做的是检查请求以查看它是否是写入请求,如果不是,请在打开后立即关闭它。请注意,如果您稍后尝试写入会话(用于验证码或 CSRF 保护或其他),这可能会产生一些不利影响,因此请谨慎使用...
但我不会试图绕过它,而是会努力缩短请求长度(以减少锁争用),或者为那些根本不需要会话的请求发出无 cookie 请求......
【讨论】:
session_start 之后致电session_write_close。否则它不会做任何事情。我不确定是否可以稍后重新开始会话,但您可以尝试...
我有一个报告页面需要超过 2 分钟(超过 80 个 AJAX 请求)。通过删除会话锁定,我将其缩短到 30 秒以下。 是的,天堂禁止你删除文件锁定,因为那样你就会有竞争条件。如果你不了解竞争条件并且你不知道会对你的会话产生什么影响......然后不要删除文件锁定。但是,如果您知道会话中有哪些数据,并且知道竞态条件的作用,并且觉得没有数据可能会受到竞态条件的不利影响,从而产生错误......您比您更了解您的环境其他人都这样做,所以去吧。
MySQL、REDIS 和 Memcache
另外,请注意,如果您切换到 MySQL 进行会话管理,恕我直言,您有 99% 的机会不会从读取时间到写入时间锁定行。因此,使用 MySQL,您仍然会遇到相同的竞争条件(或者如果您决定锁定行,则会出现锁定问题)。
根据我能找到的信息,使用 PHP Redis 的人正在使用容易出现竞争条件的非锁定应用程序......根据以下线程......他们将速度作为他们的原因之一喜欢这个“功能”:
https://github.com/phpredis/phpredis/issues/37
Memcached 直到 3.0.4 版才支持会话锁定...所以它也 - 最初 - 容易出现竞争条件。
显然,随着这些选项的成功,竞争条件不再是程序员面临的最大问题。
最终问题是
所有并发请求将总是受制于竞争条件除非您进行文件锁定,此时它们 不再是并发请求。
关于会话和锁定与并发和竞争条件的重要一点是了解您的应用程序,了解竞争条件是否会破坏您的应用程序......并设计适合您的应用程序的解决方案。如果您所做的只是将 userId 存储在会话中并在所有后续请求中读取它,您可能不需要担心竞争条件。如果您存储了详细的数据历史记录,如果事情发生混乱或数据可能丢失,这些历史记录会中断,然后在读取和写入之间锁定文件,并尝试在读取后尽快执行写入以限制时间量文件已锁定。
最佳选择
但是,对于并发请求,无会话 API 会好得多。但是,如果您没有时间重构这样的 API……那么请继续阅读。
继续使用 PHP 会话文件并停止锁定的权宜之计
要继续以默认方式使用 PHP 会话,停止锁定,并且基本上可以非常快速地解决复杂问题,您可以实现 PHP 网站的 SessionHandler 示例实现。
我有以下代码在生产环境中运行,用于一个每分钟有数万个连接的站点,我还没有遇到任何竞争条件问题,但我也没有存储竞争数据-条件可能会破裂。正如我所说,这段代码得到了一份报告,从 2 分钟多到 30 秒以下……并且花了几分钟来实现。无需创建 MySQL 架构,无需安装 Memcache 或 Redis。
这是 PHP 文档 (http://php.net/manual/en/class.sessionhandlerinterface.php) 中提供的示例实现,它在读取会话文件时不会锁定它。
注意正如 Anther 在此 cmets 中指出的那样,除非您将文件保存到单个服务器,否则这在分布式系统中不起作用。
<?php
class MySessionHandler implements SessionHandlerInterface
{
private $savePath;
public function open($savePath, $sessionName)
{
$this->savePath = $savePath;
if (!is_dir($this->savePath)) {
mkdir($this->savePath, 0777);
}
return true;
}
public function close()
{
return true;
}
public function read($id)
{
return (string)@file_get_contents("$this->savePath/sess_$id");
}
public function write($id, $data)
{
return file_put_contents("$this->savePath/sess_$id", $data) === false ? false : true;
}
public function destroy($id)
{
$file = "$this->savePath/sess_$id";
if (file_exists($file)) {
unlink($file);
}
return true;
}
public function gc($maxlifetime)
{
foreach (glob("$this->savePath/sess_*") as $file) {
if (filemtime($file) + $maxlifetime < time() && file_exists($file)) {
unlink($file);
}
}
return true;
}
}
在 PHP 5.4+ 中,使用它就像在开始会话之前设置处理程序一样简单:
$handler = new MySessionHandler();
session_set_save_handler($handler, true);
session_start();
对于较低版本的 PHP,您仍然可以通过函数调用来实现...请参阅 PHP 文档。
【讨论】:
您可以通过在 session_write_close() 之后调用 session_start() 来重新启动会话。然而,这将导致多个 SIDS。我通过在刷新输出之前从标头中删除多个 SIDS 来解决此问题。
请参阅此示例:https://gist.github.com/CMCDragonkai/6912726#file-nativesession-php-L254
【讨论】:
除了使用session_write_close() 之外?我不知道。
【讨论】:
session_start([ 'read_and_close' => true ]);
无法从 php 会话中禁用锁。这是一个真正可怕的锁定用例。摆脱会话和/或 php.ini 的唯一方法。作为临时解决方案,您可能需要使用 riak 会话处理程序:https://github.com/zacharyfox/riak-php-sessions 它是无锁的,并且可以正常工作。
memcached 会话处理程序的最新版本还引入了锁定,因为一些疯狂的原因并且无法禁用它。
【讨论】:
如果 PHP 即使在调用 session_write_close 之后也没有异步处理请求,它可能只是 xdebug。我不知道这是否是你的问题,但我一直被这个绊倒,让自己为此发疯,所以我想如果其他人有同样的问题,我会发布它:)
【讨论】:
这是我在研究会话处理程序时遇到的一个相当老的问题,但答案是肯定的——但不使用默认处理程序(无需深入入侵文件系统以禁用锁定)。
我遇到了同样的问题。需要注意的重要一点是,您确实必须确切地知道禁用锁定的后果!
我的解决方案是会话处理机制更大扩展的一部分 - Stackable Session Handler,其中包括与默认处理程序兼容的存储和(可选)非阻塞会话读取和写入。
【讨论】: