Redis: репликация, часть 2 — Master-Slave репликация, и Redis Sentinel

Автор: | 03/29/2019
 

Продолжение серии по Redis репликации.

Первая часть — Redis: репликация, часть 1 — обзор. Replication vs Sharding. Sentinel vs Cluster. Топология Redis.

Третья часть — Redis: репликация, часть 3 — redis-py и работа с Redis Sentinel из Python.

Чертвёртая часть — Redis: репликация, часть 4 — написание Ansible роли.

Собственно, вся история началась с того, что мы решили избавиться от memcached.

На данный момент у нас на серверах запущены и memcahced, и Redis.

И memcached, и Redis работают как standalone приложения на разных сервера, т.е. их инстансы никак не связаны друг с другом, из-за чего возникает проблема:

  • есть три хоста бекенда, которые расположены за AWS Applcation Load balancer
  • на ALB включены Sticky Sessions, но они работают через cookies, которые игнорируются нашими мобильными приложениями (iOS/Android)
  • соответственно, когда клиент обращается к бекенду — иногда он может получить закешированные данные, которые уже были удалены на другом инстансе memcached или Redis

Такая схема пререшла к нам после миграции приложения со старой инфрастуктуры, на которой был только один сервер, и до сих пор не доходили руки ни у меня, ни у команды бекенд-девелоперов привести её в порядок, хотя мысль об избавлении от memcached и настройке репликации Redis обсуждалась давно.

Сейчас проблема решается с помощью костылей на бекенде, который перепроверяет данные, а что бы избавиться от этого — решили:

  1. избавиться от memcached вообще, т.к. Redis может выполнять те же функции, которые сейчас выполняет memcached
  2. настроить репликацию данных Redis между всеми хостами

Настройка такой репликации и описывается в этом посте.

Сначала — базовая, и второй пример — с Sentinel.

Запускать будем на AWS EC2 инстансах с Debian 9.

Для работы с сервисами используем  три домена — redis-0.setevoy.org.ua для мастера, redis-1.setevoy.org.ua и redis-2.setevoy.org.ua для двух слейвов.

Слейв в минимальном сетапе может быть и один, но т.к. второй сетап будет с Sentinel — то создадим сразу три инстанса.

Базовая Master-Slave репликация

В этом варианте слейвы являются read-only репликами мастера, поддерживая у себя ту же информацию, которая добавляется на мастер.

Мастер шлёт на слейвы все изменения, которые выполняются в данных — записи клиентов, обновления ключей и т.д.

В случае, если связь между мастером и слейвом обрывается — слейв пытается подключиться к мастеру и выполнить частичную сихронизацию, обновив данные с того места, на котором произошёл («Ни единого разрыва!« (с)) обрыв связи.

В случае, если частичная синхронизация невозможна — слейв запрашивает у мастера полную синхронизацию. Тогда мастер создаёт полный снапшот всех имеющихся у него данных, отправляет их на слейв, а затем продолжает слать обновления в обычном режиме.

Несколько нюансов такой репликации:

  • один мастер может иметь несколько слейвов
  • слейвы могут принимать подключения от других слейвов, создавая таким образом «каскад» реплицированных нод — от мастера «вверху», слейвов «в середине» и слейвов «внизу»
  • категорически рекомендуется использовать persistence на мастере и слейвах во избежание потери данных, см. Safety of replication when master has persistence turned off
  • слейвы по умолчанию работают в read-only режиме, см. Read-only slave

Настройка Redis Master

Устанавливаем Redis:

root@redis-0:/home/admin# apt -y install redis-server

Редактируем /etc/redis/redis.conf, в bind задаём прослушивание всех интерфейсов:

...
bind 0.0.0.0
...

Можно указать IP через пробел, например:

...
bind 127.0.0.1 18.194.229.23
...

Другие интересные тут опции:

  • port 6379 — понятно
  • slave-read-only yes — слейвы принимают запросы только на read, на мастере роли не играет
  • requirepass foobared — мастер требует авторизацию с паролем foobared
  • appendonly yes и appendfilename "appendonly.aof" — уменьшаем вероятность потери последних данных, см. Redis Persistence

Перезапускаем сервис:

root@redis-0:/home/admin# systemctl restart redis

Проверяем, передавая с помощью опции -a пароль:

root@redis-0:/home/admin# redis-cli -a foobared ping
PONG

Проверяем статус репликации:

root@redis-0:/home/admin# redis-cli -a foobared info replication
Replication
role:master
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

