Tarantool Cartridge: podział Lua backendu w trzy linijki

Tarantool Cartridge: podział Lua backendu w trzy linijki

W Mail.ru Group mamy Tarantoola — to serwer aplikacji na Lua, który jednocześnie pełni funkcję bazy danych (lub odwrotnie?). Jest szybki i świetny, ale możliwości jednego serwera nie są nieskończone. Skalowanie pionowe również nie jest panaceum, dlatego w Tarantoolu są narzędzia do skalowania horyzontalnego — moduł vshard. [1]. Pozwala on na shardowanie danych pomiędzy wieloma serwerami, ale będzie trzeba się trochę napracować, aby go skonfigurować i wdrożyć logikę biznesową.

Dobre wieści: popełniliśmy kilka błędów (na przykład [2], [3]) i stworzyliśmy kolejny framework, który znacznie ułatwi rozwiązanie tego problemu.

Tarantool Cartridge — to nowy framework do tworzenia złożonych systemów rozproszonych. Pozwala skupić się na pisaniu logiki biznesowej zamiast zajmować się problemami infrastrukturalnymi. Poniżej opowiem, jak ten framework działa i jak za jego pomocą pisać rozproszone usługi.

A w czym tak naprawdę tkwi problem?

Mamy tarantula, mamy vshard — czego jeszcze chcieć?

Po pierwsze, chodzi o wygodę. Konfiguracja vshard jest ustawiana przez tabele Lua. Aby rozproszony system złożony z kilku procesów Tarantool działał poprawnie, konfiguracja musi być wszędzie taka sama. Nikt nie chce robić tego ręcznie. Dlatego wykorzystywane są różne skrypty, Ansible, systemy wdrożeniowe.

Cartridge sam zarządza konfiguracją vshard, robi to na podstawie swojej własnej rozproszonej konfiguracji.To w zasadzie prosty plik YAML, którego kopia jest przechowywana w każdym egzemplarzu Tarantool. Uproszczeniem jest to, że framework sam dba o swoją konfigurację i o to, aby była wszędzie taka sama.

Po drugie, znów chodzi o wygodę. Konfiguracja vshard nie ma nic wspólnego z rozwojem logiki biznesowej i tylko odciąga programistę od pracy. Kiedy omawiamy architekturę danego projektu, najczęściej mówimy o poszczególnych komponentach i ich interakcji. O uruchomieniu klastra w 3 centrach danych myślenie jest przedwczesne.

Rozwiązywaliśmy te problemy raz po raz i w pewnym momencie udało nam się wypracować podejście, które pozwala uprościć pracę z aplikacją na całym jej cyklu życia: tworzenie, rozwój, testowanie, CI/CD, utrzymanie.

Cartridge wprowadza pojęcie roli dla każdego procesu Tarantool. Role to koncepcja, która pozwala programiście skupić się na pisaniu kodu. Wszystkie dostępne w projekcie role można uruchomić na jednym egzemplarzu Tarantool, a do testów to będzie wystarczające.

Główne możliwości Tarantool Cartridge:

  • automatyzacja orkiestracji klastra;
  • rozszerzanie funkcjonalności aplikacji za pomocą nowych ról;
  • szablon aplikacji do tworzenia i wdrażania;
  • wbudowane automatyczne shardowanie;
  • integracja z frameworkiem testowym Luatest;
  • zarządzanie klastrem za pomocą WebUI i API;
  • narzędzia do pakowania i wdrażania.

Witaj, świecie!

Nie mogę się doczekać, aby pokazać sam framework, więc opowieść o architekturze zostawimy na później i zaczniemy od prostego. Zakładając, że Tarantool jest już zainstalowany, wystarczy tylko wykonać

$ tarantoolctl rocks install cartridge-cli
$ export PATH=$PWD/.rocks/bin/:$PATH

Te dwa polecenia zainstalują narzędzia wiersza poleceń i pozwolą stworzyć pierwszą aplikację na podstawie szablonu:

$ cartridge create --name myapp

I oto co otrzymamy:

myapp/
├── .git/
├── .gitignore
├── app/roles/custom.lua
├── deps.sh
├── init.lua
├── myapp-scm-1.rockspec
├── test
│   ├── helper
│   │   ├── integration.lua
│   │   └── unit.lua
│   ├── helper.lua
│   ├── integration/api_test.lua
│   └── unit/sample_test.lua
└── tmp/

To jest repozytorium git z gotową aplikacją „Hello, World!”. Spróbujmy od razu ją uruchomić, najpierw instalując zależności (w tym sam framework):

