
Há uma semana, falei em uma reunião do Node.JS e prometi a muitos que publicassem uma gravação da apresentação. Mais tarde, percebi que não era capaz de acomodar alguns fatos interessantes em meia hora regulamentada. Sim, e eu mesmo prefiro ler, em vez de assistir e ouvir, então decidi postar minha apresentação no formato de um artigo. No entanto, o vídeo também estará no final da postagem na seção de links.
Decidi falar sobre um ponto delicado - a vida em um monólito. Já existem centenas de artigos sobre o assunto no hub, milhares de cópias estão quebradas nos comentários, a verdade há muito morre na controvérsia, mas ... O fato é que temos uma experiência muito específica no OneTwoTrip, ao contrário de muitas pessoas que escrevem sobre certos padrões arquiteturais no vácuo:
- Em primeiro lugar, nosso monólito já tem 9 anos.
- Em segundo lugar, ele passou a vida inteira sob carga pesada (agora são 23 milhões de solicitações por hora).
- E no NaN, escrevemos nosso monólito no Node.JS, que mudou além do reconhecimento nos últimos 9 anos. Sim, começamos a escrever sobre o nó em 2010, cantamos uma música para o frenesi dos bravos!
Portanto, temos muita especificidade e experiência real. Interessante? Vamos lá!
Tempos de isenção
Esta apresentação reflete apenas a opinião privada de seu autor. Pode coincidir com a posição do OneTwoTrip ou pode não coincidir. Isso é que sorte. Trabalho como especialista técnico de uma das equipes da empresa e não pretendo ser objetivo ou expressar a opinião de alguém que não seja a minha.
Isenção de responsabilidade dois
Este artigo descreve eventos históricos e, no momento, tudo está completamente errado, portanto, não se assuste.
0. Como isso aconteceu
A tendência da consulta da palavra "microsserviço" no google:

Tudo é muito simples - há nove anos, ninguém sabia sobre microsserviços. Então começamos a escrever, como todo mundo - em um monólito.
1. Dor no monólito
Aqui vou descrever as situações problemáticas que tivemos durante esses 9 anos. Alguns deles foram resolvidos, outros foram contornados por hacks, alguns simplesmente perderam a relevância. Mas a lembrança deles, como cicatrizes de batalha, nunca vai me deixar.
1.1 Atualizando componentes conectados

