Pull to refresh

Как избежать коллизий при записи в Memcache из PHP

Reading time2 min
Views7.2K
Обычно программисты используют технологии по прямому назначению, но я решил провести эксперимент и попробовать использовать сервер memcached как масштабируемое временное key=value хранилище.
Memcached предназначен для простого кэширования статических данных, потому в нем не предусмотрена система избежания коллизий.

Запись данных


Стандартная ситуация

Предположим, что наше PHP приложение работает на одном сервере, а memcached работает на удаленной машине. Мы без проблем сможем читать и писать в одну и ту же ячейку, потому как приложение не WEB, так что процесс один. Из-за линейности процесса, ему не удастся одновременно записывать разные данные в одну и ту же ячейку.
Два и более процесса

Сегодня нам понадобилось разделить приложение на два сервера и начались проблемы. Возникли коллизии при записи в memcache. Выяснилось, что в 80% случаев приложения пытаются одновременно записать свои данные в одну ячейку. Идеальным решением было бы использование shared memory, но она не масштабируется в отличие о Memcached. В связи с большим объемом кода и предполагаемым временем на переписывание приложений, было принято решение добавить костыль.

Аглоритм чтения — записи



Представим, что к одной ячейке данных в один и тот же момент обращаются для записи два демона, что неизбежно. В обычных условиях мы получим коллизию. Действовать будем так:

  • Процесс1 читает uniqid из ячейки памяти, он пуст.
  • Процесс1 пишет uniqid со своим pid и номером сервера
  • Процесс1 проверяет значение uniqid. Если оно соответствует его ключу, пишет данные.
    В зависимости от ситуации удаляет ключ. Можно дописывать время записи, что позволит разблокировать ячейку данных при аварийном завершении процесса 1
  • Процесс2 читает uniqid из ячейки памяти, он не пуст.
  • Процесс2 ждет, к примеру usleep(rand(1,5)); (циклично) в случае, если нужно записать данные несмотря на доступ к ячейке соседнего объекта. Возможно, нам не потребуется запись, а всего лишь блокировка ячейки для запрета записи.
  • Процесс2 читает uniqid из ячейки памяти, он пуст. Далее то же самое, что и с первым процессом.
  • Процесс3 читает uniqid из ячейки памяти, он пуст.
  • Процесс3 пишет uniqid со своим pid и номером сервера
  • Процесс3 проверяет значение uniqid. Оно не соответствует его ключу.
  • Процесс3 возвращает ошибку, либо действует как то иначе по алгоритму.
  • После записи процессы должны удалить pid из uniqid (тут зависит от ситуации)


Результаты тестирования двух демонов, перезаписывающих одну и ту же ячейку в memcache с разными PID.

Первый демон:

Ожиданий очереди: 17699
Успешных запросов: 100000
Время выполнения: 89.012994 сек.
Второй демон:

Ожиданий очереди: 92999
Успешных запросов: 100000
Время выполнения: 139.522396 сек.

Алгоритм демона:
  • пока мьютекс закрыт, проверяем еще раз.
  • если мьютекс открыт, пишем свой мьютекс.
  • если мьютекс верный — пишем, если неверный — идем в начало.
  • Пишем данные
  • удаляем мьютекс


Из тестов видно, что терялась почти половина данных.

Для нормальных разработчиков: Redis, MemcacheDB и им подобные, нам же еще предстоит переписать это чудо.

С помощью этого алгоритма Memcache может быть использован даже как Gearman. Недостатки самого кэширующего сервера остаются, но в большинстве случаев не проявляются.
Tags:
Hubs:
+14
Comments79

Articles

Change theme settings