Cartucho Tarantool: Fragmento Lua Backend em três linhas


No Mail.ru Group, temos o Tarantool, um servidor de aplicativos baseado em Lua e um banco de dados unido. É rápido e elegante, mas os recursos de um único servidor são sempre limitados. A escala vertical também não é a panacéia. É por isso que o Tarantool possui algumas ferramentas para dimensionamento horizontal, ou o módulo vshard [1] . Ele permite que você espalhe dados por vários servidores, mas será necessário mexer com eles por um tempo para configurá-los e seguir a lógica de negócios.

Boas notícias: obtivemos nossa parcela de falhas (por exemplo, [2] , [3] ) e criamos outra estrutura, que simplifica significativamente a solução para esse problema.

O cartucho Tarantool é a nova estrutura para o desenvolvimento de sistemas distribuídos complexos. Ele permite que você se concentre em escrever a lógica de negócios em vez de resolver problemas de infraestrutura. Abaixo, mostrarei como essa estrutura funciona e como ela poderia ajudar na criação de serviços distribuídos.

Então, qual é exatamente o problema?


Temos Tarantool e vshard - o que mais queremos?

Primeiro, é uma questão de conveniência. Vshard está configurado nas tabelas Lua. Mas, para que um sistema distribuído de vários processos Tarantool funcione corretamente, a configuração deve ser a mesma em todos os lugares. Ninguém gostaria de fazê-lo manualmente, para que todos os tipos de scripts, Ansible e sistemas de implantação sejam usados.

O próprio cartucho gerencia a configuração vshard com base em sua própria configuração distribuída . De fato, é um arquivo YAML simples e sua cópia é armazenada em todas as instâncias do Tarantool. Em outras palavras, a estrutura monitora sua configuração para que seja a mesma em todos os lugares.

Segundo, é novamente uma questão de conveniência. A configuração do Vshard não está relacionada ao desenvolvimento da lógica de negócios e apenas distrai um desenvolvedor de seu trabalho. Quando discutimos a arquitetura de um projeto, o assunto provavelmente diz respeito a componentes separados e sua interação. É muito cedo para pensar em implantar um cluster para três datacenters.

Resolvemos esses problemas repetidamente e, em algum momento, conseguimos desenvolver uma abordagem para simplificar o trabalho com o aplicativo durante todo o seu ciclo de vida: criação, desenvolvimento, teste, CI / CD, manutenção.

Cartucho introduz o conceito de papéis para cada processo Tarantool. As funções permitem que o desenvolvedor se concentre em escrever código. Todas as funções disponíveis no projeto podem ser executadas na instância única do Tarantool, e isso seria suficiente para teste.

Principais recursos do cartucho Tarantool:

  • orquestração automatizada de cluster;
  • funcionalidade de aplicativo expandida com novas funções;
  • modelo de aplicativo para desenvolvimento e implantação;
  • sharding automático embutido;
  • integração com o framework Luatest;
  • gerenciamento de cluster usando WebUI e API;
  • ferramentas de empacotamento e implantação.

Olá Mundo!


Mal posso esperar para mostrar a estrutura em si, então vamos salvar a história sobre arquitetura para mais tarde e começar com uma tarefa fácil. Supondo que o Tarantool já esteja instalado, tudo o que precisamos fazer é

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

Como resultado, os utilitários de linha de comando são instalados, o que permite criar seu primeiro aplicativo a partir do modelo:

 $ cartridge create --name myapp 

E aqui está o que temos:

 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 é um repositório git com um "Olá, Mundo!", Pronto para uso. aplicação. Vamos tentar executá-lo depois de instalar as dependências (incluindo a própria estrutura):

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

Lançamos um nó do nosso futuro aplicativo sharded. Se você estiver curioso, poderá abrir imediatamente a interface da Web, que é executada no localhost : 8080, use o mouse para configurar um cluster de um nó e aproveitar o resultado, mas não fique animado muito cedo. O aplicativo ainda não sabe fazer nada de útil, então falarei sobre a implantação mais tarde e agora é hora de escrever algum código.

Desenvolvendo aplicativos


Imagine que estamos projetando um sistema que deve receber dados, salvá-los e criar um relatório uma vez por dia.


Então, desenhamos um diagrama com três componentes: gateway, armazenamento e agendador. Vamos continuar trabalhando na arquitetura. Como usamos vshard como armazenamento, vamos adicionar vshard-router e vshard-storage ao diagrama. Nem o gateway nem o planejador acessarão diretamente o armazenamento - um roteador é criado explicitamente para esta tarefa.


