O przeprowadzce z Redis do Redis-cluster

O przeprowadzce z Redis do Redis-cluster

Przychodząc do produktu, który rozwija się od ponad dziesięciu lat, zupełnie nie dziwi fakt napotkania w nim przestarzałych technologii. Ale co, jeśli za sześć miesięcy będziesz musiał obsłużyć obciążenie dziesięć razy wyższe, a cena awarii wzrośnie setki razy? W takim przypadku potrzebny jest świetny inżynier Highload. Ale w obliczu braku odpowiedniego, rozwiązanie problemu powierzono mnie. W pierwszej części artykułu opowiem, jak przeprowadzaliśmy się z Redis na Redis-cluster, a w drugiej części podam porady, jak zacząć korzystać z klastra i na co zwrócić uwagę podczas eksploatacji.

Wybór technologii

Czy naprawdę jest tak źle oddzielny Redis (standalone redis) w konfiguracji 1 master i N slave'ów? Dlaczego nazywam to przestarzałą technologią?

Nie, Redis nie jest taki zły... Jednak są pewne niedociągnięcia, których nie można zignorować.

  • Po pierwsze, Redis nie obsługuje mechanizmów awaryjnego przywracania po awarii mastera. Aby rozwiązać ten problem, wykorzystaliśmy konfigurację z automatycznym przełączaniem VIP-ów na nowego mastera, zmianą roli jednego ze slave'ów i przełączaniem pozostałych. Ten mechanizm działał, ale nie można go było nazwać wiarygodnym rozwiązaniem. Po pierwsze, zdarzały się fałszywe alarmy, a po drugie, był jednorazowy i po jego uruchomieniu wymagał ręcznych działań do zresetowania.

  • Po drugie, posiadanie tylko jednego mastera prowadziło do problemu z shardowaniem. Musieliśmy tworzyć kilka niezależnych klastrów '1 master i N slave'ów', a następnie ręcznie rozkładać bazy na tych maszynach i mieć nadzieję, że jutro jedna z baz nie rozrośnie się na tyle, że będzie ją trzeba przenieść na oddzielny instans.

Jakie są opcje?

  • Najdroższym i najbardziej rozbudowanym rozwiązaniem jest Redis-Enterprise. To pudełkowe rozwiązanie z pełnym wsparciem technicznym. Mimo że wygląda idealnie pod względem technicznym, nie pasowało nam z powodów ideologicznych.
  • Redis-cluster. Z pudełka ma wsparcie dla awaryjnego przełączania mastera i shardowania. Interfejs praktycznie nie różni się od zwykłej wersji. Wygląda obiecująco, o pułapkach porozmawiamy dalej.
  • Tarantool, Memcache, Aerospike i inne. Wszystkie te narzędzia robią mniej więcej to samo. Ale każde z nich ma swoje wady. Postanowiliśmy nie wkładać wszystkich jajek do jednego koszyka. Memcache i Tarantool używamy do innych zadań, a zanim przejdę dalej, powiem, że w naszej praktyce mieliśmy z nimi więcej problemów.

Specyfika użycia

Przyjrzyjmy się, jakie zadania historycznie rozwiązywaliśmy przy użyciu Redis i jakie funkcjonalności wykorzystywaliśmy:

  • Cache przed zapytaniami do zdalnych serwisów takich jak 2GIS | Golang

    GET SET MGET MSET "SELECT DB"

  • Cache przed MYSQL | PHP

    GET SET MGET MSET SCAN "KEY BY PATTERN" "SELECT DB"

  • Główne magazyn dla usługi obsługi sesji i współrzędnych kierowców | Golang

    GET SET MGET MSET "SELECT DB" "ADD GEO KEY" "GET GEO KEY" SCAN

Jak widać, żadnej wyższej matematyki. W czym więc tkwi problem? Przyjrzyjmy się każdemu metodzie osobno.

Metoda
Opis
Cechy Redis-cluster
Rozwiązanie

GET SET
Zapisz/odczytaj klucz

