PHP随机丢失部分会话

4
我正在为Joomla! 2.5.6进行开发,这段代码在Joomla 1.5下运行良好。
页面1
JFactory::getSession()->clear('domain_name', 'dominiForm');

第二页

$session = JFactory::getSession();
$session->set('domain_name', $domain_name, 'dominiForm');

第三页

$session = JFactory::getSession();
$domain_name = $session->get('domain_name', null, 'dominiForm');

问题在于第三页有时会返回null,有时会返回保存的值。在我的开发机上可以正常工作,但在生产服务器上不行,我不知道该怎么办。

以下是服务器上的phpinfo()信息:

PHP Version 5.3.3-7+squeeze14
session
Session Support     enabled
Registered save handlers    files user sqlite
Registered serializer handlers  php php_binary wddx

Directive   Local Value Master Value
session.auto_start  Off Off
session.bug_compat_42   Off Off
session.bug_compat_warn Off Off
session.cache_expire    180 180
session.cache_limiter   none    nocache
session.cookie_domain   no value    no value
session.cookie_httponly Off Off
session.cookie_lifetime 0   0
session.cookie_path /   /
session.cookie_secure   Off Off
session.entropy_file    no value    no value
session.entropy_length  0   0
session.gc_divisor  1000    1000
session.gc_maxlifetime  2700    1440
session.gc_probability  0   0
session.hash_bits_per_character 5   5
session.hash_function   0   0
session.name    a6252c638b628a21b4b4b1cf3338a103    PHPSESSID
session.referer_check   no value    no value
session.save_handler    user    files
session.save_path   /var/lib/php5   /var/lib/php5
session.serialize_handler   php php
session.use_cookies On  On
session.use_only_cookies    On  On
session.use_trans_sid   0   0

session.cookie_lifetime 0 session.gc_maxlifetime 2700 session.save_path /var/lib/php5会话.cookie_生存时间 0 会话.gc_最大生存时间 2700 会话.save_路径 /var/lib/php5 - max4ever
确保在请求第3页之前和第2页之后不会请求第1页。尝试使用命令行中的curl进行复现,因为您的浏览器会发送一些请求。您可以通过在会话中递增计数器来检查它,该计数器在每个请求上都会+1,并将计数器值显示为响应标头。然后,您可以使用curl -i查看其值。 - hakre
$session对象将值存储在$_SESSION全局变量中的命名空间索引(__default)中。如果您使用var_dump($_SESSION),您会发现什么? - tampe125
3个回答

4

你在生产服务器上使用了自定义的session.save_handler。可能在开发机器上没有这个设置。

请注意,Joomla在处理会话锁定时存在问题 - 没有进行锁定。这意味着你可能会遇到竞态条件问题。

如果你查看session_set_save_handler()文档,你会发现有open、close、read、write、destroy和gc(垃圾回收)的回调函数。

open应该“初始化”一些东西,但最重要的是应该获取用于存储的资源的写锁。

read执行通常的读取操作,write执行写入操作。

close应该释放写锁。

如果一个会话保存处理程序没有获取锁,则具有相同会话ID的多个并行请求可能会互相覆盖!

有一个简单的测试可以在你的服务器上执行,以查看是否存在此问题:

<?php

// initialize alternate session save handler here.
//include_once "session-handler.php";

if (isset($_GET['subrequest'])) {
    $starttime = time();
    $subrequest = intval($_GET['subrequest']);

    session_start(); // should wait until lock is released

    echo "<html><pre>";
    echo "Request started on ". date("Y-m-d H:i:s", $starttime)."\n";
    echo "Session locked for this request on ". date("Y-m-d H:i:s"). "\n";
    echo "Executing subrequest ". $subrequest."\n";
    $_SESSION["subrequest"][] = 'collected subrequest #'.$subrequest;
    echo "All subrequests collected:\n";
    var_dump($_SESSION["subrequest"]);
    echo "\nWaiting 1 second\n";

    sleep(1);
    echo "Releasing session lock on ". date("Y-m-d H:i:s"). "\n";
    echo "</pre></html>";
    exit();
}

