В уроке про продакшен мы бегло посмотрели на реплику и Sentinel. Сегодня разбираем их как следует. Как реплика догоняет основной сервер после обрыва связи? Что успеет потеряться, если основной сервер упадёт? И как Sentinel решает, что сервер умер, и кого назначить вместо него? Всё проверим на настоящих контейнерах.
Бухгалтер и филиал
В главной конторе бухгалтер ведёт журнал операций. В филиале сидит помощник и по телефону переписывает каждую операцию в свою копию журнала. Бухгалтер не ждёт, пока помощник допишет строку: продиктовал и пошёл дальше.
Иногда связь рвётся. Чтобы не диктовать потом весь журнал заново, бухгалтер держит у телефона листок с последними операциями. Помощник перезванивает и говорит: «У меня всё до строки 429». Бухгалтер зачитывает с листка только недостающее. А если помощник пропадал так долго, что нужные строки с листка уже стёрты, остаётся одно: отправить ему копию всего журнала.
Это и есть репликация в Redis. Основной сервер (master) пересылает каждую команду записи репликам, не дожидаясь их. Листок у телефона называется буфером репликации, а номер строки смещением.
Три контейнера
Поднимаем основной сервер и две реплики на одной сети Docker. Реплике достаточно флага --replicaof:
docker network create shop-net
docker run -d --name redis-main --network shop-net redis:7
docker run -d --name redis-r1 --network shop-net redis:7 \
redis-server --replicaof redis-main 6379 --replica-announce-ip redis-r1
docker run -d --name redis-r2 --network shop-net redis:7 \
redis-server --replicaof redis-main 6379 --replica-announce-ip redis-r2
Флаг --replica-announce-ip просит реплику называться своим именем в сети, а не IP-адресом: так логи и ответы читать проще. Реплику можно назначить и на ходу командой REPLICAOF redis-main 6379, а REPLICAOF NO ONE превращает её обратно в самостоятельный сервер.
Новая реплика ничего не знает, и в логе основного сервера (docker logs redis-main, дата и время в начале строк убраны) видно полную синхронизацию: снимок всех данных уходит реплике целиком.
* Replica redis-r1:6379 asks for synchronization
* Full resync requested by replica redis-r1:6379
* Streamed RDB transfer with replica redis-r1:6379 succeeded (socket). Waiting for REPLCONF ACK from replica to enable streaming
* Synchronization with replica redis-r1:6379 succeeded
Снимок тот же RDB из урока про персистентность, только Redis 7 пишет его не на диск, а прямо в сокет реплики. Потом основной сервер пересылает каждую новую запись. Смотрим, как это выглядит изнутри:
127.0.0.1:6379> INFO replication
# Replication
role:master
connected_slaves:2
slave0:ip=redis-r1,port=6379,state=online,offset=346,lag=0
slave1:ip=redis-r2,port=6379,state=online,offset=346,lag=0
master_failover_state:no-failover
master_replid:5e886501aff58c3b40eba01cb5dc17b645217f86
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:346
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:346
У основного сервера смещение 346, и у обеих реплик тоже 346: они получили всё. repl_backlog_size:1048576 это размер листка у телефона, 1 МБ. master_replid это идентификатор истории записей: по нему реплика понимает, что догоняет тот же журнал, а не чужой.
Реплика отдаёт данные на чтение, а писать в неё нельзя:
127.0.0.1:6379> GET book:1:title
"Master and Margarita"
127.0.0.1:6379> SET book:1:title x
(error) READONLY You can't write against a read only replica.
Реплика вернулась
Остановим реплику redis-r1 и, пока её нет, запишем в основной сервер три команды. Счётчик просмотров до этого уже был равен 1:
127.0.0.1:6379> SET book:4:title Dune
OK
127.0.0.1:6379> INCR book:1:views
(integer) 2
127.0.0.1:6379> INCR book:1:views
(integer) 3
Запускаем реплику снова. В логе основного сервера:
* Replica redis-r1:6379 asks for synchronization
* Partial resynchronization request from redis-r1:6379 accepted. Sending 108 bytes of backlog starting from offset 429.
Это частичная синхронизация. Реплика сказала: «у меня всё до смещения 429», и основной сервер дослал из буфера 108 байт, ровно три пропущенные команды. Полный снимок не понадобился. А если бы реплика отсутствовала так долго, что в 1 МБ буфера уже не осталось нужного смещения, случилась бы полная синхронизация. Поэтому на нагруженном сервере буфер увеличивают параметром repl-backlog-size: чем больше записей в секунду, тем быстрее он переполняется.
урок из курса
Урок из курса «Redis с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.
Оплата пока не подключена: премиум и курсы открываются промокодом.