Репликация и Sentinel: высокая доступность

Один Redis — одна точка отказа: процесс упал, и всё приложение осталось без кэша и сессий. Репликация держит копии данных, а Sentinel автоматически переключает приложение на живой узел.

Репликация — это держать одну или несколько точных копий данных на других серверах. Один узел — primary (master), он принимает записи; остальные — replica, они асинхронно копируют данные и могут заменить мастер при сбое.

Зачем это на практике

В проде одиночный Redis опасен по двум причинам. Во-первых, отказоустойчивость: если процесс или хост упадёт, приложение мгновенно теряет кэш, очереди и сессии пользователей. Во-вторых, масштаб чтения: тяжёлые операции чтения можно разнести по нескольким репликам и разгрузить мастер. Базовый рецепт высокой доступности в Redis — один мастер + несколько реплик + Sentinel, который следит за здоровьем и автоматически продвигает реплику в мастер, если основной узел перестал отвечать.

Важно сразу различать два инструмента. Репликация сама по себе только копирует данные — она не делает переключение автоматически. Sentinel — это уже система мониторинга и автопереключения поверх репликации. Без Sentinel при падении мастера кто-то (человек или скрипт) должен вручную повысить реплику.

Как настроить репликацию

Реплику привязывают к мастеру одной командой или строкой конфигурации. Реплика подключается, скачивает снимок данных и дальше получает поток изменений:

# на узле-реплике: сделать его репликой мастера 10.0.0.1:6379
redis-cli REPLICAOF 10.0.0.1 6379

# отвязать реплику и снова сделать самостоятельным мастером
redis-cli REPLICAOF NO ONE

# проверить роль и состояние репликации
redis-cli INFO replication

То же можно задать в redis.conf, чтобы узел стартовал уже как реплика:

# redis.conf на реплике
replicaof 10.0.0.1 6379
masterauth StrongPasswordHere    # если у мастера включён requirepass
replica-read-only yes            # реплика отвергает записи (по умолчанию yes)

Вывод INFO replication на мастере покажет число подключённых реплик и их смещение в потоке репликации:

Вывод:

role:master
connected_slaves:2
slave0:ip=10.0.0.2,port=6379,state=online,offset=15240,lag=0
slave1:ip=10.0.0.3,port=6379,state=online,offset=15240,lag=1
master_repl_offset:15240

Асинхронная репликация и её последствия

Ключевой факт: репликация в Redis асинхронная. Мастер отвечает клиенту «OK» сразу, не дожидаясь, пока реплики получат запись. Это даёт высокую скорость записи, но означает, что при внезапном падении мастера несколько последних команд, ещё не дошедших до реплик, будут потеряны.

Смягчить это помогает директива min-replicas-to-write: мастер откажется принимать записи, если к нему подключено меньше заданного числа достаточно «свежих» реплик. Это не делает репликацию синхронной, но защищает от приёма данных в момент, когда их некому продублировать:

# принимать записи, только если есть хотя бы 1 реплика
# с задержкой не больше 10 секунд
min-replicas-to-write 1
min-replicas-max-lag 10

Sentinel: автоматический failover

Sentinel — отдельный процесс Redis, запущенный в специальном режиме. Он непрерывно опрашивает мастер и реплики, и если мастер перестал отвечать, проводит failover: выбирает лучшую реплику, делает её новым мастером и переключает на неё остальные реплики.

Sentinel запускают нечётным числом экземпляров (обычно три) на разных хостах. Нечётное число нужно для кворума: чтобы признать мастер «упавшим» и начать переключение, требуется согласие большинства Sentinel. Это защищает от ложного срабатывания, когда один Sentinel просто потерял связь по сети.

# sentinel.conf — следим за мастером по имени mymaster
# 2 — кворум: столько Sentinel должны согласиться, что мастер упал
sentinel monitor mymaster 10.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000   # 5 сек без ответа -> подозрение
sentinel failover-timeout mymaster 60000
sentinel auth-pass mymaster StrongPasswordHere
# запустить процесс в режиме Sentinel
redis-sentinel /etc/redis/sentinel.conf

# спросить у Sentinel, кто сейчас мастер для mymaster
redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster

Как клиент находит нового мастера

