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

4

我开始使用PHP Memcached客户端的increment()方法,并切换到二进制协议。显然,increment()方法仅在二进制协议上受支持。偶尔地,我会看到增加后的键返回了垃圾结果。例如:

$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"

鉴于在第一次递增之前该键不存在,并且increment()调用的初始值为1,我希望返回的值是一个整数。然而,返回的字符串看起来像是剩余的垃圾数据,例如该字符串中的ants部分与其没有关系。

其他(可能)相关信息:

  • 我在一系列不同的键上看到了这个问题
  • 我们的Memcached服务器是AWS Elasticache实例
  • 使用相同缓存节点的其他客户端未使用二进制协议。
  • 所有客户端都运行相同的操作系统(CentOS)、PHP和Memcached版本。

你是否同时使用32位和64位的EC2构建来写入这个memcached实例?另外,你使用的是哪个版本的memcached PHP绑定? - Sherif
1个回答

6

简要概述

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


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

如果您查看PHP Memcached::increment()实现,您将在php_memcached.c第1858行上看到

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

问题在于offset的宽度可能是64位或不是。根据libmemcached APImemcached_increment_with_initial函数需要一个uint64_t类型的offset参数,然而这里的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
"

作为该脚本的输出结果。请注意,这仅在该memcached服务器上不存在关键字foo的情况下才有效。 还要注意该字符串的长度为22个字符,但显然不应该是这样。

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

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

结果最终成为一堆毫无意义的垃圾...
 string(44) "38353839393334353932000d0a000000000000000000"

被存储的对象在强制类型转换之间明显已经损坏。因此,您可能会得到与我相同的结果,或者像上面演示的那样获得完全破损的数据。这取决于强制类型转换对正在存储的内存块产生的影响(这里跌入了未定义行为)。另外,唯一看起来的根本原因是在初始值中使用增量(随后使用“increment”并不会显示出该问题,或者如果键已经存在)。
我认为这个问题源于libmemcached API对于memcached_increment和memcached_increment_with_initial函数中的“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 代码之间某种方式损坏密钥。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接