Используем MongoDB вместо memcached

Материал из Национальной библиотеки им. Н. Э. Баумана
Последнее изменение этой страницы: 14:25, 27 мая 2018.

Что такое Memcashed

Memcached - свободно распространяемое ПО с открытым исходным кодом, позиционирует себя как высокопроизводительную, распределенную систему кэширования объектов памяти, универсальную по своей природе, но предназначенную для использования в ускорении динамических веб-приложений путем облегчения нагрузки на базу данных. По своей сути Memcached-это хранилище ключей и значений в памяти для небольших фрагментов произвольных данных (строк, объектов) из результатов вызовов баз данных, вызовов API или отрисовки страниц. [Источник 1]

Основные функции Memcashed [Источник 2]:

  • Простое хранилище типа Key/Value Сервер не заботится о том, как выглядят ваши данные. Элементы состоят из ключа, времени истечения срока действия, необязательных флагов и необработанных данных. Сервер не понимает структуры данных; необходимо загрузить данные, которые предварительно сериализованы.
  • Половина логики в клиенте, половина в сервере Механизм Memcached реализован частично на стороне клиента, а частично не стороне сервера. Клиент понимает, как выбрать сервер для чтения или записи элемента, и что делать, если он не может связаться с сервером. Сервер же понимает, как хранить и извлекать элементы. Он также решает, когда освободить или повторно использовать память.
  • Серверы отключены друг от друга Memcached серверы не знают друг о друге. Нет помех, нет синхронизации, нет вещания, нет репликаций. Добавление серверов увеличивает объем доступной памяти. Аннулирование кэша упрощается, так как клиенты удаляют или перезаписывают данные на сервере, которому они принадлежат напрямую.
  • Высокая скорость обработки данных Обеспечена почти детерминированную скорость запросов для всех вариантов использования. Запросы на медленных машинах должны выполняться в пределах 1 мс. Высокопроизводительные серверы могут обслуживать миллионы ключей в секунду.
  • Забывание Memcached является, по умолчанию, наиболее недавно использованным кэшем. Срок действия элементов истекает через определенное время. Это является элегантными решениями для многих проблем. Отсутствие "пауз" в ожидании сборщика мусора гарантирует низкую задержку, а свободное пространство "лениво" освобождается.
  • Очистка Кэша Вместо того, чтобы транслировать изменения на все доступные хосты, клиенты напрямую обращаются к серверу, на котором хранятся данные, подлежащие аннулированию.

Memcashed используется такими ресурсами как: LiveJournal, Wikipedia, WordPress.com, Facebook, Twitter, Reddit, YouTube, GitHub ...

Почему это актуально

Безопасность

В начале 2018 были произведена DDOS атака на GitHub мощностью 1.35 ТБ/сек [Источник 3] Атака базировалась на том, что по умолчанию memcached прослушивает весь UDP трафик на порте 11211, а некоторые технические специалисты устанавливают memcached и забывают о его настройках по умолчанию, также забывая о том, что он прослушивает весь UDP трафик отправляемый на сервер и отвечает, в среднем, амплифицированными в 9000-10000 раз ответами.

Нейтрализация такой атаки может быть достигнута несколькими способами, наиболее простые из которых: установка межсетевого экрана на udp трафик из порта 11211 (маловероятно появление легитимного трафика из данного порта) или установки входящего rate-limit’а. Это подразумевает, что у вас будет достаточно полосы для обработки такой атаки перед сетевым экраном. Для провайдера крупнее есть две известные опции:

  • Фильтрация по flowspec;
  • Использование extended community.

Производительность

Понятие «кэш» имеет два различных типа использования. [Источник 4]

  • Кэш применяемый, чтобы снизить нагрузку на перестающую справляться базу данных (или другие подсистемы). Например, пусть есть 100 запросов в секунду на чтение некоторого ресурса. Включив кэширование и выставив маленькое время устаревания кэша (например, 1 секунду), можно снизить нагрузку на базу в 100 раз: ведь теперь до СУБД доходит только один запрос из ста. Также исчезает опасность, что пользователь увидит устаревшие данные: ведь время устаревания очень мало.
  • Кэш более-менее статических кусков страницы (или даже всей страницы целиком), и применяют его, чтобы снизить время формирования страницы (в том числе редко посещаемой). Он отличается от первого тем, что время жизни кэшированных записей велико (часы или даже дни), данный тип кэша имеет проблему актуализации данных. Для этого применяют тэги: каждый кусочек данных в кэше, имеющий отношение к некоторому крупному ресурсу X, помечают теми или иными тэгами. При изменении ресурса X дают команду «очистить тэг X».