$ tarantoolctl rocks make
$ ./init.lua --http-port 8080

I tak mamy uruchomioną jedną nodę przyszłej shardowanej aplikacji. Ciekawski obserwator może od razu otworzyć interfejs internetowy, skonfigurować klaster z jednego węzła za pomocą myszy i cieszyć się wynikiem, ale na radość jeszcze za wcześnie. Jak na razie aplikacja nie potrafi nic użytecznego, dlatego o wdrożeniu opowiem później, a teraz czas na pisanie kodu.

Tworzenie aplikacji

Wyobraźmy sobie, że projektujemy aplikację, która ma przyjmować dane, przechowywać je i codziennie generować raport.

Tarantool Cartridge: podział Lua backendu w trzy linijki

Zaczynamy rysować schemat i umieszczamy na nim trzy komponenty: gateway, storage i scheduler. Dalsze opracowanie architektury. Ponieważ jako pamięć używamy vshard, dodajemy do schematu vshard-router i vshard-storage. Ani gateway, ani scheduler nie będą bezpośrednio odwoływać się do pamięci, do tego służy router, który został właśnie do tego stworzony.

Tarantool Cartridge: podział Lua backendu w trzy linijki

Ten schemat wciąż nie do końca odzwierciedla to, co będziemy tworzyć w projekcie, ponieważ komponenty wyglądają abstrakcyjnie. Musimy jeszcze sprawdzić, jak to się przełoży na rzeczywisty Tarantool — pogrupujemy nasze komponenty według procesów.

Tarantool Cartridge: podział Lua backendu w trzy linijki

Nie ma sensu trzymać vshard-router i gateway na oddzielnych instancjach. Po co ponownie przechodzić przez sieć, jeśli to należy do zadań routera? Powinny być uruchomione w jednym procesie. To znaczy, że w jednym procesie inicjalizowane są zarówno gateway, jak i vshard.router.cfg, i niech działają lokalnie.

Na etapie projektowania współpraca z trzema komponentami była wygodna, ale jako programista nie chcę się martwić o uruchomienie trzech instancji Tarnatool podczas pisania kodu. Muszę uruchomić testy i sprawdzić, czy poprawnie napisałem gateway. A może chcę zademonstrować funkcję kolegom. Po co męczyć się z uruchamianiem trzech instancji? W ten sposób powstała koncepcja ról. Rola to zwykły moduł Lua, którym zarządza Cartridge. W tym przykładzie mamy cztery — gateway, router, storage, scheduler. W innym projekcie może ich być więcej. Wszystkie role można uruchomić w jednym procesie, co będzie wystarczające.

Tarantool Cartridge: podział Lua backendu w trzy linijki

A kiedy przyjdzie czas na wdrożenie w staging lub produkcję, wtedy przypiszemy każdemu procesowi Tarantool zestaw ról w zależności od możliwości sprzętowych:

Tarantool Cartridge: podział Lua backendu w trzy linijki

Zarządzanie topologią

Informacje o tym, gdzie jakie role są uruchomione, muszą być gdzieś przechowywane. A tym „gdzieś” jest rozproszona konfiguracja, o której już wspomniałem. Najważniejsza w niej jest topologia klastra. Tutaj pokazano 3 grupy replikacyjne z 5 procesami Tarantool:

Tarantool Cartridge: podział Lua backendu w trzy linijki

Nie chcemy stracić danych, dlatego ostrożnie podchodzimy do informacji o uruchomionych procesach. Cartridge monitoruje konfigurację za pomocą dwuetapowego zatwierdzenia. Gdy chcemy zaktualizować konfigurację, najpierw sprawdza dostępność wszystkich instancji i ich gotowość do przyjęcia nowej konfiguracji. Następnie w drugim etapie stosowany jest nowy config. Dzięki temu, nawet jeśli jedna instancja jest tymczasowo niedostępna, nic strasznego się nie stanie. Konfiguracja po prostu nie zostanie zastosowana i z wyprzedzeniem zobaczysz błąd.

W sekcji topologii wskazany jest również ważny parametr, jakim jest lider każdej grupy replikacji. Zazwyczaj jest to ten egzemplarz, na który odbywa się zapis. Pozostałe często są tylko do odczytu, chociaż mogą być wyjątki. Czasami odważni programiści nie boją się konfliktów i mogą równolegle zapisywać dane na kilku replikach, ale są operacje, które nie powinny być wykonywane dwukrotnie. Do tego jest wyznaczony lider.

