» » Используем mcrouter для горизонтального масштабирования memcached

 

Используем mcrouter для горизонтального масштабирования memcached

Автор: admin от 26-06-2019, 10:30, посмотрело: 89

Используем mcrouter для горизонтального масштабирования memcached


Разработка высоконагруженных проектов на любом языке требует особого подхода и применения специальных инструментов, но когда речь заходит о приложениях на PHP, ситуация может обостриться настолько, что приходится разрабатывать, к примеру, собственный сервер приложений. В данной заметке речь пойдет про знакомую всем боль с распределенным хранением сессий и кэшировании данных в memcached и о том, как мы решали эти проблемы в одном «подопечном» проекте.mcrouter.



mcrouter



Это роутер для memcached, разработанный компанией Facebook с целью решения её проблем. Он поддерживает текстовый протокол memcached, который позволяет масштабировать инсталляции memcached до безумных размеров. Подробное описание mcrouter можно найти в этом анонсе. Помимо прочей широкой функциональности он может то, что нужно нам:




  • реплицировать запись;

  • делать fallback на другие сервера группы в случае возникновения ошибки.



За дело!



Конфигурация mcrouter



Перейду сразу к конфигу:



{
 "pools": {
   "pool00": {
     "servers": [
       "mc-0.mc:11211",
       "mc-1.mc:11211",
       "mc-2.mc:11211"
   },
   "pool01": {
     "servers": [
       "mc-1.mc:11211",
       "mc-2.mc:11211",
       "mc-0.mc:11211"
   },
   "pool02": {
     "servers": [
       "mc-2.mc:11211",
       "mc-0.mc:11211",
       "mc-1.mc:11211"
 },
 "route": {
   "type": "OperationselectorRoute",
   "default_policy": "AllMajorityRoute|Pool|pool00",
   "operation_policies": {
     "get": {
       "type": "RandomRoute",
       "children": [
         "MissFailoverRoute|Pool|pool02",
         "MissFailoverRoute|Pool|pool00",
         "MissFailoverRoute|Pool|pool01"
       ]
     }
   }
 }
}


Почему три пула? Почему повторяются серверы? Давайте разберемся, как это работает.




  • В данной конфигурации mcrouter выбирает путь, по которому будет отправлен запрос исходя из команды запроса. Об этом ему говорит тип OperationselectorRoute.

  • GET-запросы попадают в обработчик RandomRoute, который случайным образом выбирает пул или маршрут среди объектов массива children. Каждый элемент этого массива в свою очередь является обработчиком MissFailoverRoute, который пройдется по каждому серверу в пуле, пока не получит ответ с данными, что и будет возвращен клиенту.

  • Если бы мы использовали исключительно MissFailoverRoute с пулом из трех серверов, то все запросы приходили бы сперва на первый экземпляр memcached, а остальные получали бы запросы по остаточному принципу, когда данные отсутствуют. Такой подход привел бы к чрезмерной нагрузке первого в списке сервера, поэтому и было решено сгенерировать три пула с адресами в разной последовательности и выбирать их случайным образом.

  • Все остальные запросы (а это запись) обрабатываются с помощью AllMajorityRoute. Данный обработчик отправляет запросы на все серверы пула и ждет ответов, как минимум, от N/2 + 1 из них. От использования AllSyncRoute для операций записи пришлось отказаться, так как данный метод требует положительного ответа от всех серверов группы — в противном случае он возвратит SERVER_ERROR. Хоть при этом mcrouter и сложит данные в доступные кэши, но вызывающая функция PHP возвратит ошибку и сгенерирует notice. AllMajorityRoute не столь строг и позволяет выводить до половины узлов из эксплуатации без вышеописанных проблем.



Основной минус этой схемы в том, что если данных в кэше действительно нет, то на каждый запрос от клиента фактически будет выполнено N запросов к memcached — ко всем серверам в пуле. Можно сократить количество серверов в пулах, например, до двух: жертвуя надежностью хранения, мы получим большую скорость и меньшую нагрузку от запросов к отсутствующим ключам.



NB: Полезными ссылками для изучения mcrouter могут также оказаться документация в wiki и issues проекта (в том числе и закрытые), представляющие целый кладезь различных конфигураций.



Сборка и запуск mcrouter



Приложение (и сам memcached) у нас работает в Kubernetes — соответственно, там же место и mcrouter. Для сборки контейнера мы используем werf, конфиг для которого будет выглядеть следующим образом:



NB: Листинги, приведённые в статье, опубликованы в репозитории flant/mcrouter.



configVersion: 1
project: mcrouter
deploy:
 namespace: '[[ env ]]'
 helmRelease: '[[ project ]]-[[ env ]]'
---
image: mcrouter
from: ubuntu:16.04
mount:
- from: tmp_dir
 to: /var/lib/apt/lists
- from: build_dir
 to: /var/cache/apt
ansible:
 beforeInstall:
 - name: Install prerequisites
   apt:
     name: [ 'apt-transport-https', 'tzdata', 'locales' ]
     update_cache: yes
 - name: Add mcrouter APT key
   apt_key:
     url: https://facebook.github.io/mcrouter/debrepo/xenial/PUBLIC.KEY
 - name: Add mcrouter Repo
   apt_repository:
     repo: deb https://facebook.github.io/mcrouter/debrepo/xenial xenial contrib
     filename: mcrouter
     update_cache: yes
 - name: Set timezone
   timezone:
     name: "Europe/Moscow"
 - name: Ensure a locale exists
   locale_gen:
     name: en_US.UTF-8
     state: present
 install:
 - name: Install mcrouter
   apt:
     name: [ 'mcrouter' ]


(werf.yaml)



… и набрасываем Helm-чарт. Из интересного — здесь только генератор конфига от количества реплик (если у кого-то есть более лаконичный и элегантный вариант — делитесь в комментариях):



{{- $count := (pluck .Values.global.env .Values.memcached.replicas | first | default .Values.memcached.replicas._default | int) -}}
{{- $pools := dict -}}
{{- $servers := list -}}
{{- /* Заполняем  массив двумя копиями серверов: "0 1 2 0 1 2" */ -}}
{{- range until 2 -}}
 {{- range $i, $_ := until $count -}}
   {{- $servers = append $servers (printf "mc-%d.mc:11211" $i) -}}
 {{- end -}}
{{- end -}}
{{- /* Смещаясь по массиву, получаем N срезов: "[0 1 2] [1 2 0] [2 0 1]" */ -}}
{{- range $i, $_ := until $count -}}
 {{- $pool := dict "servers" (slice $servers $i (add $i $count)) -}}
 {{- $_ := set $pools (printf "MissFailoverRoute|Pool|pool%02d" $i) $pool -}}
{{- end -}}
---
apiVersion: v1
kind: ConfigMap
metadata:
 name: mcrouter
data:
 config.json: |
   {
     "pools": {{- $pools | toJson | replace "MissFailoverRoute|Pool|" "" -}},
     "route": {
       "type": "OperationselectorRoute",
       "default_policy": "AllMajorityRoute|Pool|pool00",
       "operation_policies": {
         "get": {
           "type": "RandomRoute",
           "children": {{- keys $pools | toJson }}
         }
       }
     }
   }


(10-mcrouter.yaml)



Выкатываем в тестовое окружение и проверяем:



# php -a
Interactive mode enabled

php > # Проверяем запись и чтение
php > $m = new Memcached();
php > $maddServer('mcrouter', 11211);
php > var_dump($mset('test', 'value'));
bool(true)
php > var_dump($mget('test'));
string(5) "value"
php > # Работает! Тестируем работу сессий:
php > ini_set('session.save_handler', 'memcached');
php > ini_set('session.save_path', 'mcrouter:11211');
php > var_dump(session_start());
PHP Warning:  Uncaught Error: Failed to create session ID: memcached (path: mcrouter:11211) in php shell code:1
Stack trace:
#0 php shell code(1): session_start()
#1 {main}
  thrown in php shell code on line 1
php > # Не заводится… Попробуем задать session_id:
php > session_id("zzz");
php > var_dump(session_start());
PHP Warning:  session_start(): Cannot send session cookie - headers already sent by (output started at php shell code:1) in php shell code on line 1
PHP Warning:  session_start(): Failed to write session lock: UNKNOWN READ FAILURE in php shell code on line 1
PHP Warning:  session_start(): Failed to write session lock: UNKNOWN READ FAILURE in php shell code on line 1
PHP Warning:  session_start(): Failed to write session lock: UNKNOWN READ FAILURE in php shell code on line 1
PHP Warning:  session_start(): Failed to write session lock: UNKNOWN READ FAILURE in php shell code on line 1
PHP Warning:  session_start(): Failed to write session lock: UNKNOWN READ FAILURE in php shell code on line 1
PHP Warning:  session_start(): Failed to write session lock: UNKNOWN READ FAILURE in php shell code on line 1
PHP Warning:  session_start(): Unable to clear session lock record in php shell code on line 1
PHP Warning:  session_start(): Failed to read session data: memcached (path: mcrouter:11211) in php shell code on line 1
bool(false)
php >


Поиск по тексту ошибки результата не дал, однако по запросу «mcrouter php» в первых рядах значилась старейшая незакрытая проблема проекта — отсутствие поддержки бинарного протокола memcached.



NB: ASCII-протокол в memcached медленнее бинарного, а также штатные средства консистентного хэширования ключей работают только с бинарным протоколом. Но проблем для конкретного случая это не создаёт.



Дело в шляпе: осталось лишь переключиться на ASCII-протокол и всё заработает…. Однако в данном случае привычка искать ответы в документации на php.net сыграла злую шутку. Правильного ответа вы там не найдете… если, конечно, не долистаете до конца, где в секции «User contributed notes» будет верный и незаслуженно заминусованный ответ.



Да, правильное название опции — memcached.sess_binary_protocol. Её необходимо отключить, после чего сессии начнут работать. Осталось лишь положить контейнер с mcrouter в pod с PHP!



Заключение



Таким образом, одними лишь инфраструктурными изменениями нам удалось решить поставленную задачу: вопрос с отказоустойчивостью memcached решен, надежность хранения кэша повышена. Помимо очевидных плюсов для приложения это дало пространство для маневра при проведении работ над платформой: когда все компоненты имеют резерв, жизнь администратора сильно упрощается. Да, этот метод имеет и свои недостатки, может выглядеть «костылем», но если он экономит деньги, хоронит проблему и не вызывает новых — почему бы и нет?



P.S.



Читайте также в нашем блоге:





Источник: Хабр / Интересные публикации

Категория: Программирование

Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

Добавление комментария

Имя:*
E-Mail:
Комментарий:
Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
Введите два слова, показанных на изображении: *