Tarantool Cartridge: шардирование на Lua бекенда в три реда

Tarantool Cartridge: шардирование на Lua бекенда в три реда

В Mail.ru Group имаме Tarantool — това е приложение на Lua, което в същото време е и база данни (или обратното?). То е бързо и чудесно, но възможностите на един сървър все пак не са безкрайни. Вертикалното мащабиране също не е панацея, затова в Tarantool има инструменти за хоризонтално мащабиране — модул vshard. [1]. Той позволява да се шардят данни на няколко сървъра, но ще е необходимо да се поработи, за да го конфигурирате и да добавите бизнес логиката.

Добри новини: натрупахме опит (например [2], [3]) и разработихме нов фреймуърк, който значително улеснява решаването на този проблем.

Tarantool Cartridge е нов фреймуърк за разработка на сложни разпределени системи. Той позволява да се фокусирате върху написването на бизнес логиката вместо да решавате инфраструктурни проблеми. Под капака ще разкажа как този фреймуърк работи и как с него да пишете разпределени услуги.

А каква е всъщност проблемът?

Имаме тарнтула, имаме vshard — какво още да искаме?

На първо място, става въпрос за удобство. Конфигурацията на vshard се настройва чрез Lua таблици. За да работи правилно разпределената система от няколко процеса Tarantool, конфигурацията трябва да бъде навсякъде еднаква. Никой не иска да се занимава с това ръчно. Затова влизат в действие различни скриптове, Ansible и системи за разгръщане.

Cartridge автоматично управлява конфигурацията на vshard, прави го на базата на своята собствена разпределена конфигурация. По същество, това е прост YAML файл, копие от който се съхранява във всеки екземпляр на Tarantool. Оптимизацията се състои в това, че фреймуъркът сам следи своята конфигурация и че тя е еднаква навсякъде.

На второ място, отново става въпрос за удобство. Конфигурацията на vshard няма нищо общо с разработката на бизнес логика и само отклонява програмиста от работата. Когато обсъждаме архитектурата на определен проект, най-често ставаме въпрос за отделни компоненти и тяхното взаимодействие. Още е рано да мислим за разгръщането на клъстера на 3 дата центъра.

Ние решавахме тези проблеми многократно и в определен момент успяхме да разработим подход, който улеснява работата с приложението през целия му жизнен цикъл: създаване, разработка, тестване, CI/CD, поддръжка.

Картриджът въвежда понятието за роля за всеки процес на Tarantool. Ролите са концепция, която позволява на разработчика да се съсредоточи върху написването на код. Всички налични в проекта роли могат да бъдат стартирани на един екземпляр на Tarantool и за тестове това е напълно достатъчно.

Основни възможности на Tarantool Cartridge:

  • автоматизирано оркестриране на клъстера;
  • разширяване на функционалността на приложението с нови роли;
  • шаблон на приложението за разработка и внедряване;
  • вградено автоматично шардирование;
  • интеграция с тестовия фреймворк Luatest;
  • управление на клъстера чрез WebUI и API;
  • инструменти за опаковане и внедряване.

Здравей, свят!

Нямам търпение да покажа самия фреймворк, затова оставяме разказа за архитектурата за по-късно и започваме с простото. Ако предположим, че Tarantool вече е инсталиран, остава само да направим:

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

Тези две команди ще инсталират инструменти за команден ред и ще ни позволят да създадем първото си приложение от шаблона:

$ cartridge create --name myapp

И ето какво ще получим:

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/

Това е git хранилище с готово приложение „Hello, World!“. Нека веднага опитаме да го стартираме, след като инсталираме зависимостите (включително и самия фреймворк):

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

Така че, имаме стартирана една нода на бъдещото шардируемо приложение. Любопитният потребител може веднага да отвори уеб интерфейса, да конфигурира клъстера от един възел с мишката и да се наслаждава на резултата, но все още не е време за радост. Все още приложението не може да прави нищо полезно, затова ще говоря за внедряване по-късно, а сега е време да пишем код.

Разработка на приложения

Представете си, че проектираме проект, който трябва да приема данни, да ги съхранява и веднъж на ден да генерира отчет.

Tarantool Cartridge: шардирование на Lua бекенда в три реда

Започваме да рисуваме схемата и поставяме три компонента: gateway, storage и scheduler. Продължаваме с развитието на архитектурата. Тъй като използваме vshard като хранилище, добавяме в схемата vshard-router и vshard-storage. Нито gateway, нито scheduler ще се обръщат директно към хранилището, за това има роутер, който е създаден именно за тази цел.

Tarantool Cartridge: шардирование на Lua бекенда в три реда

Тази схема все още не отразява точно това, което ще създаваме в проекта, защото компонентите изглеждат абстрактно. Трябва да видим как ще се проектира на реален Tarantool — да групираме компонентите по процеси.

Tarantool Cartridge: шардирование на Lua бекенда в три реда