MGET MSET
Zapisz/odczytaj kilka kluczy
Klucze będą rozłożone na różnych węzłach. Gotowe biblioteki potrafią wykonywać operacje typu Multi tylko w ramach jednego węzła
Zastąp MGET pipeline’em z N operacjami GET

SELECT DB
Wybierz bazę, z którą będziemy pracować
Nie obsługuje wielu baz danych
Trzymaj wszystko w jednej bazie. Dodaj do kluczy prefiksy

SCAN
Przejdź przez wszystkie klucze w bazie
Ponieważ mamy jedną bazę, przeszukiwanie wszystkich kluczy w klastrze jest zbyt kosztowne
Utrzymywać inwariant wewnątrz jednego klucza i robić HSCAN po tym kluczu. Lub zrezygnować całkowicie

GEO
Operacje związane z geokluczem
Geoklucz nie jest shardowany

KEY BY PATTERN
Wyszukiwanie klucza według wzorca
Ponieważ mamy jedną bazę, będziemy szukać wśród wszystkich kluczy w klastrze. Zbyt kosztowne
Zrezygnować lub utrzymywać inwariant, jak w przypadku SCAN-a

Redis vs Redis-cluster

Czego tracimy i co zyskujemy przy przejściu na klaster?

  • Wady: tracimy funkcjonalność kilku baz.
    • Jeśli chcemy przechowywać w jednym klastrze logicznie niespowiązane dane, będziemy musieli stworzyć rozwiązania w postaci prefiksów.
    • Tracimy wszystkie operacje „na bazie”, takie jak SCAN, DBSIZE, CLEAR DB itd.
    • Operacje wielokrotne stały się znacznie trudniejsze do realizacji, ponieważ może być wymagany dostęp do kilku węzłów.
  • Zalety:
    • Odporność na awarie w postaci awaryjnego przełączania lidera.
    • Shardowanie po stronie Redis.
    • Przenoszenie danych między węzłami atomowo i bez przestojów.
    • Dodawanie i redystrybucja mocy i obciążeń bez przestojów.

Stwierdziłbym, że jeśli nie musisz zapewniać wysokiego poziomu odporności na awarie, to przeprowadzka na klaster nie jest tego warta, ponieważ może być to zadanie niezbyt proste. Ale jeśli wybierasz między oddzielną wersją a klastrem, to warto wybrać klaster, ponieważ nie jest gorszy, a dodatkowo zdejmie z ciebie część bólu głowy.

Przygotowanie do przeprowadzki

Zacznijmy od wymagań dotyczących przeprowadzki:

  • Musimy zapewnić, że będzie bezszwowa. Całkowite zatrzymanie usługi na 5 minut nas nie satysfakcjonuje.
  • Musimy zapewnić, że będzie jak najbardziej bezpieczna i stopniowa. Chcielibyśmy mieć pewną kontrolę nad sytuacją. Nie zamierzamy wrzucać wszystkiego naraz i modlić się nad przyciskiem przywracania.
  • Minimalne straty danych podczas przeprowadzki. Rozumiemy, że przeprowadzka atomowa będzie bardzo trudna, więc dopuszczamy pewną niesynchronizację między danymi w zwykłym a klastrowym Redis.

Utrzymanie klastra

Przed samą przeprowadzką warto się zastanowić, czy możemy utrzymać klaster:

  • Wykresy. Używamy Prometheusa i Grafany do wizualizacji obciążenia procesorów, zajętej pamięci, liczby klientów, liczby operacji GET, SET, AUTH itd.
  • Ekspertyza. Wyobraź sobie, że jutro pod twoją odpowiedzialnością będzie ogromny klaster. Jeśli się zepsuje, nikt oprócz ciebie go nie naprawi. Jeśli zacznie zwalniać — wszyscy przyjdą do ciebie. Jeśli trzeba dodać zasoby lub zrównoważyć obciążenie — znów do ciebie. Aby nie posiwieć w wieku 25 lat, warto przewidzieć te przypadki i wcześniej sprawdzić, jak technologia zareaguje na różne działania. O tym porozmawiamy bardziej szczegółowo w sekcji „Ekspertyza”.
  • Monitoring i powiadomienia. Kiedy klaster się psuje, chcemy dowiedzieć się o tym jako pierwsi. Ograniczyliśmy się do powiadamiania, że wszystkie węzły zwracają tę samą informację o stanie klastra (tak, zdarza się i inaczej). A inne problemy szybciej zauważysz dzięki powiadomieniom z usług-klientów Redis.