Для первого варианта использования кэша ничего лучше, чем memcached, похоже, не изобретено. А вот для второго memcached работает не столь хорошо, и тут можно попробовать использовать MongoDB вместо Memcached. Особенно следует обратить внимание на MongoDB, если используемый кэш:

  • Относительно невелик (верхний предел — сотни гигабайт);
  • Содержит много «долгоживущих» записей, устаревающих за часы и дни (или вообще никогда не устаревающих);
  • Вы существенно используете тэги и полагаетесь на то, что операция очистки тэга должна работать надежно;
  • Кэш хотелось бы сделать общим и одинаково легко доступным (т.е. реплицируемым) на всех машинах кластера, в том числе в нескольких датацентрах;
  • Вам не хочется беспокоиться, когда одна из машин для кэша на какое-то время перестанет быть доступной.

MongoDB и ее репликация с автоматическим failover мастера (превращением реплики в мастера при «смерти» последнего) позволяют гарантировать надежность очистки того или иного тэга. В memcached же с этим проблема: серверы Memcached независимы друг от друга, и для удаления тэга нужно «пойти» на каждый из них с командой очистки. Но что, если в этот момент какой-то из серверов Memcached окажется недоступным? Он «потеряет» команду очистки и начнет отдавать старые данные; MongoDB данную проблему решает.

Также MongoDB очень быстра в операциях чтения, ведь она использует событийно-ориентированный механизм работы с соединениями и memory mapped files, т.е. чтение производится напрямую из оперативной памяти при достаточном ее количестве, а не с диска.

Основная информация о MongoDB и Memcashed

Сравнение MongoDB и Memcashed [Источник 5]
Параметры Memcashed MongoDB
Описание Хранилище данных типа Key/Value, изначально предназначенное для кеширования Одно из наиболее популярных документно-ориентированных хранилищ данных
Основная модель хранения данных База данных типа Key/Value Документно-ориентированная база данных
Дополнительная модель хранения данных База данных типа Key/Value
Вебсайт www.memcached.org www.mongodb.com
Документация Wiki manual
Разработчик Danga Interactive MongoDB, Inc
Дата релиза 2003 2009
Текущая версия 1.5.6, February 2018 3.6.4, April 2018
Лицензия Открытое программное обеспечение Открытое программное обеспечение
Язык реализации С С++]
Поддерживаемые операционные системы сервера FreeBSD, Linux, OS X, Unix, Windows Linux, OS X, Unix, Windows
Поддержка вторичных индексов Нет Да
API и другие методы доступа Проприетарный протокол Проприетарный протокол с использованием JSON
Поддерживаемые языки программирования .Net, C, C++, ColdFusion, Erlang, Java, Lisp, Lua, OCaml, Perl, PHP, Python, Ruby Actionscript, C, C#, C++, Clojure, ColdFusion, D, Dart, Delphi, Erlang, Go, Groovy, Haskell, Java, JavaScript, Lisp, Lua, MatLab, Perl, PHP, PowerShell, Prolog, Python, R , Ruby, Scala, Smalltalk
Язык написания скриптов на стороне сервера JavaScript
Методы разбиения Разбиение не поддерживается Sharding
Методы репликаций Репликации не поддерживаются Master-slave репликации
MapReduce Нет Да
Концепции согласования Согласование по событию; Немедленное согласование
Параллелизм Да Да
Возможность хранения только в памяти Да
Контроль доступа пользователей С использованием SASL протокола Назначение прав для отдельных пользователей и ролей

CacheLRUd

CacheLRUd[Источник 6] - небольшая утилита, выложенная на GitHub, по сути представляет собой демона, для LRU-удаления записей в различных СУБД, но в основном ориентированная на MongoDB.

LRU (least recently used) — это алгоритм, при котором вытесняются значения, которые дольше всего не запрашивались. Соответственно, необходимо хранить время последнего запроса к значению. И как только число закэшированных значений превосходит N необходимо вытеснить из кеша значение, которое дольше всего не запрашивалось.