Este diagrama parece abstrato porque os componentes ainda não refletem o que criaremos no projeto. Vamos ter que ver como esse projeto corresponde ao Tarantool real, para que agrupemos nossos componentes pelo processo.


Não faz muito sentido manter o vshard-router e gateway em instâncias separadas. Por que passaríamos pela rede mais uma vez, se isso já é de responsabilidade do roteador? Eles devem ser executados no mesmo processo, ou seja, o gateway e o vshard.router.cfg devem ser inicializados no mesmo processo e interagir localmente.

Durante a fase de design, foi conveniente trabalhar com três componentes, mas como desenvolvedor, não quero pensar em lançar três instâncias do Tarantool enquanto escrevia o código. Preciso executar os testes e verificar se escrevi o código do gateway corretamente. Ou talvez queira mostrar um novo recurso aos meus colegas de trabalho. Por que eu teria problemas com a implantação de três instâncias? Assim, nasceu o conceito de papéis. Uma função é um módulo Lua regular e o Cartridge gerencia seu ciclo de vida. Neste exemplo, existem quatro: gateway, roteador, armazenamento e agendador. Outro projeto pode ter mais papéis. Todas as funções podem ser iniciadas em um processo, e isso seria suficiente.


E quando o assunto se refere à implantação na preparação ou produção, atribuímos um conjunto separado de funções a cada processo do Tarantool, dependendo dos recursos de hardware subjacentes:


Gerenciamento de topologia


Também devemos armazenar informações sobre as funções em execução em algum lugar. E "algures" significa a configuração distribuída acima mencionada. A coisa mais importante aqui é a topologia de cluster. Aqui você pode ver 3 grupos de replicação de 5 processos Tarantool:


Como não queremos perder os dados, tratamos as informações sobre os processos em execução com cuidado. O cartucho monitora a configuração usando uma confirmação de duas fases. Assim que queremos atualizar a configuração, ele primeiro verifica se as instâncias estão disponíveis e prontas para aceitar a nova configuração. Depois disso, a configuração é aplicada na segunda fase. Assim, mesmo se uma instância estiver temporariamente indisponível, nada poderá dar errado. A configuração simplesmente não será aplicada e você verá um erro com antecedência.

A seção de topologia também possui um parâmetro tão importante quanto o líder de cada grupo de replicação. Geralmente, esta é a instância que aceita as gravações. O restante geralmente é somente leitura, embora possa haver exceções. Às vezes, desenvolvedores corajosos não têm medo de conflitos e podem gravar dados em várias réplicas ao mesmo tempo. No entanto, algumas operações não devem ser executadas duas vezes. É por isso que temos um líder.


Ciclo de vida da função


Para que uma arquitetura de projeto contenha funções abstratas, a estrutura deve, de alguma forma, ser capaz de gerenciá-las. Naturalmente, as funções são gerenciadas sem reiniciar o processo Tarantool. Existem quatro retornos de chamada projetados para gerenciamento de funções. O próprio cartucho os chama, dependendo das informações da configuração distribuída, aplicando a configuração às funções específicas.

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

Cada função tem uma função init . É chamado uma vez: quando a função está ativada ou quando o Tarantool é reiniciado. Aqui é conveniente, por exemplo, inicializar box.space.create, ou o planejador pode executar alguma fibra em segundo plano que concluiria a tarefa em intervalos regulares.

A função init sozinha pode não ser suficiente. O cartucho permite que as funções acessem a configuração distribuída usada para armazenar a topologia. Na mesma configuração, podemos declarar uma nova seção e armazenar uma parte da configuração de negócios lá. No meu exemplo, isso pode ser um esquema de dados ou configurações de agendamento para a função de agendador.

O cluster chama validate_config e apply_config toda vez que a configuração distribuída é alterada. Quando uma configuração é aplicada em uma confirmação de duas fases, o cluster verifica se cada função em cada servidor está pronta para aceitar essa nova configuração e, se necessário, relata um erro ao usuário. Quando todos concordam com a configuração, apply_config é chamado.

As funções também suportam um método de stop para limpar o lixo. Se dissermos que não há necessidade do agendador neste servidor, ele pode parar as fibras que ele iniciou usando init .

As funções podem interagir umas com as outras. Estamos acostumados a escrever chamadas de função Lua, mas o processo pode não ter a função necessária. Para facilitar o acesso à rede, usamos um módulo auxiliar chamado rpc (chamada de procedimento remoto), construído com base no módulo Tarantool net.box padrão. Isso pode ser útil, por exemplo, se o seu gateway desejar solicitar ao agendador diretamente para executar a tarefa agora, em vez de em um dia.