Este é o caso quando a sinergia é má. Como qualquer componente foi reutilizado várias centenas de vezes e, se foi possível usá-lo de forma torta, não foi perdido. Qualquer ação pode causar efeitos completamente imprevisíveis, e nem todos são rastreados por unidades e testes de integração. Lembre-se da história sobre esfregões, um fã e um balão? Caso contrário, pesquise no Google. Ela é a melhor ilustração do código no monólito.
1.2 Migração para novas tecnologias
Você quer Express? Linter? Outra estrutura para testes ou moks? Atualizar validador ou pelo menos lodash? Atualizar o Node.js? Me desculpe Para fazer isso, você precisa editar milhares de linhas de código.
Muitos falam sobre a vantagem do monólito, que é que qualquer revisão é um commit atômico . Essas pessoas ficam caladas sobre uma coisa - essa revisão nunca será feita .
Você conhece a velha piada sobre controle de versão semântico?
a semântica real do versionamento semântico:
major = uma mudança de ruptura
menor = uma pequena mudança de quebra
patch = uma mudança de última hora
Agora imagine que qualquer alteração minúscula que quase certamente aparecerá no seu código. Não, é possível viver com isso, e periodicamente reunimos forças e migramos, mas foi realmente muito difícil. Muito.
1.3 Lançamentos
Aqui devo dizer sobre algumas especificidades do nosso produto. Temos um grande número de integrações externas e vários ramos da lógica de negócios que surgem separadamente muito raramente. Eu realmente invejo os produtos que realmente executam todas as ramificações de seu código em 10 minutos de produção, mas esse não é o caso aqui. Por tentativa e erro, descobrimos para nós um ciclo ideal de lançamento que minimizava o número de erros que atingem os usuários finais:
- o lançamento está indo e os testes de integração passam em meio dia
- depois, sob cuidadosa supervisão no palco por um dia (para 10% dos usuários)
- depois fica outro dia na produção sob supervisão ainda mais cuidadosa.
- E somente depois disso, damos a ele a luz verde no mestre.
Como amamos nossos colegas e não fazemos o lançamento às sextas-feiras, no final, isso significa que o lançamento vai para o mestre cerca de 1,5 a 2 vezes por semana. O que leva ao fato de que o lançamento pode ter 60 tarefas ou mais. essa quantidade causa conflitos de mesclagem, efeitos sinérgicos repentinos, carga total de controle de qualidade na análise de logs e outras tristezas. Em geral, foi muito difícil lançar um monólito.
1.4 Apenas muito código
Parece que a quantidade de código não deve ser de importância fundamental. Mas ... na verdade não. No mundo real é:
- Limiar de entrada mais alto
- Artefatos de construção enormes para cada tarefa
- Processos longos de IC, incluindo testes de integração, testes de unidade e até mesmo código
- Trabalho lento do IDE (no início do desenvolvimento do Jetbrains, nós os chocamos com nossos logs mais de uma vez)
- Pesquisa contextual sofisticada (não se esqueça, não temos digitação estática)
- Dificuldade em localizar e remover código não utilizado
1.5 Proprietários de código ausentes
Muitas vezes, as tarefas surgem com uma esfera de responsabilidade incompreensível - por exemplo, em bibliotecas relacionadas. E o desenvolvedor original já pode se mudar para outra equipe ou até sair da empresa. A única maneira de se responsabilizar nesse caso é a arbitrariedade administrativa - levar e nomear uma pessoa. O que nem sempre é agradável tanto para o desenvolvedor quanto para quem o faz.
1.6 Dificuldade de depuração
A memória está fluindo? Maior consumo de CPU? Queria criar gráficos de chama? Me desculpe Em um monólito, tantas coisas acontecem ao mesmo tempo que se torna insanamente difícil localizar um problema. Por exemplo, para entender qual das 60 tarefas ao iniciar a produção causa um aumento no consumo de recursos (embora isso não seja reproduzido localmente nos ambientes de teste e preparação), é quase irreal.
1.7 Uma pilha
Por um lado, é bom quando todos os desenvolvedores "falam" o mesmo idioma. No caso do JS, verifica-se que mesmo os desenvolvedores do Backend com Frontend se entendem. Mas ...
- Não existe uma bala de prata e, para algumas tarefas, às vezes você deseja usar outra coisa. Mas temos um monólito e não temos onde ficar com outros desenvolvedores.
- Não podemos simplesmente tomar uma boa equipe com a recomendação, que nos chegou a conselho de amigos - não temos onde colocar.
- Com o tempo, nos baseamos no fato de que o mercado simplesmente não possui desenvolvedores suficientes na pilha certa.
1.8 Muitas equipes com idéias diferentes sobre felicidade

