
No Mail.ru Group, temos o Tarantool - este é um servidor de aplicativos em Lua, que também possui um banco de dados (ou vice-versa?). É rápido e legal, mas os recursos de um servidor ainda não são ilimitados. A escala vertical também não é uma panacéia, então o Tarantool possui ferramentas para escala horizontal - o módulo vshard
[1] . Ele permite que você compartilhe dados em vários servidores, mas é necessário mexer com eles para configurá-los e fixar a lógica de negócios.
Boas notícias: coletamos os cones (por exemplo
[2] ,
[3] ) e cortamos outra estrutura que simplificará significativamente a solução para esse problema.
O Tarantool Cartridge é uma 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 é organizada e como escrever serviços distribuídos com ela.
E qual é, de fato, o problema?
Temos uma tarântula, há vshard - o que mais você poderia querer?
Em primeiro lugar, o ponto é a conveniência. A configuração do Vshard é configurada através de tabelas Lua. Para que um sistema distribuído de vários processos do 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, sistemas de implantação são usados.
O próprio cartucho gerencia a configuração vshard; faz isso com base em sua
própria configuração distribuída . Em essência, esse é um arquivo YAML simples, cuja cópia é armazenada em cada instância do Tarantool. A simplificação está no fato de que a própria estrutura monitora sua configuração e, portanto, é a mesma em todos os lugares.
Em segundo lugar, o ponto é novamente de conveniência. A configuração não tem relação com o desenvolvimento da lógica de negócios e apenas distrai o programador do trabalho. Quando discutimos a arquitetura de um projeto, na maioria das vezes estamos falando sobre componentes individuais e sua interação. É muito cedo para pensar em implantar um cluster em três data centers.
Resolvemos esses problemas repetidamente e, em algum momento, conseguimos desenvolver uma abordagem para simplificar o trabalho com o aplicativo ao longo de todo o seu ciclo de vida: criação, desenvolvimento, teste, CI / CD, manutenção.
Cartucho apresenta o conceito de função para cada processo Tarantool. Funções são um conceito que permite ao desenvolvedor se concentrar na escrita de 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 cluster;
- expandir a funcionalidade do aplicativo com novas funções;
- modelo de desenvolvimento e implantação de aplicativos;
- sharding automático embutido;
- integração com a estrutura de teste Luatest;
- gerenciamento de cluster usando WebUI e API;
- ferramentas de empacotamento e implantação.
Olá Mundo!
Estou ansioso para mostrar a estrutura em si, então vamos deixar a história sobre arquitetura para mais tarde e começar com uma simples. Supondo que o Tarantool já esteja instalado, tudo o que resta a ser feito é
$ tarantoolctl rocks install cartridge-cli $ export PATH=$PWD/.rocks/bin/:$PATH
Esses dois comandos instalam os utilitários de linha de comando e permitem que você crie 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 o final "Olá, mundo!" aplicação. Vamos tentar executá-lo imediatamente, pré-instalando as dependências (incluindo o próprio framework):
$ tarantoolctl rocks make $ ./init.lua --http-port 8080
Então, lançamos um nó do futuro aplicativo sharded. Um leigo curioso pode abrir imediatamente a interface da web, usar o mouse para configurar um cluster a partir de um nó e apreciar o resultado, mas é muito cedo para se alegrar. Até o momento, o aplicativo não sabe fazer nada de útil, por isso vou falar sobre a implantação mais tarde e agora é hora de escrever código.
Desenvolvimento de aplicações
Imagine, vamos projetar um projeto que deve receber dados, salvá-los e criar um relatório uma vez por dia.

Começamos a desenhar um diagrama e colocamos três componentes nele: 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 repositório; existe um roteador para isso, ele foi criado para isso.

Esse esquema ainda não reflete com precisão o que criaremos no projeto, porque os componentes parecem abstratos. Também precisamos ver como isso é projetado em um Tarantool real - agruparemos nossos componentes por processo.

Manter o vshard-router e gateway em instâncias separadas faz pouco sentido. Por que precisamos passar pela rede mais uma vez, se isso já é de responsabilidade do roteador? Eles devem estar em execução no mesmo processo. Ou seja, em um processo, o gateway e o vshard.router.cfg são inicializados e permitem que eles interajam localmente.
Era conveniente trabalhar com três componentes no estágio de design, mas como desenvolvedor, enquanto escrevo código, não quero pensar em lançar três instâncias do Tarnatool. Preciso executar testes e verificar se escrevi o gateway corretamente. Ou talvez eu queira demonstrar um recurso para meus colegas. Por que devo sofrer com a implantação de três cópias? Foi assim que nasceu o conceito de papéis. Uma função é um módulo Loach regular, cujo ciclo de vida é gerenciado pelo Cartucho. Neste exemplo, existem quatro deles - gateway, roteador, armazenamento, agendador. Em outro projeto, pode haver mais. Todas as funções podem ser iniciadas em um processo, e isso será suficiente.

E quando se trata de implantar na preparação ou na operação, atribuiremos cada conjunto de funções a cada processo do Tarantool, dependendo dos recursos de hardware:

Gerenciamento de topologia
As informações sobre onde quais funções são iniciadas devem ser armazenadas em algum lugar. E esse "lugar" é a configuração distribuída que mencionei acima. O mais importante é a topologia de cluster. Aqui estão 3 grupos de replicação de 5 processos Tarantool:

Não queremos perder dados, portanto tratamos cuidadosamente as informações sobre os processos em execução. O cartucho monitora a configuração com uma confirmação de duas fases. Assim que queremos atualizar a configuração, ele primeiro verifica a disponibilidade de todas as instâncias e sua prontidão para aceitar a nova configuração. Depois disso, a segunda fase aplica a configuração. Assim, mesmo que uma instância estivesse temporariamente indisponível, nada terrível acontecerá. A configuração simplesmente não se aplica e você verá um erro com antecedência.
Também na seção de topologia, é indicado um parâmetro tão importante quanto o líder de cada grupo de replicação. Geralmente, essa é a instância 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, apesar de tudo, não devem ser executadas duas vezes. Há um sinal de um líder para isso.

Vida do papel
Para que exista um papel abstrato em uma arquitetura, a estrutura deve, de alguma forma, gerenciá-los. Naturalmente, o controle ocorre sem reiniciar o processo Tarantool. Existem 4 retornos de chamada para gerenciar funções. O próprio cartucho os chamará, dependendo do que diz em uma configuração distribuída, aplicando a configuração a 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. É conveniente, por exemplo, inicializar box.space.create, ou o planejador pode iniciar alguma fibra em segundo plano, que fará o trabalho em determinados intervalos.
A 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. Na mesma configuração, podemos declarar uma nova seção e armazenar nela um fragmento da configuração de negócios. 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
sempre que a configuração distribuída é alterada. Quando uma configuração é aplicada por uma confirmação de duas fases, o cluster verifica se cada função está pronta para aceitar essa nova configuração e, se necessário, relata um erro ao usuário. Quando todos concordam que a configuração é normal, o
apply_config
é
apply_config
.
As funções também têm um método de
stop
, necessário para limpar os sinais vitais da função. Se dissermos que o agendador neste servidor não é mais necessário, ele poderá parar as fibras que ele iniciou com o
init
.
As funções podem interagir umas com as outras. Estamos acostumados a escrever chamadas de função em Lua, mas pode acontecer que não tenhamos a função de que precisamos nesse processo. Para facilitar o acesso à rede, usamos o módulo auxiliar rpc (chamada de procedimento remoto), que é construído com base na caixa de rede padrão do Tarantool. Isso pode ser útil se, por exemplo, seu gateway desejar solicitar diretamente ao agendador que faça o trabalho agora, em vez de esperar um dia.
Outro ponto importante é garantir a tolerância a falhas. O cartucho usa o protocolo SWIM
[4] para monitorar a saúde. Em resumo, os processos trocam “rumores” entre eles via UDP - cada processo conta a seus vizinhos as últimas notícias e eles respondem. Se a resposta não chegar, Tarantool começa a suspeitar que algo está errado, e depois de um tempo ele recita a morte e começa a contar a todos sobre essas notícias.

Com base neste protocolo, o cartucho organiza o failover automático. Cada processo monitora seu ambiente e, se o líder parar de responder repentinamente, a réplica pode assumir 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 levar a conflitos de dados durante a replicação. Ativar o failover automático aleatoriamente, é claro, não vale a pena. Você precisa entender claramente o que está acontecendo e ter certeza de que a replicação não será interrompida depois que o líder se recuperar e a coroa retornar a ele.
De tudo o que foi dito, pode parecer que as funções sejam semelhantes aos microsserviços. De certa forma, eles são apenas módulos nos processos do Tarantool. Mas há várias diferenças fundamentais. Primeiro, todas as funções do projeto devem viver em uma base de código. E todos os processos do Tarantool devem ser iniciados a partir de uma base de código, para que não haja surpresas como aquelas quando tentamos inicializar o agendador, mas isso simplesmente não acontece. Além disso, não permita 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 apenas pegar a "imagem" de uma 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 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, é único. E em terceiro lugar, as funções devem ser as mesmas dentro de todo o grupo de replicação, porque, caso contrário, seria ridículo - os dados são os mesmos e a configuração é diferente.
Ferramentas de implantação
Prometi mostrar como o Cartucho 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 carrega quase tudo o que você precisa: o aplicativo e as várias dependências instaladas. O Tarantool também chegará ao servidor como uma dependência de pacote RPM, e nosso serviço está pronto para o lançamento. Isso é feito através do systemd, mas primeiro você precisa escrever um pouco de configuração. No mínimo, especifique o URI de cada processo. Três, por exemplo, é suficiente.
$ 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 o endereço público de todo o processo, incluindo o nome do host. Isso é necessário para que os nós do cluster saibam como se conectar. É uma má idéia usar o endereço 0.0.0.0 como advertise_uri; ele deve ser um endereço IP externo, não um soquete de ligação. Sem ele, nada funcionará; portanto, o Cartucho simplesmente não permitirá que o nó com o advertise_uri errado seja iniciado.
Agora que a configuração está pronta, você pode iniciar os processos. Como uma unidade systemd comum não permite iniciar mais de um processo, os aplicativos no Cartucho instalam o chamado 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 da web - 8080. Vamos examiná-la e ver:

Vemos que os processos, embora estejam em execução, ainda não estão configurados. O cartucho ainda não sabe quem deve se replicar com quem e não pode decidir por conta própria, por isso aguarda nossa ação. E nossa escolha não é grande: a vida de um novo cluster começa com a configuração do primeiro nó. Em seguida, adicionamos o restante ao cluster, atribuímos funções a eles e, nessa implantação, podemos ser considerados concluídos com êxito.
Despeje um copo da sua bebida favorita e relaxe após uma longa semana de trabalho. O aplicativo pode ser explorado.

Sumário
E quais são os resultados? Experimente, use, deixe comentários, inicie 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 investimentos do Alfa-Bank baseado no Tarantool[3]
Arquitetura de cobrança de última geração: transição para Tarantool[4]
SWIM - protocolo de construção de cluster[5]
GitHub - tarantool / cartucho-cli[6]
GitHub - tarantool / cartucho