Tarantool Cartridge: podział Lua backendu w trzy linijki

Życie ról

Aby abstrakcyjna rola mogła istnieć w takiej architekturze, framework musi w jakiś sposób nimi zarządzać. Oczywiście zarządzanie odbywa się bez restartu procesu Tarantool. Istnieją 4 wywołania zwrotne do zarządzania rolami. Cartridge sam je wywoła w zależności od tego, co ma zapisane w rozproszonej konfiguracji, stosując tym samym konfigurację do konkretnych ról.

function init()
function validate_config()
function apply_config()
function stop()

Każda rola ma funkcję init. Jest wywoływana raz, albo przy włączaniu roli, albo przy ponownym uruchamianiu Tarantool’a. Tam wygodnie jest na przykład zainicjować box.space.create, albo scheduler może uruchomić jakiś wątek, który będzie wykonywał pracę w określonych odstępach czasu.

Jednej funkcji init może być niewystarczająco. Cartridge pozwala rolom korzystać z tej rozproszonej konfiguracji, którą używa do przechowywania topologii. W tej samej konfiguracji możemy ogłosić nową sekcję i przechować w niej fragment konfiguracji biznesowej. W moim przykładzie może to być schemat danych lub ustawienia harmonogramu dla roli scheduler.

Klaster wywołuje validate_config i apply_config przy każdej zmianie rozproszonej konfiguracji. Gdy konfiguracja jest stosowana dwuetapowym zatwierdzeniem, klaster sprawdza, czy każda rola jest gotowa przyjąć tę nową konfigurację, a w razie potrzeby informuje użytkownika o błędzie. Kiedy wszyscy zgodzą się, że konfiguracja jest poprawna, następuje wykonanie apply_config.

Również role mają metodę stop, która jest potrzebna do oczyszczenia wyników działalności roli. Jeśli mówimy, że scheduler na tym serwerze nie jest już potrzebny, może zatrzymać te wątki, które uruchomił za pomocą init.

Role mogą ze sobą współdziałać. Przyzwyczailiśmy się pisać wywołania funkcji w Lua, ale może się zdarzyć, że w danym procesie brakuje nam potrzebnej roli. Aby ułatwić komunikację sieciową, używamy pomocniczego modułu rpc (remote procedure call), który jest oparty na standardowym netboxie wbudowanym w Tarantool. Może się to przydać, jeśli na przykład Twój gateway zechce bezpośrednio poprosić scheduler o wykonanie pracy natychmiast, zamiast czekać cały dzień.

Kolejną ważną kwestią jest zapewnienie odporności na awarie. Do monitorowania stanu zdrowia w Cartridge używany jest protokół SWIM. [4]. Mówiąc krótko, procesy wymieniają się między sobą "plotkami" za pośrednictwem UDP - każdy proces przekazuje swoim sąsiadom najnowsze informacje, a oni odpowiadają. Jeśli nagle odpowiedź nie nadejdzie, Tarantool zaczyna podejrzewać, że coś jest nie tak, a po pewnym czasie ogłasza śmierć i zaczyna opowiadać wszystkim wokół tę nowinę.

Tarantool Cartridge: podział Lua backendu w trzy linijki

Na podstawie tego protokołu Cartridge organizuje automatyczne zarządzanie awariami. Każdy proces monitoruje swoje otoczenie, a jeśli lider nagle przestaje odpowiadać, replika może przejąć jego rolę, a Cartridge odpowiednio konfiguruje uruchomione role.

Tarantool Cartridge: podział Lua backendu w trzy linijki

Tutaj trzeba być ostrożnym, ponieważ częste przełączanie tam i z powrotem może prowadzić do konfliktów danych podczas replikacji. Oczywiście nie warto włączać automatycznego failovera bez zastanowienia. Należy wyraźnie rozumieć, co się dzieje, i być pewnym, że replikacja nie ulegnie uszkodzeniu po tym, jak lider się odnowi i wróci na tron.

Z tego, co zostało powiedziane, może wynikać wrażenie, że role są podobne do mikroserwisów. W pewnym sensie nimi są, tylko jako moduły wewnątrz procesów Tarantool. Istnieje jednak szereg zasadniczych różnic. Po pierwsze, wszystkie role projektu muszą żyć w jednej bazie kodu. Wszystkie procesy Tarantool muszą być uruchamiane z jednej bazy kodu, aby uniknąć niespodzianek, takich jak te, gdy próbujemy zainicjować scheduler, a go po prostu nie ma. Również nie należy dopuścić do różnic w wersjach kodu, ponieważ zachowanie systemu w takiej sytuacji jest bardzo trudne do przewidzenia i debugowania.