Няма много смисъл да държим vshard-router и gateway на отделни инстанции. Защо да се налага да пътуваме по мрежата, когато е в задълженията на роутера? Те трябва да бъдат стартирани в един и същи процес. Тоест, в един процес да се инициализират както gateway, така и vshard.router.cfg, и нека взаимодействат локално.

По време на проектирането беше удобно да работим с три компонента, но аз, като разработчик, не искам да се замислям за стартиране на три инстанции на Tarantool, докато пиша код. Нужно ми е да стартирам тестове и да проверя дали правилно съм написал gateway. Или, може би, искам да демонстрирам функция на колегите си. Защо да се измъчвам с разгръщането на три инстанции? Точно по този начин възникна концепцията за роли. Ролята е обикновен луашен модул, чийто жизнен цикъл се управлява от Cartridge. В този пример има четири — gateway, router, storage, scheduler. В друг проект може да има повече. Всички роли могат да бъдат стартирани в един и същи процес, и това ще е достатъчно.

Tarantool Cartridge: шардирование на Lua бекенда в три реда

А когато дойде време за разгръщане в staging или в експлоатация, тогава ще зададем набор от роли на всеки процес на Tarantool в зависимост от хардуерните възможности:

Tarantool Cartridge: шардирование на Lua бекенда в три реда

Управление на топологията

Информацията за това, къде са стартирани различните роли, трябва да бъде съхранявана на някое място. И това "някъде" е разпределената конфигурация, за която вече споменах по-горе. Най-важното в нея е топологията на клъстера. Тук са изобразени 3 репликационни групи от 5 процеса на Tarantool:

Tarantool Cartridge: шардирование на Lua бекенда в три реда

Не искаме да загубим данни, затова внимателно се отнасяме към информацията за стартираните процеси. Cartridge следи конфигурацията с помощта на двуфазно потвърждение. В момента, в който искаме да актуализираме конфигурацията, той първо проверява наличността на всички инстанции и тяхната готовност да приемат новата конфигурация. След това, на втория етап, се прилага конфигурацията. Така, дори ако една инстанция е временно недостъпна, няма да се случи нищо фатално. Конфигурацията просто няма да бъде приложена и предварително ще видите грешка.

В секцията за топология е посочен и важен параметър, като лидер на всяка репликационна група. Обикновено това е този екземпляр, на който се записват данни. Останалите са по-скоро само за четене, въпреки че може да има изключения. Понякога смели разработчици не се страхуват от конфликти и могат да записват данни паралелно на няколко реплики, но има операции, които, независимо от всичко, не трябва да се изпълняват два пъти. За това има признак на лидер.

Tarantool Cartridge: шардирование на Lua бекенда в три реда

Живот на ролите

За да съществува абстрактната роля в такава архитектура, рамката трябва да управлява тях. Разбира се, управлението става без да се рестартира процеса на Tarantool. За управлението на ролите съществуват 4 колбека. Cartridge самостоятелно ще ги извика в зависимост от това, което е написано в разпределената конфигурация, прилагайки по този начин конфигурацията за конкретни роли.

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

Всяка роля има функция init. Тя се извиква веднъж или при активиране на ролята, или при рестартиране на Tarantool. Там е удобно, например, да инициализирате box.space.create, или scheduler може да стартира някакъв фонова fiber, който ще извършва работа на определени интервали от време.

Една функция init може да бъде недостатъчна. Cartridge позволява на ролите да използват разпределената конфигурация, която той използва за съхранение на топологията. В тази конфигурация можем да обявим нова секция и да съхраняваме в нея фрагмент от бизнес конфигурацията. В моя пример това може да бъде схема на данни или настройки на график за ролята scheduler.

Кластера извиква validate_config и apply_config при всяка промяна на разпределената конфигурация. Когато конфигурацията се прилага с двуфазен комит, кластерът проверява, че всяка роля е готова да приеме новата конфигурация и при необходимост информира потребителя за грешка. Когато всички се съгласят с новата конфигурация, се изпълнява apply_config.

Също така ролите имат метод stop, който е нужен за почистване на резултатите от дейността на ролята. Ако кажем, че scheduler на този сървър вече не е необходим, той може да спре файловете, които е стартирал с помощта на init.

Ролите могат да взаимодейства помежду си. Свикнали сме да пишем извиквания на функции на Lua, но може да се случи така, че в този процес да няма необходимата роля. За да улесним мрежовите обращения, използваме помощен модул rpc (remote procedure call), който е построен на базата на стандартния netbox, вграден в Tarantool. Това може да бъде полезно, ако например Вашият gateway иска директно да помоли scheduler да извърши работа в момента, а не да изчаква 24 часа.

Още един важен момент – осигуряване на отказоустойчивост. За мониторинг на здравето в Cartridge се използва протокол SWIM. [4]. Ако говорим накратко, процесите обменят помежду си „слухове“ по UDP – всеки процес разказва на своите съседи последните новини, а те отговарят. Ако изведнъж отговор не дойде, Tarantool започва да подозира нещо нередно, а след известно време обявява смърт и започва да разказва на всички околни тази новина.

