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