【问题标题】:PHP & Sessions: Is there any way to disable PHP session locking?PHP & Sessions:有什么方法可以禁用 PHP 会话锁定?
【发布时间】:2011-03-23 05:42:05
【问题描述】:

在使用默认会话处理程序时,有什么方法可以禁用 PHP 中的会话锁定?

[编辑:] 或者至少有办法在调用session_write_close() 后重新启动会话?如果任何输出已经发送到浏览器,session_start() 将不起作用。

【问题讨论】:

标签: php session locking


【解决方案1】:

您不想禁用它...如果您这样做,您可能会遇到各种奇怪的问题,您在一个窗口上登录,在另一个窗口上注销,然后以不一致的状态结束。 .. 锁定是有原因的...

相反,如果您知道不会在该请求中写入会话,请尽早关闭会话。一旦你启动它,你就可以从它中读取整个请求(除非你重新启动它,或者做一些其他特殊的事情),即使你调用了session_write_close。因此,您要做的是检查请求以查看它是否是写入请求,如果不是,请在打开后立即关闭它。请注意,如果您稍后尝试写入会话(用于验证码或 CSRF 保护或其他),这可能会产生一些不利影响,因此请谨慎使用...

但我不会试图绕过它,而是会努力缩短请求长度(以减少锁争用),或者为那些根本不需要会话的请求发出无 cookie 请求......

【讨论】:

  • 这个问题没有真正的答案,但在其他方面非常有帮助。谢谢你。我从我的脚本中调用了长时间运行,并错过了预先调用 session_write_close 和之后调用 session_start 的明显解决方案。
  • 您需要在session_start 之后致电session_write_close。否则它不会做任何事情。我不确定是否可以稍后重新开始会话,但您可以尝试...
  • 什么不起作用?你是什​​么意思重新启动会话?需要更多细节...
  • 因此,用户同时点击了 3 个页面,其中一个恰好是注销页面。 1页加载,1页注销,第3页说他们需要登录。 这真的是个大问题吗? 这是开发者的决定。我们中的一些人留下了遗留代码,我们需要修复的是允许现有代码中的并发连接......如果您在会话中存储竞争条件会中断的东西......您可能需要停止它.通过解除锁定,我得到了一个 2 分钟到 30 秒的报告。没有 cookie 的请求会更好,但这需要更多的重构。
【解决方案2】:

我有一个报告页面需要超过 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 文档。

【讨论】:

  • 这段代码很容易受到竞争条件错误的影响(这就是存在会话锁定的原因)。使用适合用例的数据存储(即不是文件。redis,甚至 mysql 可能更合适)并且事情更健壮。
  • @AD7six,我同意。它很容易出现竞争条件,这就是为什么我在描述它时把它拼出来的原因。 如果您不为整个请求锁定行,那么 MySQL 会话也是如此。当您有 6 个并发 AJAX 连接用于密集报告时,锁定就不是一种选择……而且在我们的例子中,会话没有任何竞争条件会受到伤害。竞争条件是否有问题由开发人员根据他们在该会话中持有的内容来决定。然而,这确实回答了 OP 的问题,即如何不锁定。这是一个有效的答案。
  • @AD7six,redis 默认不锁定并且容易出现竞争条件......但正如一些评论者在这里指出的那样,他们主要关心的是速度而不是文件锁定:github.com/phpredis/phpredis/issues/37跨度>
  • 文件写入解决方案只有在您的代码没有分布在多个盒子中时才有效。
  • @Anther,感谢您的反馈。我在代码上方添加了一条注释。我不认为任何使用分布式系统的人都会使用这样的会话,但可能值得注意。
【解决方案3】:

您可以通过在 session_write_close() 之后调用 session_start() 来重新启动会话。然而,这将导致多个 SIDS。我通过在刷新输出之前从标头中删除多个 SIDS 来解决此问题。

请参阅此示例:https://gist.github.com/CMCDragonkai/6912726#file-nativesession-php-L254

【讨论】:

    【解决方案4】:

    除了使用session_write_close() 之外?我不知道。

    【讨论】:

    • 如果只需要读取会话数据session_start([ 'read_and_close' =&gt; true ]);
    【解决方案5】:

    无法从 php 会话中禁用锁。这是一个真正可怕的锁定用例。摆脱会话和/或 php.ini 的唯一方法。作为临时解决方案,您可能需要使用 riak 会话处理程序:https://github.com/zacharyfox/riak-php-sessions 它是无锁的,并且可以正常工作。

    memcached 会话处理程序的最新版本还引入了锁定,因为一些疯狂的原因并且无法禁用它。

    【讨论】:

      【解决方案6】:

      如果 PHP 即使在调用 session_write_close 之后也没有异步处理请求,它可能只是 xdebug。我不知道这是否是你的问题,但我一直被这个绊倒,让自己为此发疯,所以我想如果其他人有同样的问题,我会发布它:)

      【讨论】:

      • 我遇到了同样的问题,我解决了,直到关闭 XDEBUG。
      【解决方案7】:

      这是我在研究会话处理程序时遇到的一个相当老的问题,但答案是肯定的——但不使用默认处理程序(无需深入入侵文件系统以禁用锁定)。

      我遇到了同样的问题。需要注意的重要一点是,您确实必须确切地知道禁用锁定的后果!

      我的解决方案是会话处理机制更大扩展的一部分 - Stackable Session Handler,其中包括与默认处理程序兼容的存储和(可选)非阻塞会话读取和写入。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-04-27
        • 2014-01-29
        • 2016-04-06
        • 2010-10-15
        • 1970-01-01
        • 2023-04-03
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多