Описание утилиты

MongoDB очень быстра (почти так же быстра как memcached на чтениях), она поддерживает репликацию с автоматическим-обходом отказа, сегментирование и т.д. Но MongoDB не имеет алгоритма чистки памяти LRU, и конечно, нельзя обновить поле "последнее попадание" в своём кеше.

CacheLRUd пытается решить эту проблему: он следит за размером базы данныхи удаляет ключи, значения которых не использовались слишком долго.

Для того, чтобы CacheLRUd знал, какие ключи недавно использовались, кэширующий слой должен уведомить демона о том, какие ключи были прочитаны недавно, отправив UDP пакеты к нему. Так как UDP асинхронен, он не блокирует работу приложения, поэтому можно отправлять пакет UDP на каждое попадание в кэш, даже на другой датацентр (если у вас нет миллионов просмотров страниц в секунду, конечно). Рекомендуется установить службу CacheLRUd на каждом MongoDB узле и отправлять уведомления UDP только для работающих узлов.

Установка на Ubuntu 18.04 LTS

Перед установкой CacheLRUd необходимо установить и настроить MongoDB (или другую СУБД, но тогда придется настраивать утилиту специально под выбранную СУБД), установку MongoDB можно отдельно посмотреть в этой статье. Установка самой утилиты производится с GitHub следующей последовательностью команд. Для начала необходимо перейти директорию /opt и скачать туда проект с GitHub.
# Install the service on EACH MongoDB NODE:
cd /opt
git clone git@github.com:DmitryKoterov/cachelrud.git

Теперь нужно создать ссылку на файл конфингураций в директории /etc/cachelrud, после этого можно запустить демон

# Configure:
cp /opt/cachelrud/cachelrud.conf /etc/cachelrud.conf  # and then edit
# For Debian/Ubuntu:
update-rc.d cachelrud defaults

Настройка CacheLRUd

После установки CacheLRUd в директории /etc появляется файл cachelrud.conf. В данном текстовом файле настраивается утилита, рассмотрим подробнее основные ее настройки:

  • Установка UID демона. Устанавливается UID демона для выполнения.
setuid = YOUR_UID
  • Настройка логирования. В утилите присутствует возможность настройки способа ведения логов, ниже представлен вариант, предлагаемый по умолчанию:
log_type = syslog 
log_syslog_addr = /dev/log 
log_syslog_facility = local3 
log_file_path = /var/log/cachelrud.log 
  • Соединения с базой данных. Данными переменными производится настройка демона для доступа к определенной базе данных, шаблон, предлагаемый по умолчания, имеет вид:
dsn = "mongodb://user:pasword@host1,host2,.../?replica_set=replica_set_name"
dbname = "???"
collection = "???"
  • Максимальный размер коллекции. Данное поле показывает после превышения какого размера коллекции будет включаться процесс, который удаляет устаревшие ключи LRU, по умолчанию имеет размер:
maxsize = 2G
  • Настройка UDP. С помощью данных настроек указывается хост, который будет слушаться демоном CacheLRUd, и порт, по умолчанию:
listenhost = *
listenport = 43521
  • Настройка маркировки. Данным полем настраивается число ключей для сбора и учета однотипных попаданий, перед тем как пометить их как используемые в кеш, пример настройки по умолчанию:
bucket_size = 100
  • Время максимального ожидания кэш попадания. Если в течении этого времени не было кеш попаданий, объем информации (bucket) отправляется на обновление, даже если в нем находится меньше ключей, чем указано в поле bucket_size, следует отметить, что в сильно нагруженных системах нет значительных задержек между попаданиями кэша. Пример настройки по умолчанию:
bucket_flush_max_time = 10
  • Ограничение (снизу) количества ключей для удаления. Данный параметр определяет минимальное количество ключей для удаления за один раунд, если размер коллекции превысил максимальный размер коллекции. Пример настроек по умолчанию:
reaper_one_round_min = 10
  • Ограничение (сверху) количества ключей для удаления. Данный параметр определяет максимальное количество ключей для удаления за один раунд, если размер коллекции превысил максимальный размер коллекции. Пример настроек по умолчанию:
reaper_one_round_max = 100
  • Настройка задержки между раундами. Если после раунда количество ключей в коллекции все равно превышает максимальный размер коллекции, то демон "заснет" (sleep) на указанное количество секунд, перед запуском нового раунда. Пример настройки по умолчанию:
reaper_small_sleep_between_rounds = 0.1
  • Настройка задержки для 'reaper' . Если все ключи переданы процессу, который удаляет устаревшие ключи LRU, то демон засыпает на указанное количество секунд перед перед тем, как снова проверить размер коллекции. Пример настройки по умолчанию:
reaper_recheck_sleep = 60
  • Имя поля с метками времени. Устанавливается имя для поля, в котором будут находится метки времени для коллекции. Пример настройки по умолчанию:
timestampfield = h

Запуск

Так как последние изменения CachedRLUd были в 2014 году, необходимы некоторые дополнительные настроечные действия:

  • Установка Python2.7;
  • Установка PIP2;
  • Через PIP необходимо установить библиотеку pymongo версии не выше 3.0 (я рекомендую 2.6.3, так как, видимо, именно на ней разрабатывался данный продукт), в новых версиях отсутствует часть используемых в проекте функций;
  • В конфигурационном файле /etc/cacherlud.conf необходимо раскомментировать строчки, где указана конкретная база данных, а также (при необходимости) изменить режим доступа к БД;
  • В файле /opt/cacherlud/cacherlud/storage/mongo.py необходимо внести изменения в парсер входной строки в соответствии с выбранным на прошлом шаге режиме доступа;
  • Запустить программу с параметром --debug и исправить другие возможные ошибки.

Для запуска необходимо:

  • Создать mongo базу данных, в ней создать коллекции и заполнить какими-либо данными;
  • Сведения о созданной базе данных внести в файл /etc/cacherlud.conf, также рекомендую в целях безопасности сменить порт по умолчанию;
  • Запустить продукт командой:
     sudo /opt/cacherlud/bin/cacherlud --debug /etc/cacherlud.conf 
  • Теперь можно отправлять UDP-запросы специального вида на выделенный порт, для отправки UDP-запросов при демонстрации используется самописный Python-код.

Тонкости работы с MongoDB

Отправление UDP-пакета демону Для того чтобы уведомить утилиту, что к ключу ('key') в коллекции ('collection_name') в кэше было недавно обращение, нужно отправить демону (по умолчанию на порт 43521) UDP-пакет вида:

  • collection_name:key

Если было зарегистрировано много обращений, то их можно сгруппировать в один UDP-пакет вида (разделителем является символ новой строки):

  • collection_name:key1
  • collection_name:key2

Работа с набором реплик

В ходе работы ваше приложение должно сообщать демону CacheLRUd (ферме демонов), какие записи в кэше оно читает. Приложение, очевидно, не может это делать в синхронном режиме, потому что:

  • Мастер-база может оказаться в другом датацентре относительно текущей веб-морды приложения;
  • MongoDB использует протокол TCP, грозящий timeout-ами и "подвисанием" клиента при нестабильности связи;
  • Не следует выполнять запись при каждом чтении в распределенных системах.

Для решения задачи применяется протокол UDP: приложение посылает UDP-пакеты со списком недавно прочитанных ключей тому или иному демону CacheLRUd. Какому именно - вы можете решить самостоятельно в зависимости от нагрузки.

Для удобства рассмотрим конкретную ситуацию, пусть имеется набор реплик на трёх машинах A, B и C. A является мастером в настоящий момент. Запустим CacheLRUd демон на всех узлах для обеспечения отказоустойчивости. Тогда есть два способа сконфигурировать /etc/cachelrud.conf:

  • Если вы ходите, чтобы только один CacheLRUd демон был 'reaper' (процессом, который удаляет устаревшие ключи LRU), пропишите в cachelrud.conf:
     dsn = "mongodb://user:password@localhost/" 
    и отправляйте UDP-пакеты только узлу-мастеру (А). Узел мастера обычно легко обнаруживается автоматически, можно например запустить службу client.getConnections() на стороне клиента и проверить поле connection_type. Если произошла смена узла мастера (например с A на В), нужно настроить CacheLRUd на узле В, чтобы он был процессом, который удаляет устаревшие ключи LRU, а ваше приложение должно тоже быть перенастроено и отправлять UDP-пакеты только узлу B.


  • Если одного процесса, который удаляет устаревшие ключи LRU, вам недостаточно и вы хотите настроить параллельную работу узлов, произведите следующую настройку в файле /etc/cachelrud.conf:
     dsn = "mongodb://user:password@localhost/?replicaSet=YOUR_RS" 
    После этого вы сможете отправлять UDP-пакеты всем CacheLRUd демонам: они будут принимать их и запускать процесс, который удаляет устаревшие ключи LRU, параллельно.

