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.

3

Решение

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


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

Если вы посмотрите на 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 впоследствии после этого не будет продемонстрирована эта проблема или если ключ уже существует).

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

5

Другие решения

Других решений пока нет …