PHP 使用二进制协议的 Memcached — `increment()` 后返回的垃圾数据

PHP Memcached with Binary Protocol — Junk data returned after `increment()`

我已经开始使用 increment() method of the PHP Memcached client, and with that switched to the binary protocol. Apparently, increment() is only supported on the binary protocol。有时,我看到从增加的键返回垃圾结果。例如:

$memcached = new \Memcached();
$memcached->setOption(\Memcached::OPT_BINARY_PROTOCOL, TRUE);

$this->cache->increment($key,1,1);

$this->cache->get($key);

输出:

"1\u0000ants1 0 1\r\n1\r\n1\r\n25\r"

鉴于密钥在最初递增之前不存在,并且 1 的初始值被赋予 increment() 调用,我希望返回的值是整数。相反,返回的字符串看起来像是遗留的垃圾,例如该字符串的 ants 部分没有相关性。

其他(可能)相关信息:

tl;博士;

这是 PHP 扩展代码中的错误...


我深入研究了包装 libmemcached 的 PHP 扩展代码和 libmemcached API 代码本身,但我想我已经找到了您问题的可能根本原因...

如果您查看 PHP Memcached::increment() 实现,您将在 line 1858 of php_memcached.c

上看到
status = memcached_increment_with_initial(m_obj->memc, key, key_len, (unsigned int)offset, initial, expiry, &value);

这里的问题是 offset 可能是也可能不是 64 位宽。 libmemcached API 告诉我们 memcached_increment_with_initial 函数签名期望 offsetuint64_t 而这里 offset 被声明为 long 然后转换为 unsigned int.

所以如果我们做这样的事情...

$memcached = new memcached;
$memcached->addServer('127.0.0.1','11211');
$memcached->setOption(\Memcached::OPT_BINARY_PROTOCOL, TRUE);

$memcached->delete('foo'); // remove the key if it already exists
$memcached->increment('foo',1,1);

var_dump($memcached->get('foo'));

你会看到类似...

string(22) "8589934592
"

作为该脚本的输出。 请注意,这仅在该内存缓存服务器上尚不存在键 foo 时才有效。 还要注意该字符串的长度 22 个字符,显然它不存在应该在附近的任何地方。

如果您查看该字符串的十六进制表示....

 var_dump(bin2hex($memcached->get('foo')));

最后结果是清除垃圾...

 string(44) "38353839393334353932000d0a000000000000000000"

正在存储的对象在两次转换之间明显损坏。因此,您最终可能会得到与我相同的结果,或者您最终可能会得到完全损坏的数据,正如您在上面所展示的那样。这取决于演员表如何影响当时存储的内存块(此处属于未定义行为)。此外,唯一看似根本的原因是使用带有增量的初始值(在此之后使用 increment 并不能证明该问题或密钥是否已经存在)。

我猜这个问题源于 libmemcached API 在 memcached_incrementmemcached_increment_with_initial[=38 之间对 offset 参数有两个不同的大小要求=]

memcached_increment(memcached_st *ptr, const char *key, size_t key_length, uint32_t offset, uint64_t *value)

前者采用uint32_t而后者采用uint64_t,PHP的扩展代码都转换为unsigned int,相当于uint32_t ] 差不多。

offset 参数宽度的这种差异可能导致密钥在调用 PHP 扩展代码和 API 代码之间以某种方式被破坏。