Обычно программисты используют технологии по прямому назначению, но я решил провести эксперимент и попробовать использовать сервер memcached как масштабируемое временное key=value хранилище.
Memcached предназначен для простого кэширования статических данных, потому в нем не предусмотрена система избежания коллизий.
Предположим, что наше PHP приложение работает на одном сервере, а memcached работает на удаленной машине. Мы без проблем сможем читать и писать в одну и ту же ячейку, потому как приложение не WEB, так что процесс один. Из-за линейности процесса, ему не удастся одновременно записывать разные данные в одну и ту же ячейку.
Сегодня нам понадобилось разделить приложение на два сервера и начались проблемы. Возникли коллизии при записи в memcache. Выяснилось, что в 80% случаев приложения пытаются одновременно записать свои данные в одну ячейку. Идеальным решением было бы использование shared memory, но она не масштабируется в отличие о Memcached. В связи с большим объемом кода и предполагаемым временем на переписывание приложений, было принято решение добавить костыль.
Представим, что к одной ячейке данных в один и тот же момент обращаются для записи два демона, что неизбежно. В обычных условиях мы получим коллизию. Действовать будем так:
Результаты тестирования двух демонов, перезаписывающих одну и ту же ячейку в memcache с разными PID.
Алгоритм демона:
Из тестов видно, что терялась почти половина данных.
Для нормальных разработчиков: Redis, MemcacheDB и им подобные, нам же еще предстоит переписать это чудо.
С помощью этого алгоритма Memcache может быть использован даже как Gearman. Недостатки самого кэширующего сервера остаются, но в большинстве случаев не проявляются.
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. Недостатки самого кэширующего сервера остаются, но в большинстве случаев не проявляются.