Przeprowadzka

Jak będziemy przeprowadzać:

  • Po pierwsze, musimy przygotować bibliotekę do pracy z klastrem. Jako podstawę dla wersji w Go wzięliśmy go-redis i nieco go zmodyfikowaliśmy. Zrealizowaliśmy metody Multi za pomocą pipeline'ów, a także nieco poprawiliśmy zasady powtarzania zapytań. W wersji dla PHP napotkaliśmy więcej problemów, ale ostatecznie zdecydowaliśmy się na php-redis. Niedawno wprowadzili wsparcie dla klastra, które naszym zdaniem wygląda dobrze.
  • Następnie musimy uruchomić sam klaster. Robi się to dosłownie w dwóch poleceniach na podstawie pliku konfiguracyjnego. Szczegółowo omówimy konfigurację poniżej.
  • Do stopniowego przejścia używamy trybu dry-mode. Ponieważ mamy dwie wersje biblioteki o identycznym interfejsie (jedna dla wersji standardowej, druga dla klastra), nic nie stoi na przeszkodzie, by stworzyć wrapper, który będzie działał z osobną wersją i równocześnie duplikował wszystkie zapytania do klastra, porównywał odpowiedzi i zapisywał różnice w logach (w naszym przypadku w NewRelic). Dzięki temu, nawet jeśli podczas wprowadzania wersja klastrowa ulegnie awarii, nasza produkcja nie zostanie dotknięta.
  • Używając klastra w trybie dry-mode, możemy spokojnie obserwować wykres różnic w odpowiedziach. Jeśli udział błędów powoli, ale pewnie zmierza do jakiejś niewielkiej konstante, to znaczy, że wszystko jest w porządku. Dlaczego różnice nadal się pojawiają? Ponieważ zapis w osobnej wersji odbywa się nieco wcześniej, niż w klastrze, i przez mikrolag dane mogą się rozjeżdżać. Wystarczy spojrzeć na logi różnic, a jeśli wszystkie są wytłumaczalne brakiem atomowości zapisu, można iść dalej.
  • Teraz możemy przełączyć dry-mode w drugą stronę. Będziemy pisać i czytać z klastra, a duplikować do osobnej wersji. Po co? W ciągu następnego tygodnia chcemy obserwować pracę klastra. Jeśli okaże się, że w szczycie obciążenia występują problemy lub coś przeoczyliśmy, zawsze mamy awaryjny powrót do starego kodu i aktualnych danych dzięki dry-mode.
  • Pozostaje wyłączyć dry-mode i zdemontować osobną wersję.

Ekspertyza

Na początek krótko o budowie klastra.

Po pierwsze, Redis to przechowalnia key-value. Jako klucz używane są dowolne napisy. Jako wartości mogą być używane liczby, napisy oraz złożone struktury. Ich jest wiele, ale dla zrozumienia ogólnej budowy nie jest to istotne.
Następny poziom abstrakcji po kluczach to sloty (SLOTS). Każdy klucz należy do jednego z 16 383 slotów. W obrębie każdego slotu może być dowolna liczba kluczy. W ten sposób wszystkie klucze dzielą się na 16 383 rozłączne zbiory.
O przeprowadzce z Redis do Redis-cluster

Dalej, w klastrze powinno być N węzłów master. Każdy węzeł można traktować jako oddzielny instancję Redis, która zna wszystko o innych węzłach w ramach klastra. Każdy węzeł master zawiera pewną liczbę slotów. Każdy slot należy tylko do jednego węzła master. Wszystkie sloty muszą być rozdzielone między węzłami. Jeśli jakieś sloty nie są rozdzielone, klucze znajdujące się w nich będą niedostępne. Każdy węzeł master warto uruchomić na oddzielnej maszynie logicznej lub fizycznej. Należy również pamiętać, że każdy węzeł działa tylko na jednym rdzeniu, a jeśli chcesz uruchomić kilka instancji Redis na jednej maszynie logicznej, upewnij się, że będą działały na różnych rdzeniach (nie próbowaliśmy tego robić, ale teoretycznie wszystko powinno działać). W istocie, węzły master zapewniają konwencjonalne sharding, a większa liczba węzłów master pozwala na skalowanie zapytań do zapisu i odczytu.

