EDIT我尝试使用 xdebug 和 netbeans 进行调试。奇怪的是,如果我输入一些断点,导出将在调试会话期间起作用。但是,如果没有断点,环境更逼真,导出不起作用。
我尝试在代码的某些部分添加睡眠。
我认为也许 PHP 在 Redis 提交完成之前就结束了。也许 Redis 连接是异步完成的,但我检查了 PRedis 并且默认是同步连接。
<小时 />我正在开发一个报告工具。
这是基本问题。
我们将报告存储到会话对象中,但在以后的请求中,当我们尝试访问会话对象中的报告时,它就消失了。
这是一个更详细的版本。
我像这样将"报告"对象存储到会话中
$_SESSION['report_name_unixtimestamp'] = gzcompress( serialize( $reportObject ) );
用户以某种表格形式看到报表,然后,如果他们愿意,他们可以将其导出。报告可能会更改,因此将其存储在会话中的想法是,当用户将其导出为 PDF、Excel 等时,他们将获得与他们正在查看的报告相同的报告。
用户单击导出按钮,在 PHP 端它将进入会话,通过作为 get 参数提供的键获取报告(解压缩和反序列化它(,创建导出并将其发送给用户下载。
在我们尝试引入 Redis 缓存服务器作为更好的会话管理工具之前,这一直运行良好。
现在发生的情况如下:
我们第一次运行报告时,它将被存储到缓存中,导出将成功。
我们将在同一会话中使用相同的用户帐户再次运行报表。这会更改 unixtimestamp,因此$_SESSION
中应该有两个条目。($_SESSION['report_name_oldertimetamp']
和$_SESSION['report_name_newertimestamp']
(。当我们再次单击导出按钮时,我们收到一条错误消息,指出该文件不存在(因为它尚未由服务器发送(。
如果我们检查 redis 服务器以获取较新版本的报告,它不存在,但旧的时间戳仍然存在。
现在,这适用于文件会话管理,但不适用于 Redis。 我们已经尝试了 PHP 的 Redis 模块以及纯 PHP 客户端 Predis。
有人有什么想法吗?
以下是更多细节:
- Redis 没有耗尽内存。我们已经检查了很多次。
- 我们已经知道,要在会话中反序列化报表对象,必须已经包含报表类。(请记住,第一次导出工作正常,但之后的任何内容都失败了(
- 如果我们在运行报告的请求期间检查 php 会话对象,它将包含较新的报告,但它永远不会到达 Redis。
下面是与 Predis 一起使用的保存处理程序。redis_session_init是我在 session_start(( 之前调用的函数,以便它被注册。我不确定redis_session_write功能是如何工作的,所以也许有人可以帮助我。
<?php
namespace RedisSession
{
$redisTargetPrefix = "PHPREDIS_SESSION:";
$unpackItems = array( );
$redisServer = "tcp://cache.emcweb.com";
function redis_session_init( $unpack = null, $server = null, $prefix = null )
{
global $unpackItems, $redisServer, $redisTargetPrefix;
if( $unpack !== null )
{
$unpackItems = $unpack;
}
if( $server !== null )
{
$redisServer = $server;
}
if( $prefix !== null )
{
$redisTargetPrefix = $prefix;
}
session_set_save_handler( 'RedisSession'redis_session_open', 'RedisSession'redis_session_close', 'RedisSession'redis_session_read', 'RedisSession'redis_session_write', 'RedisSession'redis_session_destroy', 'RedisSession'redis_session_gc' );
}
function redis_session_read( $id )
{
global $redisServer, $redisTargetPrefix;
$redisConnection = new 'Predis'Client( $redisServer );
return base64_decode( $redisConnection->get( $redisTargetPrefix . $id ) );
}
function redis_session_write( $id, $data )
{
global $unpackItems, $redisServer, $redisTargetPrefix;
$redisConnection = new 'Predis'Client( $redisServer );
$ttl = ini_get( "session.gc_maxlifetime" );
$redisConnection->pipeline( function ($r) use (&$id, &$data, &$redisTargetPrefix, &$ttl, &$unpackItems)
{
$r->setex( $redisTargetPrefix . $id, $ttl, base64_encode( $data ) );
foreach( $unpackItems as $item )
{
$keyname = $redisTargetPrefix . $id . ":" . $item;
if( isset( $_SESSION[ $item ] ) )
{
$r->setex( $keyname, $ttl, $_SESSION[ $item ] );
}
else
{
$r->del( $keyname );
}
}
} );
}
function redis_session_destroy( $id )
{
global $redisServer, $redisTargetPrefix;
$redisConnection = new 'Predis'Client( $redisServer );
$redisConnection->del( $redisTargetPrefix . $id );
$unpacked = $redisConnection->keys( $redisTargetPrefix . $id . ":*" );
foreach( $unpacked as $unp )
{
$redisConnection->del( $unp );
}
}
// These functions are all noops for various reasons... opening has no practical meaning in
// terms of non-shared Redis connections, the same for closing. Garbage collection is handled by
// Redis anyway.
function redis_session_open( $path, $name )
{
}
function redis_session_close()
{
}
function redis_session_gc( $age )
{
}
}
问题解决了,它比我想象的要愚蠢得多。
保存处理程序不会以任何方式实现锁定。在报告页面上,通过 ajax 等向服务器发出多个请求。其中一个 ajax 请求在将报告保存到会话空间之前启动。因此,它读取会话,然后在末尾写入会话。
由于报告每次执行速度都更快,因此报告将在 Redis 中缓存到会话中,但随后会被具有较旧版本的 sessien 的其他脚本覆盖。
我得到了一位同事的帮助。呸!这是一个令人头疼的问题,我很高兴结束了。