W przeciwieństwie do Dockera, nie możemy po prostu wziąć „obrazu” roli, przenieść go na inny komputer i tam uruchomić. Nasze role nie są tak izolowane jak kontenery Dockera. Nie możemy też uruchomić na jednym egzemplarzu dwóch identycznych ról. Rola albo istnieje, albo jej nie ma, w pewnym sensie to singleton. Po trzecie, w całej grupie replikacyjnej role muszą być takie same, ponieważ inaczej byłoby to absurdalne — dane są identyczne, a konfiguracje różne.

Narzędzia do wdrożeń

Obiecałem pokazać, jak Cartridge pomaga w wdrażaniu aplikacji. Aby ułatwić życie innym, framework pakuje pakiety RPM:

$ cartridge pack rpm myapp -- zapakuje dla nas .\/myapp-0.1.0-1.rpm
$ sudo yum install .\/myapp-0.1.0-1.rpm

Zainstalowany pakiet zawiera prawie wszystko, co potrzebne: aplikację i zainstalowane zależności Lua. Tarantool na serwer również przyjedzie jako zależność pakietu RPM, a nasza usługa jest gotowa do uruchomienia. Dzieje się to za pośrednictwem systemd, ale najpierw trzeba napisać trochę konfiguracji. Co najmniej trzeba określić URI każdego procesu. Trzy dla przykładu wystarczą.

$ sudo tee \/etc\/tarantool\/conf.d\/demo.yml <<CONFIG
myapp.router: {"advertise_uri": "localhost:3301", "http_port": 8080}
myapp.storage_A: {"advertise_uri": "localhost:3302", "http_enabled": False}
myapp.storage_B: {"advertise_uri": "localhost:3303", "http_enabled": False}
CONFIG

Jest tu interesujący szczegół. Zamiast określać tylko port protokołu binarnego, podajemy pełny publiczny adres procesu, włączając hostname. Jest to potrzebne, aby węzły klastra wiedziały, jak się ze sobą połączyć. Złe pomysłem jest używanie jako advertise_uri adresu 0.0.0.0, to powinien być zewnętrzny adres IP, a nie adres bind socket. Bez niego nic nie będzie działać, dlatego Cartridge po prostu nie pozwoli uruchomić węzła z nieprawidłowym advertise_uri.

Teraz, gdy konfiguracja jest gotowa, można uruchomić procesy. Ponieważ zwykła jednostka systemd nie pozwala na uruchomienie więcej niż jednego procesu, aplikacje na Cartridge instalują tzw. instantiated-units, które działają w następujący sposób:

$ sudo systemctl start myapp@router
$ sudo systemctl start myapp@storage_A
$ sudo systemctl start myapp@storage_B

W konfiguracji określiliśmy port HTTP, na którym Cartridge obsługuje interfejs webowy — 8080. Wejdźmy na niego i zobaczmy:

Tarantool Cartridge: podział Lua backendu w trzy linijki

Zauważamy, że procesy są uruchomione, ale jeszcze nie skonfigurowane. Kartridż nie wie jeszcze, kto powinien się replikować z kim i nie może podjąć decyzji samodzielnie, dlatego czeka na nasze działanie. A nasz wybór nie jest duży: życie nowego klastra zaczyna się od konfiguracji pierwszego węzła. Później dodamy pozostałe do klastra, przypiszemy im role i można uznać, że wdrożenie zostało pomyślnie zakończone.

Nalejmy sobie kubek ulubionego napoju i zrelaksujmy się po długim tygodniu pracy. Aplikacja jest gotowa do użytku.

Tarantool Cartridge: podział Lua backendu w trzy linijki

Podsumowanie

A jakie są wyniki? Wypróbuj, korzystaj, zostawiaj opinie, zakładaj zgłoszenia na GitHubie.

Linki

[1] Tarantool » 2.2 » Referencje » Referencje Rocks » Moduł vshard

[2] Jak wdrażaliśmy rdzeń inwestycyjnego biznesu Alfa-Banku na bazie Tarantoola

[3] Architektura billingowa nowej generacji: transformacja przy użyciu Tarantoola

[4] SWIM — protokół budowy klastra

[5] GitHub — tarantool/cartridge-cli

[6] GitHub — tarantool/cartridge

Ź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