Cartucho Tarantool: fragmento dun backend Lua en tres liñas

Cartucho Tarantool: fragmento dun backend Lua en tres liñas

En Mail.ru Group temos Tarantool: este é un servidor de aplicacións en Lua, que tamén funciona como base de datos (ou viceversa?). É rápido e xenial, pero as capacidades dun servidor aínda non son ilimitadas. A escala vertical tampouco é unha panacea, polo que Tarantool ten ferramentas para a escala horizontal: o módulo vshard [1]. Permíteche dividir datos en varios servidores, pero tes que retocar con eles para configuralos e conectar a lóxica empresarial.

Boas noticias: recompilamos algunhas fotos importantes (p. ex [2], [3]) e creou outro marco que simplificará significativamente a solución a este problema.

Cartucho Tarantool é un novo marco para o desenvolvemento de sistemas distribuídos complexos. Permítelle centrarse en escribir lóxica empresarial en lugar de resolver problemas de infraestrutura. Debaixo do corte contarei como funciona este framework e como escribir servizos distribuídos usándoo.

Cal é exactamente o problema?

Temos unha tarántula, temos vshard - que máis podes querer?

En primeiro lugar, é unha cuestión de conveniencia. A configuración de vshard configúrase a través de táboas Lua. Para que un sistema distribuído de múltiples procesos Tarantool funcione correctamente, a configuración debe ser a mesma en todas partes. Ninguén quere facelo manualmente. Polo tanto, utilízanse todo tipo de scripts, Ansible e sistemas de despregamento.

O propio cartucho xestiona a configuración vshard, faino en función da súa propia configuración distribuída. É esencialmente un ficheiro YAML sinxelo, cuxa copia se almacena en cada instancia de Tarantool. A simplificación é que o propio cadro monitoriza a súa configuración e garante que sexa o mesmo en todas partes.

En segundo lugar, é de novo unha cuestión de conveniencia. A configuración de vshard non ten nada que ver co desenvolvemento da lóxica empresarial e só distrae ao programador do seu traballo. Cando comentamos a arquitectura dun proxecto, a maioría das veces falamos de compoñentes individuais e da súa interacción. É demasiado cedo para pensar en implementar un clúster en 3 centros de datos.

Resolvemos estes problemas unha e outra vez, e nalgún momento conseguimos desenvolver un enfoque que simplificou o traballo coa aplicación ao longo de todo o seu ciclo de vida: creación, desenvolvemento, probas, CI/CD, mantemento.

Cartridge introduce o concepto de función para cada proceso de Tarantool. Os roles son un concepto que permite a un programador centrarse en escribir código. Todos os roles dispoñibles no proxecto pódense executar nunha instancia de Tarantool, e isto será suficiente para as probas.

Características principais do cartucho Tarantool:

  • orquestración de clusters automatizada;
  • ampliando a funcionalidade da aplicación utilizando novos roles;
  • modelo de aplicación para o desenvolvemento e implantación;
  • fragmentación automática incorporada;
  • integración co marco de probas Luatest;
  • xestión de clúster mediante WebUI e API;
  • ferramentas de empaquetado e implantación.

Ola, mundo!

Non podo esperar para mostrar o marco en si, así que deixaremos a historia sobre a arquitectura para máis adiante e comezaremos con algo sinxelo. Se asumimos que Tarantool xa está instalado, só queda por facer

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

Estes dous comandos instalarán as utilidades da liña de comandos e permítenche crear a túa primeira aplicación a partir do modelo:

$ cartridge create --name myapp

E isto é o que obtemos:

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/

Este é un repositorio git cun "Hello, World!" aplicación. Tentemos executalo de inmediato, instalando previamente as dependencias (incluíndo o propio cadro):

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

Polo tanto, temos un nodo en execución para a futura aplicación fragmentada. Un profano curioso pode abrir inmediatamente a interface web, configurar un clúster dun nodo co rato e gozar do resultado, pero é demasiado cedo para alegrarse. Ata agora, a aplicación non pode facer nada útil, así que falarei sobre a implantación máis tarde, pero agora toca escribir código.

Desenvolvemento de aplicacións

Imaxinade, estamos deseñando un proxecto que debe recibir datos, gardalos e construír un informe unha vez ao día.

Cartucho Tarantool: fragmento dun backend Lua en tres liñas

Comezamos a debuxar un diagrama e colocamos nel tres compoñentes: pasarela, almacenamento e planificador. Seguimos traballando na arquitectura. Dado que usamos vshard como almacenamento, engadimos vshard-router e vshard-storage ao esquema. Nin a pasarela nin o programador accederán directamente ao almacenamento; para iso está o enrutador, para iso foi creado.

Cartucho Tarantool: fragmento dun backend Lua en tres liñas

Este diagrama aínda non representa exactamente o que imos construír no proxecto porque os compoñentes parecen abstractos. Aínda temos que ver como se proxectará isto no Tarantool real: agrupemos os nosos compoñentes por proceso.