Po tym, jak wszystkie klucze zostały przypisane do slotów, a sloty rozmieszczone po węzłach master, do każdej węzła master można dodać dowolną liczbę węzłów slave. W ramach każdej takiej pary „master-slave” odbywa się standardowa replikacja. Węzły slaves są potrzebne do skalowania zapytań do odczytu oraz do awaryjnego przełączania w przypadku awarii mastera.
O przeprowadzce z Redis do Redis-cluster

Teraz porozmawiajmy o operacjach, które warto znać.

Do systemu będziemy się odnosić przez Redis-CLI. Ponieważ Redis nie ma jednego punktu wejścia, wykonać następujące operacje można na dowolnym z węzłów. W każdym punkcie zwracam uwagę na możliwość wykonywania operacji pod obciążeniem.

  • Pierwsze i najważniejsze, co będzie nam potrzebne: operacja cluster nodes. Zwraca ona stan klastra, pokazuje listę węzłów, ich role, rozmieszczenie slotów itd. Dodatkowe informacje można uzyskać przy pomocy cluster info i cluster slots.
  • Dobrze byłoby umieć dodawać i usuwać węzły. W tym celu dostępne są operacje cluster meet i cluster forget. Zauważ, że cluster forget musi być zastosowane do KAŻDEGO węzła, zarówno do masterów, jak i replik. Natomiast cluster meet wystarczy wywołać tylko na jednym węźle. Taka różnica może być zniechęcająca, dlatego lepiej dowiedzieć się o niej przed uruchomieniem klastra w produkcji. Dodawanie węzła odbywa się bezpiecznie na żywo i nie wpływa na działanie klastra (co jest logiczne). Jeśli jednak planujesz usunąć węzeł z klastra, upewnij się, że nie ma na nim slotów (w przeciwnym razie ryzykujesz utratę dostępu do wszystkich kluczy na tym węźle). Nie usuwaj też mastera, który ma slave'y, w przeciwnym razie będzie się odbywać zbędne głosowanie na nowego mastera. Jeśli na węzłach już nie ma slotów, to jest to drobny problem, ale po co nam dodatkowe wybory, jeśli można najpierw usunąć slave'y.
  • Jeśli trzeba wymusić zamianę mastera i slave'a, nada się polecenie cluster failover. Uruchamiając je na żywo, należy zrozumieć, że przez czas wykonywania operacji master będzie niedostępny. Zazwyczaj przełączenie odbywa się w mniej niż sekundę, ale nie atomowo. Możesz się spodziewać, że część zapytań do mastera w tym czasie zakończy się błędem.
  • Przed usunięciem węzła z klastra nie powinno na nim pozostać slotów. Lepiej je przenieść za pomocą polecenia cluster reshard. Sloty zostaną przeniesione z jednego mastera na inny. Cała operacja może zająć kilka minut, w zależności od ilości przenoszonych danych, jednak proces przenoszenia jest bezpieczny i nie wpływa na działanie klastra. W ten sposób wszystkie dane można przenieść z jednego węzła na drugi pod obciążeniem, nie martwiąc się o ich dostępność. Istnieją jednak pewne niuanse. Po pierwsze, przenoszenie danych wiąże się z określonym obciążeniem węzła odbierającego i wysyłającego. Jeśli węzeł odbierający jest już mocno obciążony procesorem, nie należy go dodatkowo obciążać nowymi danymi. Po drugie, gdy na masterze-źródle nie pozostanie ani jeden slot, wszystkie jego slave'y natychmiast przejdą do mastera, na który te sloty zostały przeniesione. Problem w tym, że wszystkie te slave'y jednocześnie zechcą zsynchronizować dane. I będziecie mieli jeszcze szczęście, jeśli będzie to częściowa, a nie pełna synchronizacja. Weźcie to pod uwagę i łączcie operacje przenoszenia slotów z odłączaniem/przenoszeniem slave'ów. Albo miejcie nadzieję, że macie wystarczający zapas wydajności.
  • Co robić, jeśli podczas przenoszenia odkryliście, że gdzieś zagubiliście sloty? Mam nadzieję, że ten problem was nie dotknie, ale jeżeli, to istnieje operacja cluster fix. W jakiś sposób rozmieszcza sloty po węzłach w losowej kolejności. Zalecam sprawdzenie jej działania, uprzednio usuwając z klastra węzeł z rozproszonymi slotami. Ponieważ dane w nierozproszonych slotach i tak są niedostępne, martwienie się o problemy z dostępnością tych slotów jest już spóźnione. Z kolei operacja nie wpłynie na rozproszone sloty.
  • Jeszcze jedna przydatna operacja to monitor. Pozwala ona w czasie rzeczywistym zobaczyć cały wykaz żądań kierowanych do węzła. Co więcej, można na niej wykonać grep i dowiedzieć się, czy jest potrzebny ruch.

Warto również wspomnieć o procedurze awaryjnego przełączania głównego. Krótko mówiąc, istnieje ona i, moim zdaniem, działa doskonale. Nie należy jednak sądzić, że jeśli wyciągnie się kabel z gniazdka w maszynie z głównym węzłem, Redis natychmiast przełączy się i klienci nie zauważą przerwy w dostępie. Z mojej praktyki wynika, że przełączanie zajmuje kilka sekund. W tym czasie część danych będzie niedostępna: nastąpi odkrycie niedostępności głównego, węzły zagłosują na nowego, podrzędne będą się przełączać, a dane z synchronizowane. Najlepszym sposobem, aby samodzielnie się przekonać, że schemat działa, jest przeprowadzenie lokalnych ćwiczeń. Uruchom klaster na swoim laptopie, nałóż minimalne obciążenie, zasymuluj awarię (na przykład blokując porty) i oceniaj szybkość przełączania. Moim zdaniem, tylko spędzając w ten sposób dzień lub dwa, można być pewnym działania technologii. Albo zaufać, że oprogramowanie, z którego korzysta połowa internetu, na pewno działa.

Konfiguracja

Często to konfiguracja jest pierwszym krokiem, który należy wykonać, aby zacząć korzystać z narzędzia. A kiedy wszystko działa, nie chce się ruszać konfiguracji. Wymaga to pewnych wysiłków, aby zmusić się do powrotu do ustawień i dokładnego ich przeszukania. Z mojej pamięci było co najmniej dwa poważne wpadki z powodu niedbałości wobec konfiguracji. Zwróć szczególną uwagę na następujące punkty:

  • timeout 0
    Czas, po którym zamykane są nieaktywne połączenia (w sekundach). 0 — nie są zamykane.
    Nie każda nasza biblioteka potrafiła poprawnie zamykać połączenia. Wyłączając tę ustawienie, ryzykujemy natrafienie na limit liczby klientów. Z drugiej strony, jeśli taki problem występuje, automatyczne zerwanie zaginionych połączeń zamaskuje go i możemy tego nie zauważyć. Ponadto nie należy włączać tej opcji przy korzystaniu z połączeń persist.
  • Save x y & appendonly yes
    Zapisanie zrzutu RDB.
    Problemy z RDB/AOF omówimy szczegółowo poniżej.
  • stop-writes-on-bgsave-error no & slave-serve-stale-data yes
    Jeśli włączone, to w przypadku awarii zrzutu RDB główny przestaje przyjmować żądania zmian. Jeśli połączenie z głównym zostanie utracone, podrzędny może nadal odpowiadać na zapytania (tak). Lub przestanie odpowiadać (nie).
    Nie akceptujemy sytuacji, w której Redis staje się dynią.
  • repl-ping-slave-period 5
    Po tym czasie zaczniemy się martwić, że master może być uszkodzony i pora na przeprowadzenie procedury failover.
    Będziemy musieli ręcznie znaleźć równowagę między fałszywymi alarmami a uruchomieniem failover. Z naszego doświadczenia to trwa 5 sekund.
  • repl-backlog-size 1024mb & epl-backlog-ttl 0
    Dokładnie tyle danych możemy przechować w buforze dla odłączonej repliki. Jeśli bufor się skończy, konieczna będzie pełna synchronizacja.
    Praktyka pokazuje, że lepiej ustawić większą wartość. Przyczyn, dla których replika może zacząć się opóźniać, jest wiele. Jeśli zacznie się opóźniać, prawdopodobnie twój master już ma trudności, a pełna synchronizacja stanie się ostatnią kroplą.
  • maxclients 10000
    Maksymalna liczba jednoczesnych klientów.
    Z naszego doświadczenia lepiej ustawić większą wartość. Redis świetnie radzi sobie z 10 tys. połączeń. Upewnij się tylko, że w systemie jest wystarczająco dużo gniazd.
  • maxmemory-policy volatile-ttl
    Reguła, według której klucze są usuwane po osiągnięciu limitu dostępnej pamięci.
    Ważne jest nie tyle samo prawo, co zrozumienie, jak to będzie wyglądać. Redis zasługuje na pochwałę za zdolność do pracy przy osiągnięciu limitu pamięci.

Problemy RDB i AOF

Chociaż sam Redis przechowuje wszystkie informacje w pamięci operacyjnej, istnieje również mechanizm zapisywania danych na dysku. A dokładniej, trzy mechanizmy:

  • RDB-snapshot — pełny zrzut wszystkich danych. Ustala się go za pomocą konfiguracji SAVE X Y i odczytuje jako "Zachować pełny zrzut wszystkich danych co X sekund, jeśli zmienia się przynajmniej Y kluczy."
  • Append-only file — lista operacji w kolejności ich wykonania. Dodaje nowe operacje do pliku co X sekund lub co Y operacji.
  • RDB i AOF — kombinacja dwóch poprzednich.

Każda z metod ma swoje zalety i wady, nie będę ich wszystkich wymieniać, zwrócę jedynie uwagę na nieoczywiste, moim zdaniem, aspekty.

Przede wszystkim, do zapisania RDB-snapshotu konieczne jest wywołanie FORK. Jeśli danych jest dużo, może to zablokować cały Redis na okres od kilku milisekund do sekundy. Ponadto system musi przydzielić pamięć na taki zrzut, co prowadzi do konieczności utrzymania na maszynie logicznej podwójnego zapasu pamięci operacyjnej: jeśli dla Redis przydzielono 8 GB, to na wirtualnej maszynie z nim powinno być dostępne 16.

Po drugie, występują problemy z częściową synchronizacją. W trybie AOF, po ponownym połączeniu slave'a, może być wykonywana pełna synchronizacja zamiast częściowej. Dlaczego tak się dzieje, nie zdołałem zrozumieć. Ale warto to mieć na uwadze.

Te dwa punkty zmuszają do zastanowienia się, czy naprawdę potrzebujemy tych danych na dysku, skoro są już zduplikowane przez slave'y. Utrata danych może nastąpić tylko w przypadku awarii wszystkich slave'ów, a to jest problem na poziomie «pożaru w centrum danych». Jako kompromis można zaproponować przechowywanie danych tylko na slave'ach, ale w takim przypadku trzeba upewnić się, że te slave'y nigdy nie staną się masterem podczas awaryjnego przywracania (w tym celu istnieje ustawienie priorytetu slave'ów w ich konfiguracji). W każdym konkretnym przypadku zastanawiamy się, czy dane powinny być zapisywane na dysku i najczęściej odpowiadamy „nie”.

Podsumowanie

Na koniec mam nadzieję, że udało mi się przedstawić ogólny obraz działania redis-cluster-a tym, którzy w ogóle o nim nie słyszeli, a także zwrócić uwagę na nieoczywiste kwestie dla tych, którzy już od dłuższego czasu go używają.
Dziękuję za poświęcony czas i, jak zwykle, komentarze na temat są mile widziane.

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster