Tarantool Cartridge: fragmentação de back-end Lua em três linhas

Tarantool Cartridge: fragmentação de back-end Lua em três linhas

No Grupo Mail.ru temos o Tarantool - este é um servidor de aplicativos em Lua, que também funciona como banco de dados (ou vice-versa?). É rápido e interessante, mas os recursos de um servidor ainda não são ilimitados. O dimensionamento vertical também não é uma panacéia, então o Tarantool possui ferramentas para dimensionamento horizontal - o módulo vshard [1]. Ele permite que você fragmente dados em vários servidores, mas é necessário alterá-los para configurá-los e anexar a lógica de negócios.

Boas notícias: reunimos alguns figurões (por exemplo [2], [3]) e criou outra estrutura que simplificará significativamente a solução deste problema.

Cartucho Tarantool é uma nova estrutura para o desenvolvimento de sistemas distribuídos complexos. Ele permite que você se concentre em escrever lógica de negócios em vez de resolver problemas de infraestrutura. Abaixo do corte, contarei como esse framework funciona e como escrever serviços distribuídos usando-o.

E qual é, de fato, o problema?

Temos uma tarântula, temos vshard - o que mais você poderia querer?

Em primeiro lugar, é uma questão de conveniência. A configuração vshard é feita através de tabelas Lua. 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 quer fazer isso manualmente. Portanto, todos os tipos de scripts, Ansible e sistemas de implantação são usados.

O próprio cartucho gerencia a configuração do vshard, ele faz isso com base em seu configuração distribuída própria. É essencialmente um arquivo YAML simples, uma cópia do qual é armazenada em cada instância do Tarantool. A simplificação é que o próprio framework monitora sua configuração e garante que ela seja a mesma em todos os lugares.

Em segundo lugar, é novamente uma questão de conveniência. A configuração vshard não tem nada a ver com o desenvolvimento da lógica de negócios e apenas distrai o programador de seu trabalho. Quando discutimos a arquitetura de um projeto, na maioria das vezes falamos sobre componentes individuais e sua interação. É muito cedo para pensar em implementar um cluster em três data centers.

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

Cartridge introduz o conceito de uma função para cada processo Tarantool. Funções são um conceito que permite ao desenvolvedor se concentrar em escrever código. Todas as funções disponíveis no projeto podem ser executadas em uma instância do Tarantool, e isso será suficiente para testes.

Principais recursos do cartucho Tarantool:

  • orquestração automatizada de clusters;
  • expandindo a funcionalidade do aplicativo usando novas funções;
  • modelo de aplicativo para desenvolvimento e implantação;
  • fragmentação automática integrada;
  • integração com o framework de testes Luatest;
  • gerenciamento de cluster utilizando WebUI e API;
  • ferramentas de empacotamento e implantação.

Olá Mundo!

Mal posso esperar para mostrar o framework em si, então deixaremos a história da arquitetura para mais tarde e começaremos com algo simples. Se assumirmos que o próprio Tarantool já está instalado, então tudo o que resta é fazer

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

Esses dois comandos instalarão os utilitários de linha de comando e permitirão que você crie seu primeiro aplicativo a partir do modelo:

$ cartridge create --name myapp

E é isso 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 é um repositório git com um “Hello, World!” aplicativo. Vamos tentar executá-lo imediatamente, tendo previamente instalado as dependências (incluindo o próprio framework):

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

Portanto, temos um nó em execução para o futuro aplicativo fragmentado. Um leigo curioso pode abrir imediatamente a interface da web, configurar um cluster de um nó com o mouse e aproveitar o resultado, mas é muito cedo para se alegrar. Até agora, o aplicativo não pode fazer nada de útil, então falarei sobre a implantação mais tarde, mas agora é hora de escrever o código.

Desenvolvimento de aplicações

Imagine só, estamos desenhando um projeto que deve receber dados, salvá-los e construir um relatório uma vez por dia.

Tarantool Cartridge: fragmentação de back-end Lua em três linhas

Começamos a desenhar um diagrama e nele colocamos três componentes: gateway, armazenamento e agendador. Estamos trabalhando mais na arquitetura. Como usamos vshard como armazenamento, adicionamos vshard-router e vshard-storage ao esquema. Nem o gateway nem o agendador acessarão diretamente o armazenamento; é para isso que serve o roteador, é para isso que ele foi criado.

Tarantool Cartridge: fragmentação de back-end Lua em três linhas

Este diagrama ainda não representa exatamente o que construiremos no projeto porque os componentes parecem abstratos. Ainda precisamos ver como isso será projetado no Tarantool real – vamos agrupar nossos componentes por processo.

Tarantool Cartridge: fragmentação de back-end Lua em três linhas

Não faz muito sentido manter o vshard-router e o gateway em instâncias separadas. Por que precisamos navegar mais uma vez na rede se isso já é responsabilidade do roteador? Eles devem ser executados dentro do mesmo processo. Ou seja, tanto o gateway quanto o vshard.router.cfg são inicializados em um processo e permitem que interajam localmente.

Na fase de design, era conveniente trabalhar com três componentes, mas eu, como desenvolvedor, ao escrever o código, não quero pensar em lançar três instâncias do Tarnatool. Preciso fazer testes e verificar se escrevi o gateway corretamente. Ou talvez eu queira demonstrar um recurso aos meus colegas. Por que devo me dar ao trabalho de implantar três cópias? Foi assim que nasceu o conceito de papéis. Uma função é um módulo luash regular cujo ciclo de vida é gerenciado pelo Cartridge. Neste exemplo, existem quatro deles: gateway, roteador, armazenamento e agendador. Pode haver mais em outro projeto. Todas as funções podem ser executadas em um processo e isso será suficiente.

Tarantool Cartridge: fragmentação de back-end Lua em três linhas

E quando se trata de implantação em teste ou produção, atribuiremos a cada processo Tarantool seu próprio conjunto de funções, dependendo dos recursos de hardware:

Tarantool Cartridge: fragmentação de back-end Lua em três linhas

Gerenciamento de topologia

As informações sobre onde as funções estão sendo executadas devem ser armazenadas em algum lugar. E esse “algum lugar” é a configuração distribuída, que já mencionei acima. A coisa mais importante sobre isso é a topologia do cluster. Aqui estão 3 grupos de replicação de 5 processos Tarantool:

Tarantool Cartridge: fragmentação de back-end Lua em três linhas

Não queremos perder dados, por isso tratamos as informações sobre os processos em execução com cuidado. O Cartridge monitora a configuração usando um commit de duas fases. Quando quisermos atualizar a configuração, primeiro verificamos se todas as instâncias estão disponíveis e prontas para aceitar a nova configuração. Depois disso, a segunda fase aplica a configuração. Assim, mesmo que uma cópia fique temporariamente indisponível, nada de ruim acontecerá. A configuração simplesmente não será aplicada e você verá um erro antecipadamente.

Também na seção de topologia é indicado um parâmetro tão importante como o líder de cada grupo de replicação. Geralmente esta é a cópia que está sendo gravada. 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 em paralelo, mas existem algumas operações que, aconteça o que acontecer, não devem ser executadas duas vezes. Para isso existe o sinal de um líder.

Tarantool Cartridge: fragmentação de back-end Lua em três linhas

Vida de papéis

Para que exista uma função abstrata em tal arquitetura, a estrutura deve gerenciá-la de alguma forma. Naturalmente, o controle ocorre sem reiniciar o processo Tarantool. Existem 4 retornos de chamada para gerenciar funções. O próprio cartucho irá chamá-los dependendo do que está escrito em sua configuração distribuída, aplicando assim a configuração a funções específicas.

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

Cada papel tem uma função init. É chamado uma vez quando a função é habilitada ou quando o Tarantool é reiniciado. É conveniente, por exemplo, inicializar box.space.create, ou o agendador pode iniciar alguma fibra de fundo que executará o trabalho em determinados intervalos de tempo.

Uma função init pode não ser suficiente. O cartucho permite que as funções aproveitem a configuração distribuída usada para armazenar a topologia. Podemos declarar uma nova seção na mesma configuração e armazenar nela um fragmento da configuração de negócios. No meu exemplo, poderia ser um esquema de dados ou configurações de agendamento para a função de agendador.

Chamadas de cluster validate_config и apply_config sempre que a configuração distribuída muda. Quando uma configuração é aplicada por um commit de duas fases, o cluster verifica se cada função está pronta para aceitar esta nova configuração e, se necessário, reporta um erro ao usuário. Quando todos concordarem que a configuração está normal, então o apply_config.

Também as funções têm um método stop, que é necessário para limpar a saída da função. Se dissermos que o agendador não é mais necessário neste servidor, ele pode parar as fibras com as quais foi iniciado init.

As funções podem interagir entre si. Estamos acostumados a escrever chamadas de função em Lua, mas pode acontecer que um determinado processo não tenha a função que necessitamos. Para facilitar as chamadas pela rede, utilizamos o módulo auxiliar rpc (chamada de procedimento remoto), que é construído com base no netbox padrão embutido no Tarantool. Isto pode ser útil se, por exemplo, o seu gateway quiser pedir diretamente ao agendador para fazer o trabalho agora mesmo, em vez de esperar um dia.

Outro ponto importante é garantir a tolerância a falhas. O cartucho usa o protocolo SWIM para monitorar a saúde [4]. Resumindo, os processos trocam “rumores” entre si por meio do UDP – cada processo conta aos seus vizinhos as últimas notícias e eles respondem. Se de repente a resposta não vier, Tarantool começa a suspeitar que algo está errado e, depois de um tempo, recita a morte e começa a contar a notícia a todos ao seu redor.

Tarantool Cartridge: fragmentação de back-end Lua em três linhas

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

Tarantool Cartridge: fragmentação de back-end Lua em três linhas

Você precisa ter cuidado aqui, porque a alternância frequente pode levar a conflitos de dados durante a replicação. Claro, você não deve habilitar o failover automático aleatoriamente. Devemos compreender claramente o que está acontecendo e ter certeza de que a replicação não será interrompida depois que o líder for restaurado e a coroa lhe for devolvida.

Com tudo isso, você pode ter a sensação de que as funções são semelhantes aos microsserviços. De certa forma, eles são apenas isso, apenas como módulos dentro dos processos do Tarantool. Mas também há uma série de diferenças fundamentais. Primeiro, todas as funções do projeto devem residir na mesma base de código. E todos os processos do Tarantool devem ser lançados a partir da mesma base de código, para que não haja surpresas como aquelas quando tentamos inicializar o agendador, mas ele simplesmente não existe. Além disso, você não deve permitir diferenças nas versões do código, porque o comportamento do sistema em tal situação é muito difícil de prever e depurar.

Ao contrário do Docker, não podemos simplesmente pegar uma “imagem” de função, levá-la para outra máquina e executá-la lá. Nossas funções não são tão isoladas quanto os contêineres Docker. Além disso, não podemos executar duas funções idênticas em uma instância. Um papel existe ou não; em certo sentido, é um singleton. E em terceiro lugar, as funções devem ser as mesmas em todo o grupo de replicação, caso contrário seria um absurdo - os dados são os mesmos, mas a configuração é diferente.

Ferramentas de implantação

Prometi mostrar como o Cartridge ajuda a implantar aplicativos. Para facilitar a vida de outras pessoas, a estrutura empacota pacotes RPM:

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

O pacote instalado contém quase tudo que você precisa: o aplicativo e as dependências instaladas. O Tarantool também chegará ao servidor como uma dependência do pacote RPM, e nosso serviço está pronto para ser lançado. Isso é feito através do systemd, mas primeiro você precisa escrever uma pequena configuração. No mínimo, especifique o URI de cada processo. Três são suficientes, 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

Há uma nuance interessante aqui. Em vez de especificar apenas a porta do protocolo binário, especificamos todo o endereço público do processo, incluindo o nome do host. Isso é necessário para que os nós do cluster saibam como se conectar entre si. Não é uma boa ideia usar 0.0.0.0 como endereço de promote_uri; ele deve ser um endereço IP externo, não uma ligação de soquete. Sem ele, nada funcionará, então o Cartridge simplesmente não permitirá que você inicie um nó com o promote_uri errado.

Agora que a configuração está pronta, você pode iniciar os processos. Como uma unidade systemd normal não permite o início de mais de um processo, os aplicativos no Cartucho são instalados pelos chamados. unidades instanciadas que funcionam assim:

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

Na configuração, especificamos a porta HTTP na qual o Cartridge atende a interface web - 8080. Vamos lá dar uma olhada:

Tarantool Cartridge: fragmentação de back-end Lua em três linhas

Vemos que embora os processos estejam em execução, eles ainda não estão configurados. O cartucho ainda não sabe quem deve replicar com quem e não pode tomar uma decisão por conta própria, por isso aguarda nossas ações. Mas não temos muita escolha: a vida de um novo cluster começa com a configuração do primeiro nó. Em seguida, adicionaremos os outros ao cluster, atribuiremos funções a eles e, neste ponto, a implantação poderá ser considerada concluída com sucesso.

Vamos servir um copo da sua bebida preferida e relaxar após uma longa semana de trabalho. O aplicativo pode ser usado.

Tarantool Cartridge: fragmentação de back-end Lua em três linhas

Resultados de

Quais são os resultados? Experimente, use, deixe comentários, 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 do negócio de investimento do Alfa-Bank baseado no Tarantool

[3] Arquitetura de faturamento de nova geração: transformação com a transição para Tarantool

[4] SWIM - protocolo de construção de cluster

[5] GitHub - tarantool/cartucho-cli

[6] GitHub - tarantool/cartucho

Fonte: habr.com

Adicionar um comentário