Cartucho Tarantool: fragmento dun backend Lua en tres liñas

Non ten sentido manter o enrutador vshard e a pasarela en instancias separadas. Por que necesitamos navegar pola rede unha vez máis se isto xa é responsabilidade do enrutador? Deben executarse dentro do mesmo proceso. É dicir, tanto gateway como vshard.router.cfg inícianse nun proceso e permítenlles interactuar localmente.

Na fase de deseño, era conveniente traballar con tres compoñentes, pero eu, como desenvolvedor, mentres escribo o código, non quero pensar en lanzar tres instancias de Tarnatool. Necesito realizar probas e comprobar que escribín correctamente a pasarela. Ou quizais quero demostrar unha característica aos meus compañeiros. Por que debería pasar pola molestia de despregar tres copias? Así naceu o concepto de roles. Un rol é un módulo luash regular cuxo ciclo de vida é xestionado por Cartridge. Neste exemplo hai catro deles: pasarela, enrutador, almacenamento e planificador. Pode haber máis noutro proxecto. Todos os roles pódense executar nun só proceso, e isto será suficiente.

Cartucho Tarantool: fragmento dun backend Lua en tres liñas

E cando se trate de implementación para a posta en escena ou a produción, asignaremos a cada proceso de Tarantool o seu propio conxunto de roles dependendo das capacidades do hardware:

Cartucho Tarantool: fragmento dun backend Lua en tres liñas

Xestión da topoloxía

A información sobre onde se executan os roles debe almacenarse nalgún lugar. E este "nalgún lugar" é a configuración distribuída, que xa mencionei anteriormente. O máis importante é a topoloxía do clúster. Aquí están 3 grupos de replicación de 5 procesos de Tarantool:

Cartucho Tarantool: fragmento dun backend Lua en tres liñas

Non queremos perder datos, polo que tratamos a información sobre os procesos en execución con coidado. Cartridge fai un seguimento da configuración mediante unha confirmación de dúas fases. Unha vez que queremos actualizar a configuración, primeiro comproba que todas as instancias están dispoñibles e listas para aceptar a nova configuración. Despois diso, a segunda fase aplica a configuración. Así, aínda que unha copia resulte non dispoñible temporalmente, non pasará nada malo. Simplemente non se aplicará a configuración e verás un erro con antelación.

Tamén na sección de topoloxía indícase un parámetro tan importante como o líder de cada grupo de replicación. Normalmente esta é a copia que se está gravando. O resto adoita ser de só lectura, aínda que pode haber excepcións. Ás veces, os desenvolvedores valentes non teñen medo aos conflitos e poden escribir datos en varias réplicas en paralelo, pero hai algunhas operacións que, pase o que pase, non se deben realizar dúas veces. Para iso hai un sinal de líder.

Cartucho Tarantool: fragmento dun backend Lua en tres liñas

Vida dos roles

Para que exista un papel abstracto nesta arquitectura, o marco debe xestionalos dalgún xeito. Por suposto, o control ocorre sen reiniciar o proceso de Tarantool. Hai 4 devolucións de chamada para xestionar roles. O propio Cartucho chamaraos dependendo do que estea escrito na súa configuración distribuída, aplicando así a configuración a roles específicos.

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

Cada rol ten unha función init. Chámase unha vez cando se activa o rol ou cando se reinicia Tarantool. É conveniente alí, por exemplo, inicializar box.space.create, ou o planificador pode lanzar algunha fibra en segundo plano que fará traballo en determinados intervalos de tempo.

Unha función init pode non ser suficiente. Cartridge permite aos roles aproveitar a configuración distribuída que usa para almacenar a topoloxía. Podemos declarar unha nova sección na mesma configuración e almacenar nela un fragmento da configuración empresarial. No meu exemplo, isto podería ser un esquema de datos ou unha configuración de programación para o rol de planificador.

Chamadas en clúster validate_config и apply_config cada vez que cambie a configuración distribuída. Cando se aplica unha configuración mediante unha confirmación en dúas fases, o clúster comproba que cada rol está preparado para aceptar esta nova configuración e, se é necesario, informa dun erro ao usuario. Cando todos estean de acordo en que a configuración é normal, entón o apply_config.

Tamén os roles teñen un método stop, que é necesario para limpar a saída do rol. Se dicimos que o programador xa non é necesario neste servidor, pode deter esas fibras coas que comezou init.

Os papeis poden interactuar entre si. Estamos afeitos a escribir chamadas de función en Lua, pero pode ocorrer que un determinado proceso non teña o papel que necesitamos. Para facilitar as chamadas a través da rede, usamos o módulo auxiliar rpc (chamada de procedemento remoto), que está construído sobre a base da netbox estándar integrada en Tarantool. Isto pode ser útil se, por exemplo, a súa pasarela quere pedir directamente ao planificador que faga o traballo agora mesmo, en lugar de esperar un día.