Tarantool Cartridge: шардирование на Lua бекенда в три реда

На базата на този протокол Cartridge организира автоматична обработка на отказите. Всеки процес следи своето обкръжение и ако лидерът изведнъж престане да отговаря, репликата може да поеме неговата роля, а Cartridge съответно конфигурира стартираните роли.

Tarantool Cartridge: шардирование на Lua бекенда в три реда

Тук трябва да бъдете внимателни, защото честото превключване наляво-надясно може да доведе до конфликти на данни при репликация. Включването на автоматичен failover на сляпо, разбира се, не е разумно. Трябва ясно да разбирате какво се случва и да сте сигурни, че репликацията няма да се разруши след възстановяването на лидера и връщането му на короната.

От всичко казано може да се състави впечатление, че ролите са подобни на микросервизи. В известен смисъл те наистина са такива, само че като модули в рамките на процесите на Tarantool. Но има и редица принципни различия. На първо място, всички роли на проекта трябва да живеят в една кодова база. И всички процеси на Tarantool трябва да се стартират от една кодова база, за да няма изненади, като например, когато се опитваме да инициализираме scheduler, а него просто го няма. Също така не бива да допускаме разлики в версиите на кода, защото поведението на системата в такава ситуация е много трудно за предвиждане и отстраняване.

За разлика от Docker, не можем просто да вземем "образа" на ролята, да го пренесем на друга машина и там да го стартираме. Нашите роли не са толкова изолирани, колкото Docker-контейнерите. Не можем също така да стартираме две еднакви роли на един и същ екземпляр. Ролята или съществува, или я няма, в известен смисъл това е singleton. И трето, в цялата репликационна група ролите трябва да са идентични, защото в противен случай би било смешно — данните са еднакви, а конфигурацията различна.

Инструменти за разгръщане

Обещах да покажа как Cartridge помага за разгръщането на приложения. За да улесни живота на околните, фреймуъркът опакова RPM-пакети:

$ cartridge pack rpm myapp -- ще опакова за нас .\/myapp-0.1.0-1.rpm
$ sudo yum install .\/myapp-0.1.0-1.rpm

Инсталираният пакет носи почти всичко необходимо: и приложението, и инсталираните lua зависимости. Tarantool ще дойде на сървъра като зависимост на RPM-пакета и нашият сервис е готов за стартиране. Това става чрез systemd, но първо трябва да напишем малко конфигурация. Минимум, да посочим URI на всеки процес. Трите за пример са достатъчни.

$ 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

Тук има интересен нюанс. Вместо да посочим само порта на бинарния протокол, посочваме публичния адрес на процеса изцяло, включително hostname. Това е необходимо, за да знаят възлите на клъстера как да се свързват помежду си. Лоша идея е да използвате адрес 0.0.0.0 за advertise_uri, това трябва да бъде външен IP-адрес, а не адрес на свързващия сокет. Без него нищо няма да работи, така че Cartridge просто няма да позволи старта на узел с неправилен advertise_uri.

Сега, когато конфигурацията е готова, можем да стартираме процесите. Тъй като обикновеният systemd юнит не позволява стартиране на повече от един процес, приложенията на Cartridge инсталират т.н. instantiated юнити, които работят по следния начин:

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

В конфигурацията посочихме HTTP порта, на който Cartridge обслужва уеб интерфейса — 8080. Нека да го отворим и да видим:

Tarantool Cartridge: шардирование на Lua бекенда в три реда

Виждаме, че процесите са стартирани, но все още не са конфигурирани. Картриджът не знае кой с кого трябва да се репликира и не може да вземе решение самостоятелно, затова чака нашите действия. А нашият избор е малък: животът на новия клъстер започва с конфигурацията на първия възел. После ще добавим останалите към клъстера, ще им назначим роли, и на това внедряването може да се счита успешно завършено.

Наливаме чашка с любимата напитка и се отпускаме след дългата работна седмица. Приложението може да се експлоатира.

Tarantool Cartridge: шардирование на Lua бекенда в три реда

Резюме

А какви са резултатите? Изпробвайте, ползвайте, оставяйте обратна връзка, създавайте тикети в GitHub.

Връзки

[1] Tarantool » 2.2 » Справочник » Справочник за Rocks » Модул vshard

[2] Как внедрихме ядрото на инвестиционния бизнес на Алфа-Банк на база Tarantool

[3] Архитектурата на ново поколение за таксуване: трансформация с преминаване на Tarantool.

[4] SWIM — протокол за изграждане на клъстери

[5] GitHub — tarantool/cartridge-cli

[6] GitHub — tarantool/cartridge

Източник: habr.com

Купете надежден хостинг за сайтове с защита от DDoS, VPS VDS сървъри 🔥 Купете надежден хостинг за сайтове с защита от DDoS, VPS VDS сървъри | ProHoster