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

Автор: | 29/03/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:

[simterm]

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

[/simterm]

Редактируем /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

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

[simterm]

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

[/simterm]

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

[simterm]

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

[/simterm]

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

[simterm]

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

[/simterm]

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

[simterm]

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

[/simterm]

Получаем их:

[simterm]

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

[/simterm]

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

Настройка Redis Slave

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

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

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

[simterm]

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

[/simterm]

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

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

Тут:

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

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

[simterm]

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

[/simterm]

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

[simterm]

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
...

[/simterm]

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

[simterm]

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

[/simterm]

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

[simterm]

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

[/simterm]

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

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

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

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

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

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

[simterm]

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

[/simterm]

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

[simterm]

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

[/simterm]

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

[simterm]

127.0.0.1:6379> auth foobared
OK

[/simterm]

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

[simterm]

127.0.0.1:6379> slaveof no one
OK

[/simterm]

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

[simterm]

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

[/simterm]

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

[simterm]

127.0.0.1:6379> set test2 'test2'
OK

[/simterm]

Проверяем:

[simterm]

127.0.0.1:6379> get test2
"test2"

[/simterm]

Учтите, что т.к. в файле настроек /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 – время одновременной сихронизации слейвов после переназначения роли мастера

Запускаем:

[simterm]

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

[/simterm]

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

[simterm]

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

[/simterm]

Тут:

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

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

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

[simterm]

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

[/simterm]

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

[simterm]

...
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
...

[/simterm]

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

[simterm]

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

[/simterm]

sentinels=3 – окей.

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

[simterm]

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

[/simterm]

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

Redis Sentinel Automatic Failover

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

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

[simterm]

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

[/simterm]

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

[simterm]

...
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
...

[/simterm]

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

[simterm]

...
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
...

[/simterm]

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

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

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

[simterm]

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

[/simterm]

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

[simterm]

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

[/simterm]

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

[simterm]

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

[/simterm]

Лог:

[simterm]

...
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

[/simterm]

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

Команды 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 удалить мастер из-под наблюдения

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