Outro punto importante é garantir a tolerancia a fallos. O cartucho usa o protocolo SWIM para supervisar a saúde [4]. En resumo, os procesos intercambian "rumores" entre si a través de UDP: cada proceso lles informa aos seus veciños as últimas noticias e estes responden. Se de súpeto a resposta non chega, Tarantool comeza a sospeitar que algo está mal, e despois dun tempo recita a morte e comeza a contar esta noticia a todos.

Cartucho Tarantool: fragmento dun backend Lua en tres liñas

En base a este protocolo, Cartridge organiza o procesamento automático de fallos. Cada proceso supervisa o seu ambiente e, se o líder deixa de responder de súpeto, a réplica pode asumir o seu papel e Cartridge configura os roles en execución en consecuencia.

Cartucho Tarantool: fragmento dun backend Lua en tres liñas

Debes ter coidado aquí, porque os cambios frecuentes de ida e volta poden provocar conflitos de datos durante a replicación. Por suposto, non deberías activar o fallo automático ao chou. Debemos comprender claramente o que está a suceder e estar seguros de que a réplica non se romperá despois de que o líder sexa restaurado e lle devolva a coroa.

De todo isto, pode ter a sensación de que os roles son similares aos microservizos. En certo sentido, son só iso, só como módulos dentro dos procesos de Tarantool. Pero tamén hai unha serie de diferenzas fundamentais. En primeiro lugar, todos os roles do proxecto deben vivir na mesma base de código. E todos os procesos de Tarantool deberían lanzarse desde a mesma base de código, para que non haxa sorpresas como as que intentamos inicializar o planificador, pero simplemente non existe. Ademais, non debe permitir diferenzas nas versións de código, porque o comportamento do sistema en tal situación é moi difícil de predicir e depurar.

A diferenza de Docker, non podemos simplemente tomar unha "imaxe" de papel, levala a outra máquina e executala alí. Os nosos roles non están tan illados como os contedores Docker. Ademais, non podemos executar dous roles idénticos nunha instancia. Un papel existe ou non; en certo sentido, é un único. E, en terceiro lugar, os roles deben ser os mesmos dentro de todo o grupo de replicación, porque se non sería absurdo: os datos son os mesmos, pero a configuración é diferente.

Ferramentas de implantación

Prometín mostrar como Cartridge axuda a implementar aplicacións. Para facilitarlles a vida aos demais, o marco paquetes paquetes RPM:

$ cartridge pack rpm myapp -- упакует для нас ./myapp-0.1.0-1.rpm
$ sudo yum install ./myapp-0.1.0-1.rpm

O paquete instalado contén case todo o que precisa: tanto a aplicación como as dependencias instaladas. Tarantool tamén chegará ao servidor como unha dependencia do paquete RPM e o noso servizo está listo para lanzarse. Isto faise a través de systemd, pero primeiro cómpre escribir unha pequena configuración. Como mínimo, especifique o URI de cada proceso. Tres é suficiente, por exemplo.

$ 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

Aquí hai un matiz interesante. En lugar de especificar só o porto do protocolo binario, especificamos o enderezo público completo do proceso, incluíndo o nome de host. Isto é necesario para que os nodos do clúster saiban conectarse entre si. É unha mala idea usar 0.0.0.0 como enderezo advertise_uri; debería ser un enderezo IP externo, non un enlace de socket. Sen el, nada funcionará, polo que Cartridge simplemente non che permitirá lanzar un nodo co advertise_uri incorrecto.

Agora que a configuración está lista, pode iniciar os procesos. Dado que unha unidade de sistema normal non permite que se inicie máis dun proceso, as aplicacións do Cartucho son instaladas polo chamado. unidades instanciadas que funcionan así:

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

Na configuración, especificamos o porto HTTP no que Cartridge serve a interface web: 8080. Imos a el e botamos unha ollada:

Cartucho Tarantool: fragmento dun backend Lua en tres liñas

Vemos que aínda que os procesos están en execución, aínda non están configurados. O cartucho aínda non sabe quen debe replicar con quen e non pode tomar unha decisión por si mesmo, polo que está á espera das nosas accións. Pero non temos moita opción: a vida dun novo clúster comeza coa configuración do primeiro nodo. A continuación, engadiremos os outros ao clúster, asignarémoslles roles e neste momento pódese considerar que a implantación finalizou con éxito.

Imos botar un vaso da túa bebida favorita e relaxarte despois dunha longa semana de traballo. A aplicación pódese usar.

Cartucho Tarantool: fragmento dun backend Lua en tres liñas

Resultados de

Cales son os resultados? Probao, utilízao, deixa comentarios, crea entradas en Github.

referencias

[1] Tarantool » 2.2 » Referencia » Referencia de rocas » Módulo vshard

[2] Como implementamos o núcleo do negocio de investimento de Alfa-Bank baseado en Tarantool

[3] Arquitectura de facturación de nova xeración: transformación coa transición a Tarantool

[4] SWIM - protocolo de construción de cluster

[5] GitHub - tarantool/cartucho-cli

[6] GitHub - tarantool/cartucho

Fonte: www.habr.com

Engadir un comentario