- Praktický úvod do Redis (1): vaše distribuovaná NoSQL cache
- Praktický úvod do Redis (2): transakce
- Praktický úvod do Redis (3): cluster
V závěrečné části seriálu o NoSQL in-memory databázi Redis si ukážeme, jak funguje clustering. Minule jsme rozebrali transakční zpracovaní a v první části jsme si Redis nainstalovali a naučili se používat.
Cluster
Redis cluster podporuje dvě základní operace – sharding a replikaci.
Sharding je o tom, že data rozprostřete přes víc nodů – některé klíče budou na prvním, jiné na druhém a tak dál. Jakým způsobem se Redis rozhodne na který uzel klíč uložit? Celý systém funguje na principu hash operace. Ta vezme klíč a dojde k nějakému výsledku (velké číslo), které se transformuje na jakousi adresu, ve které je uložena hodnota. Když chcete ke klíči přistoupit, provede se stejná operace a tím se zjistí adresa hodnoty – takže nedochází k žádnému sekvenčnímu vyhledávání, víme hned, kde je hodnota uložena. Pokud máte víc nodů, Redis si možné výsledky hash operace rozdělí na několik rozsahů (konkrétně 16k). Tak například první slot jsou hash čísla od 0 do 7, další od 8 do 15 a tak podobně (samozřejmě tohle je jen příklad, reálně jsou čísla o dost větší). Na základě toho po provedení hash operace Redis ví, do které výseče výsledek patří a uvnitř výseče pak jde přímo na konkrétní adresu. Sharding mezi nody funguje tak, že každý uzel si vezme na starost několik výsečí. Pokud máte třeba 3 nody, tak každý je odpovědný za držení třetiny slotů. Tato informace je všem uzlům známa, takže z výsledku hash je možné říci, na kterém nodu se nachází výsledek. Jak hledání výsledné adresy tak nalezení nodu je tak extrémně rychlé – nic se nevyhledává sekvenčně.
Druhá funkce spočívá v replikaci typu master-slave. Můžete například říct, že jeden z uzlů je master, a ten funguje pro čtení i zápis. Jakmile něco zapíšete, tak master node provede na pozadí replikaci na všechny slave nody (tzn. Redis nemá silnou konzistenci, aplikace nečeká na výsledek replikace – uloží data do master a ten teprve zpětně replikuje, takže není 100% garance, že to v případě výpadku masteru stihne).
Obě funkce můžete samozřejmě kombinovat a mít třeba 5 masterů a každý může mít k sobě 2 slave nody pro redunanci (záloha pro případ výpadku mastera) a výkon (lze z nich číst a využít, že jsou třeba blíž).
Jak nastavit cluster pořádně? Pročtěte si oficiální dokumentaci, je dobře pochopitelná. Pro naše hraní poslouží skript, který je součástí zdrojáků – ten dokáže na jednom serveru (nebo VM) spustit vícero instancí Redis, takže si můžete cluster snadno vyzkoušet.
Skript používá Ruby, které nejprve nainstalujeme:
sudo apt-get install ruby sudo gem install redis
Skript je ve výchozím stavu nastaven na 6 nodů s 1 replikou – udělá tedy 3 mastery a 3 slave, což se pro náš účel docela hodí a nemusíme to měnit. Nejprve skriptem nastartujte procesy:
cloudsvet@ubuntu:~$ cd redis-stable/utils/create-cluster/ cloudsvet@ubuntu:~/redis-stable/utils/create-cluster$ ./create-cluster start Starting 30001 Starting 30002 Starting 30003 Starting 30004 Starting 30005 Starting 30006
Necháme skript nastavit nody:
cloudsvet@ubuntu:~/redis-stable/utils/create-cluster$ ./create-cluster create >>> Creating cluster Connecting to node 127.0.0.1:30001: OK Connecting to node 127.0.0.1:30002: OK Connecting to node 127.0.0.1:30003: OK Connecting to node 127.0.0.1:30004: OK Connecting to node 127.0.0.1:30005: OK Connecting to node 127.0.0.1:30006: OK >>> Performing hash slots allocation on 6 nodes... Using 3 masters: 127.0.0.1:30001 127.0.0.1:30002 127.0.0.1:30003 Adding replica 127.0.0.1:30004 to 127.0.0.1:30001 Adding replica 127.0.0.1:30005 to 127.0.0.1:30002 Adding replica 127.0.0.1:30006 to 127.0.0.1:30003 M: e774ac4c6b8929a5dfd924a4865c7b6f1d909381 127.0.0.1:30001 slots:0-5460 (5461 slots) master M: aae878886205eae6b85e86407b90095fd68ccd36 127.0.0.1:30002 slots:5461-10922 (5462 slots) master M: 4d0df598ed3e48cdd1d8ea85e9789e27ad6b72b1 127.0.0.1:30003 slots:10923-16383 (5461 slots) master S: e1f64f60ed5cd9535764b36076a22f1e315350f2 127.0.0.1:30004 replicates e774ac4c6b8929a5dfd924a4865c7b6f1d909381 S: af07e977bf6609875355e9b6c5b48a256a964e45 127.0.0.1:30005 replicates aae878886205eae6b85e86407b90095fd68ccd36 S: 6a1dd9d09280f3302804b6dc4c97077d426531cf 127.0.0.1:30006 replicates 4d0df598ed3e48cdd1d8ea85e9789e27ad6b72b1 Can I set the above configuration? (type 'yes' to accept): yes >>> Nodes configuration updated >>> Assign a different config epoch to each node >>> Sending CLUSTER MEET messages to join the cluster Waiting for the cluster to join. >>> Performing Cluster Check (using node 127.0.0.1:30001) M: e774ac4c6b8929a5dfd924a4865c7b6f1d909381 127.0.0.1:30001 slots:0-5460 (5461 slots) master M: aae878886205eae6b85e86407b90095fd68ccd36 127.0.0.1:30002 slots:5461-10922 (5462 slots) master M: 4d0df598ed3e48cdd1d8ea85e9789e27ad6b72b1 127.0.0.1:30003 slots:10923-16383 (5461 slots) master M: e1f64f60ed5cd9535764b36076a22f1e315350f2 127.0.0.1:30004 slots: (0 slots) master replicates e774ac4c6b8929a5dfd924a4865c7b6f1d909381 M: af07e977bf6609875355e9b6c5b48a256a964e45 127.0.0.1:30005 slots: (0 slots) master replicates aae878886205eae6b85e86407b90095fd68ccd36 M: 6a1dd9d09280f3302804b6dc4c97077d426531cf 127.0.0.1:30006 slots: (0 slots) master replicates 4d0df598ed3e48cdd1d8ea85e9789e27ad6b72b1 [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. cloudsvet@ubuntu:~/redis-stable/utils/create-cluster$
Jak vidíte, máme 3 mastery a 3 slave, což můžeme ověřit i z CLI
cloudsvet@ubuntu:~$ redis-cli -p 30001 127.0.0.1:30001> cluster nodes e774ac4c6b8929a5dfd924a4865c7b6f1d909381 127.0.0.1:30001 myself,master - 0 0 1 connected 0-5460 6a1dd9d09280f3302804b6dc4c97077d426531cf 127.0.0.1:30006 slave 4d0df598ed3e48cdd1d8ea85e9789e27ad6b72b1 0 1440666538266 6 connected e1f64f60ed5cd9535764b36076a22f1e315350f2 127.0.0.1:30004 slave e774ac4c6b8929a5dfd924a4865c7b6f1d909381 0 1440666538266 4 connected aae878886205eae6b85e86407b90095fd68ccd36 127.0.0.1:30002 master - 0 1440666538266 2 connected 5461-10922 4d0df598ed3e48cdd1d8ea85e9789e27ad6b72b1 127.0.0.1:30003 master - 0 1440666538266 3 connected 10923-16383 af07e977bf6609875355e9b6c5b48a256a964e45 127.0.0.1:30005 slave aae878886205eae6b85e86407b90095fd68ccd36 0 1440666538266 5 connected
Připojme se do CLI na první z nodů (identifikací portu) a zkusme vytvořit pár záznamů – hází to nějaké divné věci, že?
cloudsvet@ubuntu:~$ redis-cli -p 30001 127.0.0.1:30001> set k1 v1 (error) MOVED 12706 127.0.0.1:30003 127.0.0.1:30001> set k2 v2 OK 127.0.0.1:30001> set k3 v3 OK 127.0.0.1:30001> set k4 v4 (error) MOVED 8455 127.0.0.1:30002 127.0.0.1:30001> set k5 v5 (error) MOVED 12582 127.0.0.1:30003 127.0.0.1:30001>
Jak už jsem psal, Redis udělá nad klíčem hash a tím zjistí, do jakého patří hash slotu. Pokud ten je na lokálním nodu, tak příkaz provede (to je případ k2 a k3). Pokud ale tento hash slot není na lokálním uzlu, příkaz vrátí chybu a instrukce co dál – například k1 patřící do hash slotu 12706 máme zapsat do 127.0.0.1:30003, tedy do třetího master uzlu. V CLI si tohle tedy musíme vyřešit sami a připojovat se do správných uzlů. Aplikace by tedy měla MOVED odpověď chápat a opakovat akci na jiném uzlu a navíc pokud něco uloží, je ideální, když si zapamatuje, který node to byl (protože je velmi pravděpodobné, že tam to bude – resp. jisté, pokud node neumřel nebo se nezměnil počet master nodů a tím rozložení hash výsečí).
Naštěstí třeba do Python existuje knihovna, které se o vše postará za nás. Nainstalujte redis-py-cluster:
sudo pip install redis-py-cluster
Vytvořme si velmi jednoduchou aplikace v Python. Nejprve se připojíme ke clusteru našich nodů a následně nastavíme pět klíčů – nemusíme řešit kam je psát, o to se postará knihovna a totéž platí pro čtení.
from rediscluster import StrictRedisCluster startup_nodes = [ {"host": "127.0.0.1", "port": "30001"}, {"host": "127.0.0.1", "port": "30002"}, {"host": "127.0.0.1", "port": "30003"}, {"host": "127.0.0.1", "port": "30004"}, {"host": "127.0.0.1", "port": "30005"}, {"host": "127.0.0.1", "port": "30006"}] mujcluster = StrictRedisCluster(startup_nodes=startup_nodes, decode_responses=True) mujcluster.set("mujklic1", "mojehodnota1") mujcluster.set("mujklic2", "mojehodnota2") mujcluster.set("mujklic3", "mojehodnota3") mujcluster.set("mujklic4", "mojehodnota4") mujcluster.set("mujklic5", "mojehodnota5") print mujcluster.get("mujklic1") print mujcluster.get("mujklic2") print mujcluster.get("mujklic3") print mujcluster.get("mujklic4") print mujcluster.get("mujklic5")
Takhle to pak vypadá:
cloudsvet@ubuntu:~$ python redisclusterdemo.py mojehodnota1 mojehodnota2 mojehodnota3 mojehodnota4 mojehodnota5
Podívejme se teď příkazovou řádkou, jak to dopadlo, tedy co kde je. Prozkoumejte nejen master nody (30001, 30002, 30003), ale i slave (z těch můžete číst také):
cloudsvet@ubuntu:~$ redis-cli -p 30001 127.0.0.1:30001> keys * 1) "mujklic2" 2) "mujklic3" 127.0.0.1:30001> get mujklic2 "mojehodnota2" 127.0.0.1:30001> quit cloudsvet@ubuntu:~$ redis-cli -p 30002 127.0.0.1:30002> keys * 1) "mujklic5" 2) "mujklic1" 127.0.0.1:30002> quit cloudsvet@ubuntu:~$ redis-cli -p 30003 127.0.0.1:30003> keys * 1) "mujklic4" 127.0.0.1:30003> quit cloudsvet@ubuntu:~$ redis-cli -p 30004 127.0.0.1:30004> keys * 1) "mujklic2" 2) "mujklic3" 127.0.0.1:30004> quit cloudsvet@ubuntu:~$ redis-cli -p 30005 127.0.0.1:30005> keys * 1) "mujklic5" 2) "mujklic1" 127.0.0.1:30005> quit cloudsvet@ubuntu:~$ redis-cli -p 30006 127.0.0.1:30006> keys * 1) "mujklic4" 127.0.0.1:30006> quit
Jako poslední krok si teď vyzkoušíme redundanci a zabijeme jednoho z masterů. Jaké máme aktuální rozložení nodů co do master/slave?
cloudsvet@ubuntu:~$ redis-cli -p 30001 cluster nodes e774ac4c6b8929a5dfd924a4865c7b6f1d909381 127.0.0.1:30001 myself,master - 0 0 1 connected 0-5460 6a1dd9d09280f3302804b6dc4c97077d426531cf 127.0.0.1:30006 slave 4d0df598ed3e48cdd1d8ea85e9789e27ad6b72b1 0 1440667480102 6 connected e1f64f60ed5cd9535764b36076a22f1e315350f2 127.0.0.1:30004 slave e774ac4c6b8929a5dfd924a4865c7b6f1d909381 0 1440667480101 4 connected aae878886205eae6b85e86407b90095fd68ccd36 127.0.0.1:30002 master - 0 1440667480102 2 connected 5461-10922 4d0df598ed3e48cdd1d8ea85e9789e27ad6b72b1 127.0.0.1:30003 master - 0 1440667480101 3 connected 10923-16383 af07e977bf6609875355e9b6c5b48a256a964e45 127.0.0.1:30005 slave aae878886205eae6b85e86407b90095fd68ccd36 0 1440667480102 5 connected
V našem případě nejsou nody celé VM nebo kontejnery, ale jen běžící procesy – najděme tedy jejich čísla:
cloudsvet@ubuntu:~$ ps aux | grep redis cloudsvet 1382 0.2 0.7 38104 7872 pts/1 Sl Aug26 0:34 redis-server *:6379 cloudsvet 1531 0.2 0.7 38100 7456 ? Ssl Aug26 0:23 ../../src/redis-server *:30001 [cluster] cloudsvet 1533 0.2 0.9 38100 9296 ? Ssl Aug26 0:23 ../../src/redis-server *:30002 [cluster] cloudsvet 1535 0.2 0.7 38100 7468 ? Ssl Aug26 0:23 ../../src/redis-server *:30003 [cluster] cloudsvet 1541 0.2 0.7 38100 7632 ? Ssl Aug26 0:23 ../../src/redis-server *:30004 [cluster] cloudsvet 1545 0.4 0.7 38100 7624 ? Ssl Aug26 0:37 ../../src/redis-server *:30005 [cluster] cloudsvet 1549 0.3 0.7 38100 7612 ? Ssl Aug26 0:36 ../../src/redis-server *:30006 [cluster] cloudsvet 2057 0.0 0.0 11744 940 pts/1 R+ 02:24 0:00 grep --color=auto redis
Pojďme jeden z masterů sestřelit:
cloudsvet@ubuntu:~$ sudo kill 1533
No a jak to dopadlo? Měli bychom vidět, že jeden z masterů umřel a jeho slave se stal masterem:
cloudsvet@ubuntu:~$ redis-cli -p 30001 cluster nodes e774ac4c6b8929a5dfd924a4865c7b6f1d909381 127.0.0.1:30001 myself,master - 0 0 1 connected 0-5460 6a1dd9d09280f3302804b6dc4c97077d426531cf 127.0.0.1:30006 slave 4d0df598ed3e48cdd1d8ea85e9789e27ad6b72b1 0 1440667500285 6 connected e1f64f60ed5cd9535764b36076a22f1e315350f2 127.0.0.1:30004 slave e774ac4c6b8929a5dfd924a4865c7b6f1d909381 0 1440667500285 4 connected aae878886205eae6b85e86407b90095fd68ccd36 127.0.0.1:30002 master,fail - 1440667493416 1440667493217 2 disconnected 4d0df598ed3e48cdd1d8ea85e9789e27ad6b72b1 127.0.0.1:30003 master - 0 1440667500284 3 connected 10923-16383 af07e977bf6609875355e9b6c5b48a256a964e45 127.0.0.1:30005 master - 0 1440667500284 7 connected 5461-10922