2022-03-16 / Bartłomiej Kurek
Redis: replikacja

W części poprzedniej przyjrzeliśmy się podstawom partycjonowania danych w klastrze Redis.
W tym artykule zajmiemy się podstawowym scenariuszem replikacji.
Oczywiście należy mieć na uwadze wiele niuansów, które tutaj zostaną pominięte. Wśród nich są jednak istotne kwestie bezpieczeństwa i integralności danych opisane w dokumentacji, a m.in.:
- asynchroniczna natura replikacji (strumieniowanie, opóźnienia)
- persystencja danych
- autentykacja
- replikacja TTL (wygasanie kluczy)

Zdecydowanie zachęcam do zapoznania się z dokumentacją.
Tutaj zajmiemy się jedynie replikacją w zakresie podstawowym - bez klastra oraz Sentinel, które zapewniają wysoką dostępność (high availablity), czy automatyczny failover.

Konfiguracja master oraz replik

Zrealizujemy scenariusz z jedną instancją master oraz dwiema replikami. W takim scenariuszu instancje replik Redis są domyślnie w trybie read-only. Zapisy wykonujemy na instancji master, a odczytów możemy dokonywać na wszystkich instancjach. Należy jednak mieć na uwadze ewentualne opóźnienie.

$ tree configs/
configs/
├── master.conf
├── replica-1.conf
└── replica-2.conf

0 directories, 3 files

Plik configs/master.conf:

daemonize yes
bind 0.0.0.0
port 16001
pidfile ./run/master-A.pid

Plik configs/replica-1.conf:

daemonize yes
bind 0.0.0.0
port 17001
pidfile ./run/replica-1.pid
replicaof 127.0.0.1 16001

Plik configs/replica-2.conf:

daemonize yes
bind 0.0.0.0
port 17002
pidfile ./run/replica-2.pid
replicaof 127.0.0.1 16001

Uruchamiamy instancje

Start:

$ redis-server configs/master.conf 
$ redis-server configs/replica-1.conf 
$ redis-server configs/replica-2.conf 

Wyświetlamy stan replikacji dla instancji master:

$ redis-cli -p 16001 info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=17001,state=online,offset=210,lag=0
slave1:ip=127.0.0.1,port=17002,state=online,offset=210,lag=0
master_replid:dc6e272c994fbebae0d7019f8f655131eecb0975
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:210
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:210

Możemy również sprawdzić stan instancji w roli 'slave':

$ redis-cli -p 17001 info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:16001
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_repl_offset:252
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:dc6e272c994fbebae0d7019f8f655131eecb0975
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:252
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:252

Sprawdzamy zapis i odczyt

$ redis-cli -p 16001 set x 987
OK

$ redis-cli -p 17001 get x
"987"

$ redis-cli -p 17002 get x
"987"

REPLICAOF

Komendą REPLICAOF możemy zarówno wyłączyć replikację, jak i włączyć replikację z danej instancji.
Przykładowo: wyłączamy replikację na instancji replica-2 (port 17002).

$ redis-cli -p 17002 
127.0.0.1:17002> REPLICAOF no one
OK

W tym momencie nasza instancja master posiada tylko jedną replikę:

$ redis-cli -p 16001 info replication | head -n 3
# Replication
role:master
connected_slaves:1

Jeśli sprawdzimy stan replikacji dla replica-2 - zauważymy, że serwer działa w trybie master i nie ma żadnych replik.

$ redis-cli -p 17002 info replication | head -n 3
# Replication
role:master
connected_slaves:0

Dane zapisywane na master nie będą replikowane na replica-2:

$ redis-cli -p 16001 set foo bar
OK
$ redis-cli -p 17001 get foo
"bar"
$ redis-cli -p 17002 get foo
(nil)

Replikacja kaskadowa

Replikacja może odbywać się z dowolnej instancji, uzyskamy wtedy replikację kaskadową (master -> replica -> replica):

$ redis-cli -p 17002 
127.0.0.1:17002> REPLICAOF 127.0.0.1 17001
OK

Zapis i odczyt:

$ redis-cli -p 16001 set hello world
OK
$ redis-cli -p 17001 get hello
"world"
$ redis-cli -p 17002 get hello
"world"

Jeśli powrócimy do replikacji instancji master, na replice pojawią się również dane zapisane wcześniej pod kluczem 'foo', które nie były replikowane.

127.0.0.1:17002> REPLICAOF 127.0.0.1 16001
OK
127.0.0.1:17002> 
$ redis-cli -p 17002 get foo
"bar"

Replikacja w Redis zapamiętuje offset ("punkt w historii danych"). Serwer replikujący synchronizuje się do punktu w czasie odpowiadającego instancji master.

Promocja instancji do roli master

W przypadku kiedy nie używamy trybu klastra, ani Redis Sentinel, promocję instancji do roli master możemy wykonać ręcznie przy użyciu komendy REPLICAOF.
W tym scenariuszu wyłączymy instancję master, przełączymy replica-2 do roli master, a instancję replica-1 przekonfigurujemy na replikę nowego mastera.

$ cat run/master-A.pid | xargs kill
$ redis-cli -p 17002 -c replicaof no one
OK
$ redis-cli -p 17001 -c replicaof 127.0.0.1 17002
OK

Po tym zabiegu instancja na porcie 17002 działa w roli master, a serwer na porcie 17001 replikuje z niej dane.

(instancja 17002, nowy master):

$ redis-cli -p 17002 info replication | head -5
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=17001,state=online,offset=2526,lag=0
master_replid:2c93f3fe047448399e470dc08153ca50ea3dfd3c

Replika z nowego mastera:

$ redis-cli -p 17001 info replication | head -5
# Replication
role:slave
master_host:127.0.0.1
master_port:17002
master_link_status:up

Podsumowanie

Przybliżyliśmy tutaj jedynie podstawy replikacji w Redis, które umożliwiają wstępne zapoznanie się z samym mechanizmem i komendami. W przyszłych częściach tej serii przyjrzymy się bardziej zaawansowanym scenariuszom oraz rozwiązaniom zapewniającym pewien stopień automatyzacji promocji repliki do instancji master.