2011-12-23 69 views
2

编辑 我试着用xdebug和netbeans进行调试。如果我输入了一些断点,那么在调试会话期间出口将工作是很奇怪的。但是,没有断点,更加现实的环境,出口不起作用。PHP Redis Session not saving

我已经尝试在代码的某些部分添加睡眠。

我想也许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。

有没有人有任何想法?

这里有一些更多的细节:

  1. Redis的还没有用完的内存。我们已经检查了很多次。
  2. 我们已经知道,要反序列化会话中的报表对象,报表类必须已包含在内。 (请记住,第一次导出可以正常工作,但之后的任何操作都会失败)
  3. 如果我们在运行报表的请求期间检查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) 
     { 



     } 
    } 
+0

您可以请(仅用于诊断)使用会话变量的不同密钥?如md5('report_name_unixtimestamp')或'timestamp-reportname'? 背景:我怀疑被截断的钥匙somwhere – 2011-12-23 19:13:30

+0

@EugenRieck,你的意思是像这样$ _SESSION [md5('report_name_1234')]?也许,但这并不能解释为什么第一次报告导出工作。报告的关键字长度是相同的,因为它只是不同的时间戳。我会尽力,谢谢。 – 2011-12-23 19:34:44

+0

@EugenRieck,它没有工作。我试着按照你的建议去做md5散列,但它也不起作用。 – 2011-12-23 19:49:13

回答

2

问题解决了,它比我想象的要笨多了。

保存处理程序没有以任何方式实现锁定。在报告页面上,通过ajax等向服务器发出多个请求。在将报告保存到会话空间之前,其中一个Ajax请求开始。因此,它读取会话,然后在会话结束时写入会话。

由于报表每次执行速度都很快,报表会缓存到Redis中的会话中,但会被另一个具有较旧版本的sessien的脚本覆盖。

我从我的一位同事那里得到了帮助。啊!我很高兴能够结束这令人头痛的事情。

+0

所以...你是怎么解决它的? – 2013-02-08 20:20:07

+0

我们最终没有使用redis进行会话管理,并坚持使用php会话。但解决方案是编写一个保存处理程序锁定机制,以便对不同请求捕获会话更改。 – 2013-02-12 15:01:49

+0

我其实给了一些想法。不是完全锁定,而是在每个会话中维护一个校验和,当你写入它时,如果校验和已经改变,那么你需要合并两个数组,否则,只需要覆盖。在PHP中很容易做,而不是在CPP – 2013-02-12 15:38:38