请注意 CakePHP 中通过 Redis 的反序列化错误


Notice unserialize error in CakePHP via Redis

更新:

根据下面接受答案的建议,我测试了阅读负数:

$negativeInt = -1;
Cache::write('my-test-count', $negativeInt, 'short');
$readVal = Cache::read('my-test-count', 'short');
debug($readVal);
exit;

在尝试读取任何负数时,未锯齿化错误会始终如一地重现。 它现在是一个公认的错误,我认为它将在 2.8.1 中解决


原始问题:

我不断收到此错误,但无法弄清楚原因,甚至无法弄清楚如何进一步进行故障排除。

仅当 false 返回 false 时,引发错误的行才应命中Cache::read()。 但是,如果我没有将 @ 放在它前面,该行本身会引发反序列化错误。

问题:如何可靠地使用 Redis 进行计数,而无需定期获得反序列化通知? 如果密钥中的数据是"坏的",我怎么知道没有仅仅通过::read得到通知。 我尝试确保我的数据是(int)(见下文),但这似乎没有帮助。

注意 (8): unserialize(): 偏移量 0 处的错误 2 个字节 [应用程序/供应商/梨 pear.cakephp.org/CakePHP/Cake/Cache/Engine/RedisEngine.php, 第136行]

检查错误后:

> unserialize - [internal], line ??
> RedisEngine::read() - APP/Vendor/pear-pear.cakephp.org/CakePHP/Cake/Cache/Engine/RedisEngine.php, line 136
> Cache::read() - APP/Vendor/pear-pear.cakephp.org/CakePHP/Cake/Cache/Cache.php, line 358
> Cache::remember() - APP/Vendor/pear-pear.cakephp.org/CakePHP/Cake/Cache/Cache.php, line 567
> Item::getCount() - APP/Model/Item.php, line 812

它似乎来自这个函数:

public function getCount($id) {
    $model = $this;
    // here is where the Cache::read() and debug are in the update below
    return Cache::remember('item' . $id. '_count', function() use ($model, $id) {
        $count = $model->find('count', array(
            'conditions' => array(
                $model->alias . '.status' => 1,
                $model->alias . '.id' => $id
            )
        ));
        return ($count === false) ? 0 : (int)$count;
    }, 'my_counts'); // THIS IS LINE 812
}
public function decrementCount($id, $offset = 1) {
    if(empty($id)) return false;
    $count = @Cache::read('item' . $id . '_count', 'my_counts');
    if($count === false) {
        $this->getCount($id);
    } else {
        Cache::decrement('item' . $id . '_count', $offset, 'my_counts');
    }
}
public function incrementCount($id, $offset = 1) {
    if(empty($id)) return false;
    $count = @Cache::read('item' . $id. '_count', 'my_counts');
    if($count === false) {
        $this->getCount($id);
    } else {
        Cache::increment('item' . $id. '_count', $offset, 'my_counts');
    }
}

更新:

此函数在循环中运行(通过 1-20 个项目)。 当我在Cache::remember(...之前添加以下内容时:

$toReturn = Cache::read('item' . $id. '_count', 'my_counts');
debug($toReturn);

它给出了这个:

  1. 调试: (整数) 0
  2. 调试: (整数) 0
  3. 注意 (8): unserialize(): 错误... (来自缓存::读取)
  4. 调试:假
  5. 注意 (8): unserialize(): 错误... (来自缓存::记住
  6. 调试: (整数) 0
  7. 注意 (8): unserialize(): 错误... (来自缓存::读取)
  8. 调试:假
  9. 注意 (8): unserialize(): 错误... (来自缓存::记住
  10. 0
  11. 3
  12. 1
  13. 1...

这似乎是一个核心错误。我也许能够回答为什么会出现这个问题。

我猜这个问题是由int(-1)引起的。写入数据时,如果数据是整数,RedisEngine 不会序列化数据。因此,int(-1)将被保存而不调用serialize()

public function write($key, $value, $duration) {
    if (!is_int($value)) {
        $value = serialize($value);
    }
    ...
}

但是在读取数据时,实现似乎是不对称的。我不太了解Redis,但我猜Redis商店int(-1) string(-1).由于ctype_digit("-1")返回falsestring(-1)将被错误地取消序列化。

public function read($key) {
    $value = $this->_Redis->get($key);
    if (ctype_digit($value)) { // ctype_digit("-1") === false
        $value = (int)$value;
    }
    if ($value !== false && is_string($value)) { // is_string("-1") === true
        $value = unserialize($value);
    }
    ...
}

因此,您将看到"注意 (8): unserialize(): 偏移量 0 of 2 字节处出错"。string(-1)的大小为 2 个字节。

在 github 上打开一个问题怎么样,如果你可以重现那个写作int(-1)并阅读它会抛出一个通知。

除此之外,解决方法是停止使用 Cache::decrement() .在竞争条件下,该方法将存储int(-1)。如果Cache::write()不是重要数据,则可以改用它。例如:

$count = Cache::read('item' . $id. '_count', 'my_counts');
if ($count === false || 0 > $count - $offset) {
    $this->getCount($id);
} else {
    Cache::write('item' . $id. '_count', $count - $offset, 'my_counts');
}

但如果是重要数据,那么可能需要实现独占锁定/解锁。

感谢您阅读到最后,如果我错了,请道歉。

解决方案:

return ($count === false) ? 0 : (int)$count;

长版本:

不应递增/递减序列化值。

Cache::

remember执行以下操作:

$key = 'key';
$value = 10;
$serialized = serialize($value); // $serialized = 'i:10;'
$redis->set($key, $serialized);
Cache::

read 执行以下操作:

$key = 'key';
$serialized = $redis->get($key); // $serialized = 'i:10;'
$value = unserialize($serialized); // $value = (int) 10
return $value;

但是 Cache::d ecrement 是这样做的:

$key = 'key';
$count = 1;
$redis->decrby($key, $count); // Error, decrby expects plain value, "10", not "i:10;" ( http://redis.io/commands/decrby )

此外,如果您成功递减,那么您将在 redis 中具有纯值,因此结果将是:

$key = 'key';
$serialized = $redis->get($key); // $serialized = '10'
$value = unserialize($serialized); // error, cannot unserialize '10'

编辑:检查来源后:https://github.com/cakephp/cakephp/blob/master/src/Cache/Engine/RedisEngine.php

我看到蛋糕会检查:

    if (!is_int($value)) {
        $value = serialize($value);
    }

因此,为了避免序列化,您需要做的就是确保存储 (int)。

只需删除第三个参数,它应该可以工作

return Cache::remember('item' . $id. '_count', function() use ($model, $id) {
    $count = $model->find('count', array(
        'conditions' => array(
            $model->alias . '.status' => 1,
            $model->alias . '.id' => $id
        )
    ));
    return ($count === false) ? 0 : (int)$count;
});