Это центральный вопрос: после failover адрес мастера изменился — как приложение об этом узнает? Решение в том, что клиент подключается не к мастеру напрямую, а к Sentinel. Современная клиентская библиотека (Sentinel-aware) при старте спрашивает у любого Sentinel: «кто сейчас мастер для mymaster?» — командой SENTINEL get-master-addr-by-name — и подключается уже по полученному адресу.

Более того, клиент подписывается на канал событий Sentinel (+switch-master). Когда происходит переключение, Sentinel публикует событие с новым адресом, и клиент автоматически переподключается к свежему мастеру. Приложению не нужно знать IP заранее — оно знает только список Sentinel.

Как это работает под капотом

При подключении реплики идёт синхронизация. Сначала пробуется частичная (partial resync): у мастера есть кольцевой replication backlog — буфер последних команд, а у потока репликации есть идентификатор (replid) и смещение (offset). Если реплика отвалилась ненадолго и нужные данные ещё в буфере, мастер досылает только пропущенные команды. Если разрыв был долгим — выполняется полная синхронизация: мастер делает RDB-снимок, передаёт его реплике, а затем продолжает поток команд.

Sentinel внутри различает два уровня «падения». SDOWN (subjectively down) — данный конкретный Sentinel не дождался ответа от мастера за down-after-milliseconds. ODOWN (objectively down) — в падении согласилось большинство Sentinel (набрался кворум). Только после ODOWN запускается failover. Сами Sentinel выбирают между собой лидера (по алгоритму, близкому к Raft), и именно лидер проводит переключение: выбирает реплику с наибольшим offset (самые свежие данные), отдаёт ей REPLICAOF NO ONE и перенаправляет остальные реплики на неё.

Частые ошибки

  • Ждать от асинхронной репликации нулевой потери данных. Мастер подтверждает запись до её доставки на реплики; при падении последние команды теряются. Для критичных данных используйте min-replicas-to-write и осознавайте остаточный риск.
  • Запускать чётное число Sentinel или всего один. Один Sentinel не даёт кворума и сам становится точкой отказа; чётное число рискует не собрать большинство. Держите три (или пять) на разных хостах.
  • Подключаться к мастеру по фиксированному IP в обход Sentinel. После failover этот IP — уже бывший мастер. Клиент обязан спрашивать адрес у Sentinel и слушать события переключения.
  • Разрешать запись на реплику. Случайно отключённый replica-read-only приводит к расхождению данных: запись на реплику не уходит на мастер и затирается при следующей синхронизации.
  • Считать, что репликация заменяет бэкап. Реплика честно копирует и ошибочный DEL, и FLUSHALL. Репликация — про доступность, а не про защиту от «удалили не то».

Итоги

  • Репликация держит копии данных: primary принимает записи, replica копируют их и готовы заменить мастер.
  • Репликация асинхронная — быстро, но при сбое мастера возможна потеря последних незареплицированных записей; смягчается min-replicas-to-write.
  • Sentinel добавляет автоматику: мониторит узлы и при падении мастера проводит failover, продвигая лучшую реплику.
  • Sentinel запускают нечётным числом (3+) ради кворума; различают SDOWN (мнение одного) и ODOWN (согласие большинства).
  • Клиент находит нового мастера через Sentinel: спрашивает адрес и слушает событие +switch-master, а не хранит IP мастера жёстко.
Проверьте себя
1. Почему при асинхронной репликации Redis возможна потеря данных, если мастер внезапно упадёт?
AРеплики удаляют данные старше минуты
BМастер отвечает клиенту OK сразу, не дожидаясь доставки записи на реплики, поэтому последние команды могут не успеть реплицироваться
CRedis вообще не реплицирует записи, только чтения
DРеплики хранят данные только в RDB и теряют их при перезапуске
2. Как Sentinel-aware клиент узнаёт адрес нового мастера после failover?
AПеребирает IP-адреса подряд, пока не найдёт отвечающий узел
BХранит фиксированный IP мастера в конфиге и не меняет его
CСпрашивает у Sentinel (get-master-addr-by-name) и слушает событие +switch-master, после чего переподключается
DУзнаёт адрес из RDB-файла на диске
3. Зачем Sentinel запускают нечётным числом экземпляров (например, три)?
AЧтобы ускорить репликацию данных
BЧтобы для признания мастера упавшим и запуска failover собиралось большинство (кворум) и не срабатывал один потерявший связь Sentinel
CЭто требование лицензии Redis
DЧтобы каждый Sentinel хранил треть данных