带有二进制协议的PHP Memcached - 在`increment()`

时间:2015-11-05 17:20:15

标签: php memcached amazon-elasticache

我已经开始使用PHP Memcached客户端的increment()方法,并且已经切换到二进制协议。显然,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实例
  • 使用相同缓存节点的其他客户端未使用二进制协议。
  • 所有客户端都运行相同的操作系统(CentOS),PHP和Memcached版本。

1 个答案:

答案 0 :(得分:5)

TL;博士;

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

我深入研究了包含libmemcached和libmemcached API代码本身的PHP扩展代码,但我认为我发现了问题可能的根本原因...

如果你看一下你在line 1858 of php_memcached.c上看到的PHP Memcached::increment()实现

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函数签名需要uint64_t offset,而offset声明为long,然后转换为{{} 1}}。

所以如果我们这样做......

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'));

作为该脚本的输出。 请注意,这只适用于该memcached服务器上尚未存在密钥string(22) "8589934592 " 的情况。还要注意该字符串的长度为foo个字符,当它显然为{s}时不应该在那附近。

如果你看一下那个字符串的十六进制表示....

22

结果是结尾的垃圾......

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

正在存储的对象在演员之间明显被破坏。因此,您可能最终得到与我相同的结果,或者您可能最终得到完全破碎的数据,如上所述。它取决于强制转换如何影响当时存储的内存块(这里将进入未定义的行为)。此外,唯一看似根本原因是使用带有增量的初始值(在此之后使用 string(44) "38353839393334353932000d0a000000000000000000" 未显示该问题或密钥已存在)。

我想这个问题源于这样一个事实:libmemcached API对incrementoffset之间的memcached_increment参数有两种不同的大小要求

memcached_increment_with_initial

前者需要memcached_increment(memcached_st *ptr, const char *key, size_t key_length, uint32_t offset, uint64_t *value) 而后者需要uint32_t,而PHP的扩展代码会将其转换为uint64_t,这几乎等同于unsigned int

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