PHP Memcached с бинарным протоколом — ненужные данные возвращаются после `increment()`

Я начал использовать метод increment() клиента PHP Memcached и с тем, что перешел на бинарный протокол. По-видимому, 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"

Учитывая, что ключ не существовал до того, как он был сначала увеличен, а начальное значение 1 было передано вызову increment(), я ожидаю, что возвращаемое значение будет целым числом. Вместо этого возвращаемые строки выглядят как остаточный мусор, например. часть ants этой строки не имеет значения.

Другая (возможно) важная информация:

  • Я вижу это на разных ключах
  • Наш сервер Memcached — это экземпляр AWS Elasticache.
  • Другие клиенты, использующие тот же узел кэша, не используют двоичный протокол.
  • Все клиенты работают под управлением одной и той же ОС (CentOS), версий PHP и Memcached.

person Ross McFarlane    schedule 05.11.2015    source источник
comment
Вы случайно не используете EC2 как 32-битных, так и 64-битных сборок, которые пишут в этот экземпляр memcached? Также какую версию привязок memcached PHP вы используете?   -  person Sherif    schedule 05.11.2015


Ответы (1)


tl;dr;

Это ошибка в коде расширения PHP...


Я копался в коде расширения PHP, который обертывает libmemcached и сам код API libmemcached, но я думаю, что нашел возможную основную причину вашей проблемы...

Если вы посмотрите на реализацию PHP Memcached::increment(), вы увидите в строке 1858 г. из 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 ожидает 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
"

как результат этого скрипта. Обратите внимание, что это работает только в том случае, если ключ foo еще не существует на этом сервере memcached. Также обратите внимание на длину этой строки в 22 символов, хотя очевидно, что она не должна быть близкой к этому.

Если вы посмотрите на шестнадцатеричное представление этой строки....

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

В итоге получается чистый мусор...

 string(44) "38353839393334353932000d0a000000000000000000"

Сохраняемый объект был явно поврежден между слепками. Таким образом, вы можете получить тот же результат, что и я, или вы можете получить полностью неверные данные, как вы продемонстрировали выше. Это зависит от того, как приведение повлияло на фрагмент памяти, хранящийся в то время (что здесь приводит к неопределенному поведению). Кроме того, единственной, по-видимому, основной причиной этого является использование начального значения с приращением (использование increment впоследствии после этого не демонстрирует эту проблему или наличие ключа уже существует).

Я предполагаю, что проблема связана с тем, что API libmemcached имеет два разных требования к размеру для параметра offset между 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, тогда как последний принимает uint64_t, а код расширения PHP приводит оба к unsigned int, что в значительной степени эквивалентно uint32_t.

Это несоответствие в ширине параметра offset, вероятно, является причиной того, что ключ каким-то образом поврежден между вызывающим кодом расширения PHP и кодом API.

person Sherif    schedule 05.11.2015