session_start();

$_SESSION['subrequest'] = array('master request');

?>
<html>
<iframe src="?subrequest=1" width="90%" height="100"></iframe>
<hr>
<iframe src="?subrequest=2" width="90%" height="100"></iframe>
<hr>
<iframe src="?subrequest=3" width="90%" height="100"></iframe>
</html>

这个PHP文件将初始化会话,并在屏幕上发出三个iframe,几乎立即再次向服务器发出三个请求。
如果会话被正确锁定,则每个iframe将依次填充,紧接着是下一个iframe。此外,按照出现的顺序,$_SESSION['subrequest']的控制输出应包括最后返回的所有子请求。
如果会话没有正确锁定,则在主页面加载后一秒钟,所有三个iframe都将立即填充,并且它们都将与主请求一起报告其自己的子请求以及调试输出中的主请求。
如果我使用这个实现来进行文件系统存储,我得到了从php.net文档(示例#2)页面,会话的适当保存失败!
<?php
class FileSessionHandler
{
    private $savePath;

    function open($savePath, $sessionName)
    {
        $this->savePath = $savePath;
        if (!is_dir($this->savePath)) {
            mkdir($this->savePath, 0777);
        }

        return true;
    }

    function close()
    {
        return true;
    }

    function read($id)
    {
        return (string)@file_get_contents("$this->savePath/sess_$id");
    }

    function write($id, $data)
    {
        return file_put_contents("$this->savePath/sess_$id", $data) === false ? false : true;
    }

    function destroy($id)
    {
        $file = "$this->savePath/sess_$id";
        if (file_exists($file)) {
            unlink($file);
        }

        return true;
    }

    function gc($maxlifetime)
    {
        foreach (glob("$this->savePath/sess_*") as $file) {
            if (filemtime($file) + $maxlifetime < time() && file_exists($file)) {
                unlink($file);
            }
        }

        return true;
    }
}

$handler = new FileSessionHandler();
session_set_save_handler(
    array($handler, 'open'),
    array($handler, 'close'),
    array($handler, 'read'),
    array($handler, 'write'),
    array($handler, 'destroy'),
    array($handler, 'gc')
);

// the following prevents unexpected effects when using objects as save handlers
register_shutdown_function('session_write_close');

如果我观察一些Joomla会话类,我预测存储到APC、数据库和XCache会失败,因为它们使用自定义函数而没有正确实现openclose

我对Joomla不熟悉,所以您需要自己将Joomla使用会话的方式实现到这个测试脚本中。

最后注意:如果在数据库中无法对单个数据集进行锁定(例如,您正在使用MyISAM表),那么实际上您无法使用此表来存储会话数据。在表上获取锁将停止所有其他用户的会话。


哇,这是一个很好的答案。我通过手动将数据保存在数据库表中“修复”了问题,但我会尝试你的测试文件。 - max4ever
好的,这是我得到的 http://snag.gy/4pGTM.jpg 这意味着它工作不正确,对吗? - max4ever
我无法看到你的图片,它无法加载。 - Sven
好的,现在我明白了。是的,它显示了预期的失败,你的会话处理程序没有锁定。 - Sven
好的,但这是一个安装了标准Joomla的托管服务器,有点奇怪。 - max4ever
错误在 Joomla 代码中的会话处理程序内部。如上所述,数据库会话存储不会进行适当的锁定,同时也不使用 APC 或 XCache。 - Sven

0

我认为你可以使用这个脚本。 在第一页或第二页使用以下脚本。

 $session =& JFactory::getSession();
    $session->set('name', "value");

*name 是您的会话变量,而 value 是您的值。

然后在第三个页面使用此代码:

$session =& JFactory::getSession();

echo $session->get('name'); *name 是您的会话变量


0

你在生产环境中使用缓存机制吗,例如 varnish?因为在处理会话和 joomla 时,缓存总是很麻烦,我在使用缓存时遇到了许多与缓存相关的问题(这就是为什么我目前不使用缓存,只用 APC 而不带用户缓存)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接