Добавляем данные:

root@redis-0:/home/admin# redis-cli -a foobared set test 'test'
OK

Получаем их:

root@redis-0:/home/admin# redis-cli -a foobared get test
"test"

ОК — тут всё работает.

Настройка Redis Slave

На оставшихся двух хостах выполняем настройку слейвов.

Тут всё одинаково, просто повторяем на обоих.

Устанавливаем:

root@redis-1:/home/admin# apt -y install redis-server

Редактируем /etc/redis/redis.conf:

...
slaveof redis-0.setevoy.org.ua 6379
...
masterauth foobared
...
requirepass foobared
...

Тут:

  • slaveof — указываем хост и порт мастера
  • masterauth — пароль для авторизации на мастере
  • requirepass — пароль для авторизации на слейве

Перезапускаем:

root@redis-1:/home/admin# systemctl restart redis

Проверяем статус:

root@redis-1:/home/admin# redis-cli -a foobared info replication
Replication
role:slave
master_host:redis-0.setevoy.org.ua
master_port:6379
master_link_status:up
master_last_io_seconds_ago:5
master_sync_in_progress:0
...

Проверяем лог:

root@redis-1:/home/admin# tail -f /var/log/redis/redis-server.log
16961:S 29 Mar 10:54:36.263 * Connecting to MASTER redis-0.setevoy.org.ua:6379
16961:S 29 Mar 10:54:36.308 * MASTER <-> SLAVE sync started
16961:S 29 Mar 10:54:36.309 * Non blocking connect for SYNC fired the event.
16961:S 29 Mar 10:54:36.309 * Master replied to PING, replication can continue...
16961:S 29 Mar 10:54:36.310 * Partial resynchronization not possible (no cached master)
16961:S 29 Mar 10:54:36.311 * Full resync from master: 93585eeb7e32c0550c35f8d4935c9a18c4177ab9:1
16961:S 29 Mar 10:54:36.383 * MASTER <-> SLAVE sync: receiving 92 bytes from master
16961:S 29 Mar 10:54:36.383 * MASTER <-> SLAVE sync: Flushing old data
16961:S 29 Mar 10:54:36.383 * MASTER <-> SLAVE sync: Loading DB in memory
16961:S 29 Mar 10:54:36.383 * MASTER <-> SLAVE sync: Finished with success

Коннект к мастеру есть, синхронизация прошла — окей, проверяем данные:

root@redis-1:/home/admin# redis-cli -a foobared get test
"test"

На слейве данные тоже есть.

Переключение Slave => Master роли

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

Если сейчас на слейве попробовать записать данные — Redis вернёт ошибку, т.к. по умолчанию слейвы находятся в read-only режиме:

...
slave-read-only yes
...

Пробуем добавить данные на слейве:

root@redis-1:/home/admin# redis-cli -a foobared set test2 'test2'
(error) READONLY You can't write against a read only slave.

Подключаемся к слейву:

root@redis-1:/home/admin# redis-cli

Авторизируемся:

127.0.0.1:6379> auth foobared
OK

Отключаем слейв-роль:

127.0.0.1:6379> slaveof no one
OK

Проверяем статус:

127.0.0.1:6379> info replication
Replication
role:master
connected_slaves:0
master_repl_offset:1989
repl_backlog_active:0
repl_backlog_size:1048576

Добавляем новый ключ ещё раз:

127.0.0.1:6379> set test2 'test2'
OK

Проверяем:

127.0.0.1:6379> get test2
"test2"

Учтите, что т.к. в файле настроек /etc/redis/redis.conf всё ещё установлен параметр slaveof — то при рестарте эта нода снова станет слейвом.

Redis Sentinel

Теперь дополним наш кластер, и добавим Redis в режиме Sentinel, который будет мониторить статус нод, и выполнять переключение ролей автоматически.

Общая схема работы будет выгядеть так:

Тут:

  • M1 = Master
  • R1 = Replica 1 / Slave 1
  • R2 = Replica 2 / Slave 2
  • S1 = Sentinel 1
  • S2 = Sentinel 2
  • S3 = Sentinel 3

M1 и S1 — будут на redis-0, R1 и S2 — на redis-1, R2 и S3 — на redis-2.

Запуск Sentinel

Для запуска Sentinel используем тот же Redis-сервер, но для него создадим отдельный конфиг /etc/redis/sentinel.conf.

Добавляем его сначала на хосте с Redis Master:

sentinel monitor redis-test redis-0.setevoy.org.ua 6379 2
sentinel down-after-milliseconds redis-test 6001
sentinel failover-timeout redis-test 60000
sentinel parallel-syncs redis-test 1
bind 0.0.0.0
sentinel auth-pass redis-test foobared

Тут:

  • monitor — адрес мастер-ноды, которую будем мониторить, 2 — кол-во инстансов Sentinel для принятия решений
  • down-after-milliseconds — время, после которого мастер будет считаться упавшим
  • failover-timeout — время ожидания после смены ролей слейва на мастер в случае, если мастер вышел из строя
  • parallel-syncs — время одновременной сихронизации слейвов после переназначения роли мастера

Запускаем:

root@redis-0:/home/admin# redis-server /etc/redis/sentinel.conf --sentinel
10447:X 29 Mar 14:15:53.192 * Increased maximum number of open files to 10032 (it was originally set to 1024).
_._
_.-``__ ''-._
_.-``    `.  `_.  ''-._           Redis 3.2.6 (00000000/0) 64 bit
.-`` .-```.  ```\/    _.,_ ''-._
(    '      ,       .-`  | `,    )     Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'|     Port: 26379
|    `-._   `._    /     _.-'    |     PID: 10447
`-._    `-._  `-./  _.-'    _.-'
|`-._`-._    `-.__.-'    _.-'_.-'|
|    `-._`-._        _.-'_.-'    |           http://redis.io
`-._    `-._`-.__.-'_.-'    _.-'
|`-._`-._    `-.__.-'    _.-'_.-'|
|    `-._`-._        _.-'_.-'    |
`-._    `-._`-.__.-'_.-'    _.-'
`-._    `-.__.-'    _.-'
`-._        _.-'
`-.__.-'
10447:X 29 Mar 14:15:53.193 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
10447:X 29 Mar 14:15:53.195 # Sentinel ID is e9fb72c8edb8ec2028e6ce820b9e72e56e07cf1e
10447:X 29 Mar 14:15:53.195 # +monitor master redis-test 35.158.154.25 6379 quorum 2
10447:X 29 Mar 14:15:53.196 * +slave slave 3.121.223.95:6379 3.121.223.95 6379 @ redis-test 35.158.154.25 6379
10447:X 29 Mar 14:16:43.402 * +slave slave 18.194.45.17:6379 18.194.45.17 6379 @ redis-test 35.158.154.25 6379

Проверяем статус Sentinel — подключаемся на порт 26379:

root@redis-0:/home/admin# redis-cli -p 26379 info sentinel
Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=redis-test,status=ok,address=35.158.154.25:6379,slaves=2,sentinels=1

Тут:

  • master0:name=redis-test,status=ok — мастер работает
  • slaves=2 — у него два слейва
  • sentinels=1 — пока запущен только один Sentinel

Можно получить различные данные о статусе репликации.

Например — адрес текущего мастера:

root@redis-0:/home/admin# redis-cli -p 26379 sentinel get-master-addr-by-name redis-test
1) "35.158.154.25"
2) "6379"

Повторяем запуск Sentinel на слейвах, используя такой же конфиг, как на мастере — в логе Sentinel на мастере должны появится сообщения о новых Sentinel инстансах:

...
10447:X 29 Mar 14:18:40.437 * +sentinel sentinel fdc750c7d6388a6142d9e27b68172f5846e75d8c 172.31.36.239 26379 @ redis-test 35.158.154.25 6379
10447:X 29 Mar 14:18:42.725 * +sentinel sentinel ecddb26cd27c9a17c4251078c977761faa7a3250 172.31.35.218 26379 @ redis-test 35.158.154.25 6379
...

Проверяем статус ещё раз:

root@redis-0:/home/admin# redis-cli -p 26379 info sentinel
Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=redis-test,status=ok,address=18.194.229.23:6379,slaves=2,sentinels=3

sentinels=3 — окей.

Кроме всего прочего — Sentinel автоматически выполняет настройку:

root@redis-1:/home/admin# cat /etc/redis/sentinel.conf
sentinel myid fdc750c7d6388a6142d9e27b68172f5846e75d8c
sentinel monitor redis-test 35.158.154.25 6379 2
sentinel down-after-milliseconds redis-test 6001
bind 0.0.0.0
sentinel failover-timeout redis-test 60000
Generated by CONFIG REWRITE
port 26379
dir "/home/admin"
sentinel auth-pass redis-test foobared
sentinel config-epoch redis-test 0
sentinel leader-epoch redis-test 0
sentinel known-slave redis-test 18.194.45.17 6379
sentinel known-slave redis-test 3.121.223.95 6379
sentinel known-sentinel redis-test 172.31.35.218 26379 ecddb26cd27c9a17c4251078c977761faa7a3250
sentinel known-sentinel redis-test 172.31.47.184 26379 e9fb72c8edb8ec2028e6ce820b9e72e56e07cf1e
sentinel current-epoch 0

Тут добавился sentinel myid fdc750c7d6388a6142d9e27b68172f5846e75d8c, и целый блок после Generated by CONFIG REWRITE.

Redis Sentinel Automatic Failover

Теперь проверим что будет, если умрёт мастер.

Сделать это можно просто убив его вручную, напрмиер через kill -9, либо с помощью redis-cli — отправив ему команду DEBUG и указав либо время, на которое его надо остановить, либо сигнал, от которого ему умереть 🙂

root@redis-0:/home/admin# redis-cli -a foobared DEBUG sleep 30

В логе Sentinel на мастере:

...
10447:X 29 Mar 14:24:56.549 # +sdown master redis-test 35.158.154.25 6379
10447:X 29 Mar 14:24:56.614 # +new-epoch 1
10447:X 29 Mar 14:24:56.615 # +vote-for-leader ecddb26cd27c9a17c4251078c977761faa7a3250 1
10447:X 29 Mar 14:24:56.649 # +odown master redis-test 35.158.154.25 6379 #quorum 3/2
10447:X 29 Mar 14:24:56.649 # Next failover delay: I will not start a failover before Fri Mar 29 14:26:57 2019
10447:X 29 Mar 14:24:57.686 # +config-update-from sentinel ecddb26cd27c9a17c4251078c977761faa7a3250 172.31.35.218 26379 @ redis-test 35.158.154.25 6379
10447:X 29 Mar 14:24:57.686 # +switch-master redis-test 35.158.154.25 6379 3.121.223.95 6379
10447:X 29 Mar 14:24:57.686 * +slave slave 18.194.45.17:6379 18.194.45.17 6379 @ redis-test 3.121.223.95 6379
10447:X 29 Mar 14:24:57.686 * +slave slave 35.158.154.25:6379 35.158.154.25 6379 @ redis-test 3.121.223.95 6379
10447:X 29 Mar 14:25:03.724 # +sdown slave 35.158.154.25:6379 35.158.154.25 6379 @ redis-test 3.121.223.95 6379
...

Сейчас тут особо интересны вот эти две строки:

...
10384:X 29 Mar 14:24:57.686 # +config-update-from sentinel ecddb26cd27c9a17c4251078c977761faa7a3250 172.31.35.218 26379 @ redis-test 35.158.154.25 6379
10384:X 29 Mar 14:24:57.686 # +switch-master redis-test 35.158.154.25 6379 3.121.223.95 6379
...

Sentinel выполнил автоматическую перенастройку слейва в мастер.

35.158.154.25 — старый мастер, который умер, а 3.121.223.95 — новый мастер, который раньше был первым слейвом, с именем хоста redis-1.

Проверяем запись в него:

root@redis-1:/home/admin# redis-cli -a foobared set test3 'test3'
OK

Тогда как попытка записать данные на старом мастере, который теперь стал слейвом, приведёт к ошибке:

root@redis-0:/home/admin# redis-cli -a foobared set test4 'test4'
(error) READONLY You can't write against a read only slave.

Либо убиваем его вообще — и посмотрим, что сделает Sentinel:

root@redis-0:/home/admin# redis-cli -a foobared DEBUG SEGFAULT
Error: Server closed the connection

Лог:

...
10447:X 29 Mar 14:26:21.897 * +reboot slave 35.158.154.25:6379 35.158.154.25 6379 @ redis-test 3.121.223.95 6379

Мастер просто перезапущен.

Команды Sentinel

Command Description
sentinel masters список всех мастеров и их состояния
sentinel master состояние конкретного мастера
sentinel slaves список всех слейвов и их состояния
sentinel sentinels список всех Sentinel инстансов и их состояния
sentinel failover вручную запустить процесс восстановление и замены вышедшего из строя мастера
sentinel flushconfig Force Sentinel to rewrite it’s configuration on disk
sentinel monitor добавить новый мастер
sentinel remove удалить мастер из-под наблюдения

Ссылки по теме