Производительность

Следует поманить, что MongoDB очень быстра в операциях чтения, ведь она использует событийно-ориентированный механизм работы с соединениями и memory mapped files, т.е. чтение производится напрямую из оперативной памяти при достаточном ее количестве, а не с диска. Некоторые специалисты считают, что MongoDB настолько же быстра, как memcached, но вероятно это все же не так: просто разница между ними тонет на фоне сетевых задержек.

Вот как выглядит результат работы CacheLRUd на одном не слишком нагруженном проекте. Видно, что размер коллекции с кэшем действительно поддерживается постоянным на заданном в конфиге уровне 1G.

Работа CacheLRUd

Реализация поддержки других баз данных

Утилита CacheLRUd написана на Python, вы можете добавить поддержку новой базу данных, для этого вам нужно создать файл вида: lib/cachelrud/storage/your_database_name.py, в качестве образца используйте файл mongodb.py.

Дополнительные инструменты

  • CacheLRUdWrapper: это простенький класс для общения с CacheLRUd из кода приложения на PHP, оборачивающий стандартный Zend_Cache_Backend, находится в репозитории Github вместе с CacheLRUd;
  • Zend_Cache_Backend_Mongo: это реализация Zend_Cache_Bachend для MongoDB из соседнего GitHub-репозитория. Можно обернуть объект данного класса в CacheLRUdWrapper, и получить интерфейс для работы с LRU-кэшем в MongoDB в стиле ZF1.

Заключение

Несмотря на определенные плюсы использования MongoDB вместо Memcached, найти каких-либо реальных историй применения таких технологий не удалось. Утилита, организующая с помощью MongoDB замену Memcached, на данный момент не развивается, последние правки в репозитории разработчиков датированы 2014 годом. Если у разработчиков появится реальный интерес к этому продукту, то имеет смысл его переписать практически с нуля, так как в нем используются слишком старые технологии:

  • Python версии 2, несмотря на то что даже на сегодняшний день многие продукты продолжают использовать Python2.7, от него все же стоит отказаться, так как официального его поддержка должна прекратиться в 2020 году;
  • Библиотека pymongo версии не выше 3.0, использование функций из данных библиотек не является безопасным по мнению разработчиков MongoDB, а использование новой библиотеки ведет к полной переписи кода, обеспечивающего подключение к БД.

Источники

  1. Memcashed // Memcashed Documentation. [2003—2018]. Дата обновления: 21.12.2017. URL: http://memcached.org (дата обращения: 01.04.2018).
  2. Memcashed // Memcashed Wiki. [2003—2018]. Дата обновления: 21.12.2017. URL: https://github.com/memcached/memcached/wiki (дата обращения: 03.04.2018).
  3. На GitHub обрушилась DDoS-атака мощностью 1,35 Тб/сек, и благодарить за это нужно Memcached // Хакер [1999—2018]. Дата обновления: 02.03.2018. URL: https://xakep.ru/2018/03/02/github-memcached-ddos/ (дата обращения: 03.03.2018).
  4. Используем MongoDB вместо memcached: быть или не быть? // habr.com [2006—2018]. Дата обновления: 16.04.2014. URL: https://habr.com/post/219481/ (дата обращения: 02.05.2018).
  5. System Properties Comparison Memcached vs. MongoDB vs. Redis // DB-engines.com. Дата обновления: 2018. https://db-engines.com/en/system/Memcached%3BMongoDB%3BRedis (дата обращения: 02.05.2018).
  6. CacheLRUd: добавляет функцию LRU-очистки данных (как в memcached) в MongoDB //Дмитрий Котеров, Лаборатория dk. [1999-2018]. Дата обновления: 10.04.2014. http://dklab.ru/lib/cachelrud/ (дата обращения: 06.05.2018).