我已经开始使用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()调用,我期望返回的值是整数.相反,返回的字符串看起来像左边的垃圾,例如该字符串的蚂蚁部分没有相关性.
其他(可能)相关信息:
>我在一系列不同的钥匙上看到了这一点
>我们的Memcached服务器是AWS Elasticache实例
>使用相同缓存节点的其他客户端未使用二进制协议.
>所有客户端都运行相同的操作系统(CentOS),PHP和Memcached版本.
解决方法:
TL;博士;
我挖掘了包含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);
这里的问题是偏移可能是64位宽,也可能不是. libmemcached API告诉我们memcached_increment_with_initial函数签名期望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
"
作为该脚本的输出.请注意,这仅在该memcached服务器上尚不存在密钥foo时才有效.还要注意该字符串的长度为22个字符,显然它不应该在那附近.
如果你看一下该字符串的十六进制表示….
var_dump(bin2hex($memcached->get('foo')));
结果是最后清除垃圾……
string(44) "38353839393334353932000d0a000000000000000000"
正在存储的对象在演员之间明显被破坏.因此,您可能最终得到与我相同的结果,或者您可能最终得到完全破碎的数据,如上所述.它取决于强制转换如何影响当时存储的内存块(这里将进入未定义的行为).此外,唯一看似根本原因是使用带增量的初始值(在此之后使用增量不会证明该问题或密钥已存在).
我猜这个问题源于这样一个事实,即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.