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
部分没有相关性。
其他(可能)相关信息:
- 我在一系列不同的键上看到了这个
- 我们的 Memcached 服务器是一个 AWS Elasticache 实例
- 使用相同缓存节点的其他客户端未使用二进制协议。
- 所有客户端都是运行相同的OS(CentOS)、PHP和Memcached版本。
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
函数签名期望 offset
的 uint64_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_increment
和 memcached_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 代码之间以某种方式被破坏。
我已经开始使用 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
部分没有相关性。
其他(可能)相关信息:
- 我在一系列不同的键上看到了这个
- 我们的 Memcached 服务器是一个 AWS Elasticache 实例
- 使用相同缓存节点的其他客户端未使用二进制协议。
- 所有客户端都是运行相同的OS(CentOS)、PHP和Memcached版本。
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
函数签名期望 offset
的 uint64_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_increment
和 memcached_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 代码之间以某种方式被破坏。