Outro ponto importante é garantir a tolerância a falhas. O cartucho usa o protocolo SWIM [4] para monitorar a saúde. Em suma, os processos trocam "rumores" uns com os outros via UDP, ou seja, todo processo conta a seus vizinhos as últimas notícias e eles respondem. Se de repente não houver resposta, Tarantool suspeita que algo está errado e, depois de um tempo, declara a morte e envia essa mensagem a todos.


Com base neste protocolo, o cartucho organiza o failover automático. Cada processo monitora seu ambiente e, se o líder parar subitamente de responder, a réplica poderá reivindicar sua função, e o Cartucho configurará as funções em execução de acordo.


Você precisa ter cuidado aqui, pois alternar com frequência pode resultar em conflitos de dados durante a replicação. O failover automático certamente não deve ser ativado aleatoriamente. Você deve ter uma idéia clara do que está acontecendo e garantir que a replicação não falhe quando o líder se recuperar e recuperar sua coroa.

De tudo o que foi dito, as funções podem parecer semelhantes aos microsserviços. Em certo sentido, eles são apenas módulos nos processos do Tarantool e existem várias diferenças fundamentais. Primeiro, todas as funções do projeto devem viver na mesma base de código. E todos os processos do Tarantool devem ser executados na mesma base de código, para que não haja surpresas, como quando tentamos inicializar o agendador, mas simplesmente não há agendador. Além disso, não devemos permitir diferenças nas versões do código, porque o comportamento do sistema é complicado de prever e depurar em tal situação.

Ao contrário do Docker, não podemos simplesmente pegar uma "imagem" de uma função, transferi-la para outra máquina e executá-la lá. Nossas funções não são tão isoladas quanto os contêineres do Docker. Além disso, não podemos executar duas funções idênticas na mesma instância. O papel está lá ou não; em certo sentido, é um singleton. E em terceiro lugar, as funções devem ser as mesmas dentro de todo o grupo de replicação porque, caso contrário, pareceria ridículo: os dados são iguais, mas o comportamento é diferente.

Ferramentas de implantação


Prometi mostrar a você como o Cartucho poderia ajudar a implantar aplicativos. Para facilitar a vida, a estrutura cria pacotes RPM:

 $ cartridge pack rpm myapp # will create ./myapp-0.1.0-1.rpm $ sudo yum install ./myapp-0.1.0-1.rpm 

O pacote instalado contém quase tudo o que você precisa: o aplicativo e as dependências Lua instaladas. O Tarantool também chega ao servidor como uma dependência de pacote RPM, e nosso serviço está pronto para o lançamento. Isso tudo é feito usando o systemd, mas primeiro, devemos fazer algumas configurações, pelo menos especificar o URI de cada processo. Três seriam suficientes para o nosso 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 

Há um aspecto interessante que deve ser considerado: em vez de especificar apenas a porta do protocolo binário, especificamos o endereço público de todo o processo, incluindo o nome do host. Estamos fazendo isso porque os nós do cluster devem saber como se conectar. Seria uma má idéia usar o endereço 0.0.0.0 como advertise_uri, pois deveria ser um endereço IP externo, em vez de um soquete. Nada funciona sem ele, portanto, o Cartucho simplesmente não deixaria o nó com o advertise_uri errado iniciar.

Agora que a configuração está pronta, podemos iniciar os processos. Como uma unidade systemd comum não permite iniciar vários processos, as chamadas unidades instanciadas instalam os aplicativos no cartucho:

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

Especificamos a porta HTTP para a interface da web do cartucho na configuração: 8080. Vamos lá e dar uma olhada:


Podemos ver que os processos ainda não estão configurados, embora já estejam em execução. O cartucho ainda não sabe como a replicação deve ser executada e não pode decidir por conta própria; portanto, está aguardando nossas ações. Não temos muitas opções de escolha: a vida de um novo cluster começa com a configuração do primeiro nó. Em seguida, adicionamos outros nós ao cluster, atribuímos funções a eles e a implantação pode ser considerada concluída com êxito.

Vamos tomar uma bebida e relaxar após uma longa semana de trabalho. O aplicativo está pronto para uso.


Resultados


E os resultados? Teste, use, deixe comentários e crie tickets no Github.

Referências


[1] Tarantool »2.2» Referência »Referência de rochas» Módulo vshard
[2] Como implementamos o núcleo dos negócios de investimento do Alfa-Bank com base no Tarantool
[3] Arquitetura de cobrança de próxima geração: transição para Tarantool
[4] SWIM - Cluster Building Protocol.
[5] GitHub - tarantool / cartucho-cli
[6] GitHub - tarantool / cartucho

Source: https://habr.com/ru/post/pt470812/


All Articles