Se você tem dois desenvolvedores, já tem duas idéias diferentes sobre qual é a melhor estrutura, quais padrões devem ser respeitados, usar bibliotecas e assim por diante.
Se você possui dez equipes, cada uma com vários desenvolvedores, isso é apenas um desastre.
E há apenas duas maneiras de resolvê-lo - seja "democrático" (todo mundo faz o que quer) ou totalitário (os padrões são impostos de cima). No primeiro caso, qualidade e padronização sofrem, no segundo - pessoas que não têm permissão para realizar sua idéia de felicidade.
2. As vantagens do monólito
Obviamente, existem algumas vantagens no monólito que podem ser diferentes para diferentes pilhas, produtos e equipes. É claro que existem muito mais que três, mas não serei responsável por tudo que for possível, apenas por aqueles que foram relevantes para nós.
2.1 Fácil de implantar
Quando você tem um serviço, aumentar e testar é muito mais fácil do que uma dúzia de serviços. É verdade que esse plus é relevante apenas no estágio inicial - por exemplo, você pode elevar o ambiente de teste e usar todos os serviços, exceto os desenvolvidos. Ou de recipientes. Ou o que mais.
2.2 Sem sobrecarga de transferência de dados
Uma vantagem bastante duvidosa, se você não tiver carga alta. Mas temos exatamente esse caso - portanto, o custo do transporte entre microsserviços é perceptível para nós. Não importa como você tente fazer isso rapidamente, armazene e transfira tudo na RAM mais rapidamente do que qualquer outra coisa - isso é óbvio.
2.2 Um conjunto
Se você precisar retroceder em algum momento da história, é muito simples fazê-lo com um monólito - pegue e retroceda. No caso de microsserviços, é necessário selecionar versões compatíveis de serviços que foram usadas entre si em um determinado momento, o que nem sempre pode ser simples. É verdade que isso também é resolvido com a ajuda da infraestrutura.
3. Vantagens imaginárias de um monólito
Aqui eu peguei todas as coisas que geralmente são consideradas vantagens, mas do meu ponto de vista elas não são.
3.1 Código - esta é a documentação
Muitas vezes ouvi essa opinião. Mas geralmente é seguido por desenvolvedores iniciantes que não viram arquivos em dezenas de milhares de linhas de código escritas por pessoas que saíram anos atrás. Bem, por algum motivo, na maioria das vezes esse item é adicionado a favor dos apoiadores do monólito - eles dizem que não precisamos de documentação, não temos transporte ou API - tudo está no código, é fácil e claro. Não discutirei com esta afirmação, apenas digo que não acredito nela.
3.2 Não há versões diferentes de bibliotecas, serviços e APIs. Não há repositórios diferentes.
Sim Mas não. Porque, à segunda vista, você entende que o serviço não existe no vácuo. E entre um grande número de outros códigos e produtos com os quais se integra - a partir de bibliotecas de terceiros, continuando com versões de software para servidor e não terminando com integrações externas, uma versão IDE, ferramentas de CI e assim por diante. E assim que você entender quantas coisas diferentes de versão seu serviço inclui indiretamente, fica imediatamente claro que esse plus é apenas demagogia.
3.3 monitoramento mais fácil
Mais fácil. Porque você tem aproximadamente um painel, em vez de algumas dezenas. Mas é mais complicado, e às vezes até impossível, porque você não pode decompor seus gráficos em diferentes partes do código, e você só tem a temperatura média no hospital. Em geral, eu já disse tudo no parágrafo sobre a complexidade da depuração, apenas esclarecerei que a mesma complexidade se aplica ao monitoramento.
3.4 É mais fácil cumprir padrões comuns
Sim Mas, como já escrevi no parágrafo, sobre muitas equipes com a ideia de felicidade - os padrões são impostos totalitariamente ou enfraquecidos quase à ausência deles.
3.5 Menos chance de duplicação de código
A opinião estranha é que o código não é duplicado no monólito. Mas o conhecia com bastante frequência. Na minha prática, verifica-se que a duplicação de código depende unicamente da cultura de desenvolvimento na empresa. Se for, o código geral é alocado para todos os tipos de bibliotecas, módulos e microsserviços. Se não estiver lá, haverá uma cópia-pasta vinte vezes no monólito.
4. Profissionais de microsserviços
Agora vou escrever sobre o que conseguimos após a migração. Novamente, essas são conclusões reais de uma situação real.
4.1 Você pode criar uma infraestrutura heterogênea
Agora, podemos escrever código em uma pilha ideal para resolver um problema específico. E é racional usar bons desenvolvedores que vieram até nós. Por exemplo, aqui está uma lista de exemplo de tecnologias que temos atualmente:

4.2 Você pode fazer muitos lançamentos frequentes
Agora podemos fazer vários lançamentos independentes pequenos, e eles são mais simples, rápidos e não dolorosos. No passado, tínhamos apenas um time, mas agora já existem 18 anos. Teria havido um intervalo se todos permanecessem no monólito. Ou as pessoas responsáveis por isso ...
4.3 Mais fácil de realizar testes independentes
Reduzimos o tempo para testes de integração, que agora testam apenas o que realmente mudou e, ao mesmo tempo, não temos medo dos efeitos de uma sinergia repentina. Obviamente, tive que começar a andar pelo rake - por exemplo, aprendendo a criar APIs compatíveis com versões anteriores - mas, com o tempo, tudo se acalmou.
4.4 Mais fácil de implementar e testar novos recursos
Agora estamos abertos à experimentação. Quaisquer estruturas, pilhas, bibliotecas - você pode tentar de tudo e, se for bem-sucedido, seguir em frente.
4.5 Você pode atualizar qualquer coisa
Você pode atualizar a versão do mecanismo, bibliotecas, mas qualquer coisa! Como parte de um pequeno serviço, encontrar e consertar todas as alterações de última hora é uma questão de minutos. E não semanas, como era antes.
4.6 E você não pode atualizar
Curiosamente, esse é um dos recursos mais interessantes dos microsserviços. Se você tem um código de trabalho estável, basta congelá-lo e esquecê-lo. E você nunca precisará atualizá-lo, por exemplo, para executar o código do produto em um novo mecanismo. O produto em si funciona em um novo mecanismo e o microsserviço continua a viver como ele viveu. As moscas com costeletas podem finalmente ser consumidas separadamente.
5 Contras de microsserviços
Obviamente, uma mosca na pomada não estava completa e uma solução perfeita para apenas sentar e receber o pagamento não funcionou. O que encontramos:
5.1 Precisa de um barramento para troca de dados e registro limpo.
A interação de serviços através de HTTP é um modelo clássico e, em geral, mesmo funcional, desde que existam camadas de log e balanceamento entre eles. Mas é melhor ter um pneu mais distinto. Além disso, você deve pensar em como coletar e combinar logs entre si - caso contrário, terá apenas mingau em suas mãos.
5.2 Acompanhe o que os desenvolvedores estão fazendo.
Em geral, isso sempre deve ser feito, mas em microsserviços, os desenvolvedores obviamente têm mais liberdade, o que às vezes pode dar origem a coisas das quais Stephen King teria arrepios. Mesmo que externamente pareça que o serviço esteja funcionando - não esqueça que deve haver uma pessoa que monitora o que está dentro dele.
5.3 Você precisa de uma boa equipe de DevOps para gerenciar tudo.
Quase qualquer desenvolvedor pode implantar um monólito de uma maneira ou de outra e fazer upload de seus lançamentos (por exemplo, via FTP ou SSH, eu vi isso). Mas com microsserviços, existem todos os tipos de serviços centralizados para coletar logs, métricas, painéis, chefs para gerenciar configurações, volts, jenkins e outras coisas boas, sem as quais você geralmente pode viver - mas é ruim e incompreensível o porquê. Portanto, para gerenciar microsserviços, você precisa de uma boa equipe de DevOps.
5.4 Você pode tentar pegar um hype e dar um tiro no próprio pé.
Talvez este seja o principal sinal negativo da arquitetura e seu perigo. Muitas vezes, as pessoas seguem cegamente as tendências e começam a introduzir arquitetura e tecnologia sem entendê-las. Depois que tudo cai, eles ficam confusos na bagunça resultante e escrevem um artigo no Habr "como passamos de microsserviços para um monólito", por exemplo. Em geral, mude apenas se você souber por que está fazendo isso e quais problemas resolverá. E quais você recebe.
6 caqui em monólito
Alguns dos hacks que nos permitiram viver em um monólito são um pouco melhores e um pouco mais.
6.1 Linting
A introdução de um linter em um monólito não é uma tarefa tão simples como parece à primeira vista. Claro, você pode fazer regras estritas, adicionar uma configuração e ... Nada mudará, todo mundo simplesmente desliga o linter, porque metade do código fica vermelho.
Para a introdução gradual do linting, escrevemos um complemento simples para o eslint - slowlint , que permite fazer uma coisa simples - para conter uma lista de arquivos ignorados temporariamente. Como resultado:
- Todo o código incorreto é destacado no IDE
- Novos arquivos são criados de acordo com as regras do linting, caso contrário, o CI não sentirá falta deles
- Os idosos gradualmente governam e se afastam das exceções
Ao longo do ano, foi possível incorporar metade do código do monólito em um único estilo, ou seja, quase todos os códigos adicionados ativamente.
6.2 Melhorias no teste de unidade
Uma vez que os testes de unidade foram realizados conosco por três minutos. Os desenvolvedores não queriam esperar tanto tempo, então tudo foi verificado apenas no IC no servidor. Depois de algum tempo, o desenvolvedor descobriu que os testes caíam, amaldiçoavam, abriam um ramo, retornavam ao código ... Em geral, ele sofreu. O que fizemos com isso:
- Para iniciantes, começamos a executar testes multithread. O Yandex tem uma variante do mocha multiencadeado, mas conosco não decolou, então eles mesmos escreveram um invólucro simples. Os testes começaram a ser realizados uma vez e meia mais rápido.
- Em seguida, passamos de 0,12 nós para o 8º (sim, o próprio processo atrai para um relatório separado). Por mais estranho que possa parecer, não deu um ganho fundamental em produtividade na produção, mas os testes começaram a ser realizados 20% mais rápido.
- E então nos sentamos para depurar os testes e otimizá-los individualmente. O que deu o maior aumento de velocidade.
Em geral, no momento, os testes de unidade são executados no gancho de preparação e são executados em 10 segundos, o que é bastante confortável e permite que você os execute no trabalho.
6.3 Diminuindo o peso do artefato
O artefato monolítico acabou ocupando 400 megabytes. Considerando o fato de que ele é criado para cada confirmação, o volume total acabou sendo bastante grande. O módulo de diarréia , um garfo do módulo modclean , nos ajudou com isso. Removemos os testes de unidade do artefato e limpamos vários tipos de lixo, como arquivos leia-me, testes dentro de pacotes e assim por diante. O ganho foi de cerca de 30% do peso!
6.4 Cache de Dependência
Antigamente, a instalação de dependências usando o npm levava tanto tempo que você não só podia tomar café, mas também, por exemplo, assar pizza. Portanto, no início, usamos o módulo npm-cache , que foi bifurcado e um pouco terminado. Ele permitiu armazenar dependências em uma unidade de rede compartilhada, da qual todas as outras compilações a usariam.
Depois pensamos na reprodutibilidade das montagens. Quando você tem um monólito, a mudança de dependências transitivas é o flagelo de Deus. Dado o fato de estarmos muito atrasados na versão do mecanismo na época, a alteração de alguma dependência do quinto nível quebrou facilmente toda a nossa montagem. Então começamos a usar o npm-shrinkwrap. Já era mais fácil para ele, embora mesclar suas mudanças fosse um prazer para os fortes de espírito.
E então o package-lock e o excelente comando npm ci
finalmente apareceram - que rodavam a uma velocidade ligeiramente menor que a instalação de dependências do cache de arquivos. Portanto, começamos a usá-lo apenas e paramos de armazenar assemblies de dependência. Nesse dia, trouxe para o trabalho várias caixas de donuts.
6.5 Distribuição da ordem dos releases.
E isso é mais um truque administrativo, não técnico. Inicialmente, eu era contra ele, mas o tempo mostrou que o segundo especialista técnico estava certo e geralmente bem-feito. Quando as versões foram distribuídas entre várias equipes, ficou claro onde exatamente os erros apareciam e, mais importante, cada equipe sentia sua responsabilidade pela velocidade e tentava resolver problemas e implementar o mais rápido possível.
6.6 Excluindo código morto
Em um monólito, é muito assustador excluir o código - você nunca sabe onde pode ficar preso nele. Portanto, na maioria das vezes, resta apenas ficar de lado. Ao longo dos anos. E mesmo o código morto precisa ser suportado, sem mencionar a confusão que ele introduz. Portanto, com o tempo, começamos a usar a análise de requisitos para uma pesquisa superficial de código morto e testes de integração, além de iniciar no modo de verificação de cobertura para uma pesquisa mais profunda.
7 Corte de monólito
Por alguma razão, muitas pessoas pensam que, para mudar para microsserviços, você precisa abandonar seu monólito, escrever vários microsserviços perto do zero, executá-lo de uma só vez - e haverá felicidade. Mas esse modelo ... Hmm ... É repleto de fato de você não fazer nada, e apenas gastar muito tempo e dinheiro escrevendo códigos que precisa jogar fora.
Proponho outra opção, que me parece mais funcional e que foi implementada conosco:
- Começamos a escrever novos serviços em microsserviços. Executamos a tecnologia, saltamos para o rake, entendemos se queremos fazê-lo.
- Extraímos o código em módulos, bibliotecas ou o que for usado lá.
- Distinguimos serviços de um monólito.
- Distinguimos microsserviços de serviços. Sem pressa e um de cada vez.
8 e finalmente

No final, decidi deixar a coisa mais importante.
Lembre-se:
- Você não é google
- Você não é microsoft
- Você não é facebook
- Você não é Yandex
- Você não é netflix
- Você não é OneTwoTrip
Se algo funcionar em outras empresas, não é absolutamente um fato que o beneficie. Se você tentar copiar cegamente a experiência de outras empresas com as palavras "funciona para elas", isso provavelmente terminará mal. Toda empresa, todo produto e toda equipe são únicos. O que funciona para alguns não funciona para outros. Não gosto de dizer coisas óbvias, mas muitas pessoas começam a criar um culto à carga em torno de outras empresas, copiando cegamente abordagens e enterrando-se sob as falsas decorações da árvore de Natal. Não faça isso. Experimente, experimente, desenvolva as soluções ideais para você. E só então tudo vai dar certo.
Links úteis: