JavaRush /Blogue Java /Random-PT /Um guia para microsserviços Java. Parte 3: questões gerai...

Um guia para microsserviços Java. Parte 3: questões gerais

Publicado no grupo Random-PT
Tradução e adaptação de microsserviços Java: um guia prático . Partes anteriores do guia: Vejamos os problemas inerentes aos microsserviços em Java, começando com coisas abstratas e terminando com bibliotecas concretas. Um guia para microsserviços Java.  Parte 3: questões gerais - 1

Como tornar um microsserviço Java resiliente?

Lembre-se de que, ao criar microsserviços, você está essencialmente trocando chamadas de método JVM por chamadas HTTP síncronas ou mensagens assíncronas. Embora seja garantido que uma chamada de método seja concluída (exceto um desligamento inesperado da JVM), uma chamada de rede não é confiável por padrão. Pode funcionar, mas pode não funcionar por vários motivos: a rede está sobrecarregada, uma nova regra de firewall foi implementada e assim por diante. Para ver como isso faz diferença, vamos dar uma olhada no exemplo do BillingService.

Padrões de resiliência HTTP/REST

Digamos que os clientes possam comprar e-books no site da sua empresa. Para fazer isso, você acabou de implementar um microsserviço de cobrança que pode ligar para sua loja online para gerar faturas reais em PDF. Por enquanto, faremos essa chamada de forma síncrona, via HTTP (embora faça mais sentido chamar esse serviço de forma assíncrona, já que a geração do PDF não precisa ser instantânea da perspectiva do usuário. Usaremos o mesmo exemplo no próximo seção e observe as diferenças).
@Service
class BillingService {

    @Autowired
    private HttpClient client;

     public void bill(User user, Plan plan) {
        Invoice invoice = createInvoice(user, plan);
        httpClient.send(invoiceRequest(user.getEmail(), invoice), responseHandler());
        // ...
    }
}
Para resumir, aqui estão três resultados possíveis desta chamada HTTP.
  • OK: a chamada foi completada, a conta foi criada com sucesso.
  • ATRASO: A chamada foi completada, mas demorou muito para ser concluída.
  • ERRO. A chamada falhou, você pode ter enviado uma solicitação incompatível ou o sistema pode não estar funcionando.
Espera-se que qualquer programa lide com situações de erro, não apenas com sucesso. O mesmo se aplica aos microsserviços. Mesmo que você precise fazer um esforço extra para garantir que todas as versões implantadas da API sejam compatíveis quando você iniciar as implantações e lançamentos de microsserviços individuais. Um guia para microsserviços Java.  Parte 3: questões gerais - 2Um caso interessante de se observar é o caso do atraso. Por exemplo, o disco rígido de microsserviço do respondente está cheio e, em vez de levar 50 ms, leva 10 segundos para responder. Fica ainda mais interessante quando você experimenta uma certa carga que faz com que a falta de resposta do seu BillingService comece a se espalhar pelo seu sistema. Como exemplo ilustrativo, imagine uma cozinha iniciando lentamente um “bloco” de todos os garçons do restaurante. Esta seção obviamente não pode fornecer uma visão geral exaustiva do tópico de resiliência de microsserviços, mas serve como um lembrete aos desenvolvedores de que isso é realmente algo que precisa ser abordado e não ignorado antes do seu primeiro lançamento (o que, por experiência própria, acontece com mais frequência do que não). segue). Uma biblioteca popular que ajuda você a pensar sobre latência e tolerância a falhas é a Hystrix da Netflix. Use sua documentação para se aprofundar no tópico.

Padrões de resiliência de mensagens

Vamos dar uma olhada mais de perto na comunicação assíncrona. Nosso programa BillingService agora pode ser parecido com isto, supondo que estamos usando Spring e RabbitMQ para mensagens. Para criar uma conta, enviamos agora uma mensagem para nosso corretor de mensagens RabbitMQ, onde existem vários trabalhadores aguardando novas mensagens. Esses funcionários criam faturas em PDF e as enviam aos usuários apropriados.
@Service
class BillingService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

     public void bill(User user, Plan plan) {
        Invoice invoice = createInvoice(user, plan);
        // преобразует счет, например, в json и использует его How тело messages
        rabbitTemplate.convertAndSend(exchange, routingkey, invoice);
        // ...
    }
}
Os erros potenciais parecem um pouco diferentes agora, pois você não recebe mais respostas imediatas de OK ou ERRO, como acontecia com uma conexão HTTP síncrona. Em vez disso, podemos ter três cenários potenciais que podem dar errado, o que poderia levantar as seguintes questões:
  1. Minha mensagem foi entregue e usada pelo funcionário? Ou está perdido? (O usuário não recebe fatura).
  2. Minha mensagem foi entregue apenas uma vez? Ou entregue mais de uma vez e processado apenas uma vez? (O usuário receberá várias faturas).
  3. Configuração: De "Usei as chaves/nomes de roteamento corretos para a troca" até "Meu agente de mensagens está configurado e mantido corretamente ou suas filas estão cheias?" (O usuário não recebe fatura).
Uma descrição detalhada de cada padrão individual de resiliência de microsserviços assíncronos está além do escopo deste guia. No entanto, existem sinais na direção certa. Além disso, dependerão da tecnologia de mensagens. Exemplos:
  • Se você estiver usando implementações JMS como ActiveMQ, poderá trocar a velocidade pela garantia de confirmações de duas fases (XA).
  • Se você estiver usando RabbitMQ, leia este tutorial primeiro e depois pense cuidadosamente sobre confirmações, tolerância a falhas e confiabilidade de mensagens em geral.
  • Talvez alguém seja versado em configurar servidores Active ou RabbitMQ, especialmente em combinação com clustering e Docker (alguém? ;))

Qual framework seria a melhor solução para microsserviços Java?

Por um lado, você pode instalar uma opção muito popular como Spring Boot . Torna muito fácil criar arquivos .jar, vem com um servidor web integrado como Tomcat ou Jetty e pode ser executado rapidamente e em qualquer lugar. Ideal para criar aplicativos de microsserviços. Recentemente, surgiram duas estruturas especializadas de microsserviços, Kubernetes ou GraalVM , parcialmente inspiradas na programação reativa. Aqui estão mais alguns concorrentes interessantes: Quarkus , Micronaut , Vert.x , Helidon . No final, você terá que escolher por si mesmo, mas podemos dar algumas recomendações que podem não ser completamente padrão: Com exceção do Spring Boot, todas as estruturas de microsserviços são geralmente comercializadas como incrivelmente rápidas, com inicialização quase instantânea , baixo consumo de memória e capacidade de expansão até o infinito. Os materiais de marketing normalmente apresentam gráficos impressionantes que mostram a plataforma ao lado do gigante Spring Boot ou entre si. Isso, em teoria, poupa os nervos dos desenvolvedores que apoiam projetos legados, que às vezes levam vários minutos para carregar. Ou desenvolvedores que trabalham na nuvem e desejam iniciar/parar quantos microcontêineres forem necessários atualmente em 50 ms. Um guia para microsserviços Java.  Parte 3: questões gerais - 3O problema, contudo, é que estes tempos (artificiais) de arranque bare metal e tempos de redistribuição dificilmente contribuem para o sucesso global do projecto. Pelo menos eles influenciam muito menos do que uma forte infraestrutura de framework, forte documentação, comunidade e fortes habilidades de desenvolvedor. Portanto, é melhor ver desta forma: Se até agora:
  • Você deixa seus ORMs correrem soltos, gerando centenas de consultas para fluxos de trabalho simples.
  • Você precisa de gigabytes infinitos para executar seu monólito moderadamente complexo.
  • Você tem tanto código e a complexidade é tão alta (não estamos falando de inicializações potencialmente lentas como o Hibernate agora) que seu aplicativo leva vários minutos para carregar.
Se for esse o caso, adicionar problemas adicionais de micosserviços (resiliência de rede, mensagens, DevOps, infraestrutura) terá um impacto muito maior em seu projeto do que carregar um Olá mundo vazio. E para reimplantações a quente durante o desenvolvimento, você pode acabar com soluções como JRebel ou DCEVM . Vamos citar Simon Brown novamente : “ Se as pessoas não conseguirem construir monólitos (rápidos e eficientes), elas terão dificuldade em construir microsserviços (rápidos e eficientes), independentemente da estrutura .” Portanto, escolha suas estruturas com sabedoria.

Quais bibliotecas são melhores para chamadas Java REST síncronas?

No lado técnico de baixo nível, você provavelmente acabará com uma das seguintes bibliotecas de cliente HTTP: HttpClient nativo do Java (desde Java 11), HttpClient do Apache ou OkHttp . Observe que digo "provavelmente" aqui porque existem outras opções, que vão desde os bons e antigos clientes JAX-RS até os modernos clientes WebSocket . De qualquer forma, a tendência é gerar um cliente HTTP, deixando de mexer você mesmo com chamadas HTTP. Para fazer isso, você precisa dar uma olhada no projeto OpenFeign e sua documentação como ponto de partida para leituras adicionais.

Quais são os melhores corretores para mensagens Java assíncronas?

Muito provavelmente você encontrará os populares ActiveMQ (Classic ou Artemis) , RabbitMQ ou Kafka .
  • ActiveMQ e RabbitMQ são corretores de mensagens tradicionais e completos. Eles envolvem a interação de um “corretor inteligente” e “usuários estúpidos”.
  • Historicamente, o ActiveMQ teve o benefício de fácil inlining (para teste), que pode ser mitigado com configurações RabbitMQ/Docker/TestContainer.
  • Kafka não pode ser chamado de corretor tradicional “inteligente”. Em vez disso, é um armazenamento de mensagens (arquivo de log) relativamente "burro" que requer processamento por consumidores inteligentes.
Para entender melhor quando usar RabbitMQ (ou outros corretores de mensagens tradicionais em geral) ou Kafka, dê uma olhada neste post Pivotal como ponto de partida. Em geral, ao escolher um corretor de mensagens, tente ignorar motivos artificiais de desempenho. Houve um tempo em que as equipes e comunidades online discutiam constantemente sobre o quão rápido o RabbitMQ era e o quão lento o ActiveMQ era. Agora os mesmos argumentos são apresentados em relação ao RabbitMQ, dizem que funciona lentamente com 20 a 30 mil mensagens por segundo. Kafka registra 100 mil mensagens por segundo. Falando francamente, essas comparações são como comparar o quente com o suave. Além disso, em ambos os casos, os valores de rendimento podem estar na faixa baixa a média, digamos, do Grupo Alibaba. No entanto, é improvável que você tenha encontrado projetos desta escala (milhões de mensagens por minuto) na realidade. Eles definitivamente existem e teriam problemas. Ao contrário dos outros 99% dos projetos de negócios Java “regulares”. Portanto, não preste atenção à moda e ao hype. Escolha sabiamente.

Quais bibliotecas posso usar para testar microsserviços?

Depende da sua pilha. Se você tiver um ecossistema Spring implantado, seria aconselhável usar as ferramentas específicas do framework . Se JavaEE for algo como Arquillian . Talvez valha a pena dar uma olhada no Docker e na excelente biblioteca Testcontainers , que ajuda, em particular, a configurar de maneira fácil e rápida um banco de dados Oracle para desenvolvimento local ou testes de integração. Para testes simulados de servidores HTTP inteiros, confira Wiremock . Para testar mensagens assíncronas, tente implementar ActiveMQ ou RabbitMQ e depois escrever testes usando Awaitility DSL . Além disso, todas as suas ferramentas usuais são usadas - Junit , TestNG para AssertJ e Mockito . Observe que esta não é uma lista completa. Se você não encontrar sua ferramenta favorita aqui, poste-a na seção de comentários.

Como habilitar o log para todos os microsserviços Java?

O registro no caso de microsserviços é um tópico interessante e bastante complexo. Em vez de ter um arquivo de log que pode ser manipulado com comandos less ou grep, agora você tem n arquivos de log e deseja que eles não fiquem muito dispersos. As características do ecossistema madeireiro estão bem descritas neste artigo (em inglês). Certifique-se de lê-lo e preste atenção à seção Registro centralizado da perspectiva de microsserviços . Na prática, você encontrará diferentes abordagens: O administrador do sistema escreve certos scripts que coletam e mesclam arquivos de log de diferentes servidores em um único arquivo de log e os colocam em servidores FTP para download. Executando combinações cat/grep/unig/sort em sessões SSH paralelas. Isso é exatamente o que o Amazon AWS faz, e você pode informar seu gerente. Use uma ferramenta como Graylog ou ELK Stack (Elasticsearch, Logstash, Kibana)

Como meus microsserviços se encontram?

Até agora, assumimos que nossos microsserviços se conhecem e conhecem o IPS correspondente. Vamos falar sobre configuração estática. Portanto, nosso monólito bancário [ip = 192.168.200.1] sabe que precisa se comunicar com o servidor de risco [ip = 192.168.200.2], que está codificado no arquivo de propriedades. No entanto, você pode tornar as coisas mais dinâmicas:
  • Use um servidor de configuração baseado em nuvem do qual todos os microsserviços extraem suas configurações em vez de implantar arquivos application.properties em seus microsserviços.
  • Como suas instâncias de serviço podem alterar dinamicamente sua localização, vale a pena examinar serviços que saibam onde seus serviços residem, quais são seus IPs e como roteá-los.
  • Agora que tudo é dinâmico, surgem novos problemas, como a eleição automática de um líder: quem é o mestre que trabalha em determinadas tarefas, para não processá-las duas vezes, por exemplo? Quem substitui um líder quando ele falha? Com que base ocorre a substituição?
Em termos gerais, isso é chamado de orquestração de microsserviços e é outro tópico sem fundo. Bibliotecas como Eureka ou Zookeeper tentam “resolver” estes problemas mostrando quais serviços estão disponíveis. Por outro lado, introduzem complexidade adicional. Pergunte a qualquer pessoa que já instalou o ZooKeeper.

Como organizar autorização e autenticação usando microsserviços Java?

Este tópico também merece uma história separada. Novamente, as opções variam desde autenticação HTTPS básica codificada com estruturas de segurança personalizadas até a execução de uma instalação Oauth2 com seu próprio servidor de autorização.

Como posso ter certeza de que todos os meus ambientes têm a mesma aparência?

O que é verdade para implantações sem microsserviço também é verdade para implantações com um. Experimente uma combinação de Docker/Testcontainers e Scripting/Ansible.

Sem dúvida: brevemente sobre YAML

Vamos nos afastar das bibliotecas e dos problemas relacionados por um momento e dar uma olhada rápida no Yaml. Este formato de arquivo é usado de fato como um formato para “escrever configuração como código”. Também é usado por ferramentas simples como Ansible e gigantes como Kubernetes. Para experimentar a dor do recuo YAML, tente escrever um arquivo Ansible simples e veja quanto você precisa editar o arquivo antes que ele funcione conforme o esperado. E isso apesar do formato ser suportado por todos os principais IDEs! Depois disso, volte para terminar de ler este guia.
Yaml:
  - is:
    - so
    - great

E quanto às transações distribuídas? Teste de performance? Outros tópicos?

Talvez algum dia, em futuras edições do manual. Por enquanto, isso é tudo. Fique conosco!

Problemas conceituais com microsserviços

Além dos problemas específicos de microsserviços em Java, existem outros problemas, digamos, que aparecem em qualquer projeto de microsserviços. Eles se relacionam principalmente com organização, equipe e gestão.

Incompatibilidade de front-end e back-end

A incompatibilidade de front-end e back-end é um problema muito comum em muitos projetos de microsserviços. O que isso significa? Só que nos bons e velhos monólitos, os desenvolvedores de interfaces web tinham uma fonte específica para obtenção de dados. Em projetos de microsserviços, os desenvolvedores front-end repentinamente têm n fontes das quais obter dados. Imagine que você está criando algum tipo de projeto de microsserviços IoT (Internet of Things) em Java. Digamos que gere máquinas geodésicas e fornos industriais em toda a Europa. E esses fornos enviam atualizações regulares com suas temperaturas e similares. Mais cedo ou mais tarde, você poderá querer encontrar fornos na interface do administrador, talvez usando microsserviços de "pesquisa de forno". Dependendo de quão estritamente suas contrapartes de back-end aplicam leis de design orientado a domínio ou microsserviços, o microsserviço “encontrar forno” pode retornar apenas IDs de forno e não outros dados, como tipo, modelo ou localização. Para fazer isso, os desenvolvedores front-end precisarão fazer uma ou n chamadas adicionais (dependendo da implementação de paginação) no microsserviço “obter dados do forno” com os IDs que receberam do primeiro microsserviço. Um guia para microsserviços Java.  Parte 3: questões gerais - 4E embora este seja apenas um exemplo simples, ainda que retirado de um projeto real (!), ainda demonstra o seguinte problema: os supermercados tornaram-se extremamente populares. Isso porque com eles você não precisa ir a 10 lugares diferentes para comprar verduras, limonada, pizza congelada e papel higiênico. Em vez disso, você vai para um lugar. É mais fácil e rápido. O mesmo vale para desenvolvedores front-end e de microsserviços.

Expectativas de gestão

A administração tem a impressão equivocada de que agora precisa contratar um número infinito de desenvolvedores para um projeto (abrangente), uma vez que os desenvolvedores agora podem trabalhar de forma totalmente independente uns dos outros, cada um em seu próprio microsserviço. Apenas um pouco de trabalho de integração é necessário no final (pouco antes do lançamento). Um guia para microsserviços Java.  Parte 3: questões gerais - 5Na verdade, esta abordagem é extremamente problemática. Nos parágrafos seguintes tentaremos explicar o porquê.

“Pedaços menores” não são iguais a “pedaços melhores”

Seria um grande erro presumir que um código dividido em 20 partes será necessariamente de qualidade superior a uma peça inteira. Mesmo se considerarmos a qualidade de um ponto de vista puramente técnico, nossos serviços individuais ainda podem estar executando 400 consultas do Hibernate para selecionar um usuário do banco de dados, atravessando camadas de código não suportado. Mais uma vez, voltamos à citação de Simon Brown: se você não conseguir construir monólitos adequadamente, será difícil construir microsserviços adequados. Muitas vezes é extremamente tarde para falar sobre tolerância a falhas em projetos de microsserviços. Tanto que às vezes é assustador ver como funcionam os microsserviços em projetos reais. A razão para isso é que os desenvolvedores Java nem sempre estão prontos para estudar tolerância a falhas, redes e outros tópicos relacionados no nível adequado. As “peças” em si são menores, mas as “partes técnicas” são maiores. Imagine que sua equipe de microsserviços seja solicitada a escrever um microsserviço técnico para fazer login em um sistema de banco de dados, algo como isto:
@Controller
class LoginController {
    // ...
    @PostMapping("/login")
    public boolean login(String username, String password) {
        User user = userDao.findByUserName(username);
        if (user == null) {
            // обработка варианта с несуществующим пользователем
            return false;
        }
        if (!user.getPassword().equals(hashed(password))) {
            // обработка неверного пароля
            return false;
        }
        // 'Ю-ху, залогинorсь!';
        // установите cookies, делайте, что угодно
        return true;
    }
}
Agora sua equipe pode decidir (e talvez até convencer os empresários) que tudo isso é muito simples e chato, em vez de escrever um serviço de login, é melhor escrever um microsserviço UserStateChanged realmente útil, sem quaisquer implicações comerciais reais e tangíveis. E como algumas pessoas atualmente tratam Java como um dinossauro, vamos escrever nosso microsserviço UserStateChanged no moderno Erlang. E vamos tentar usar árvores rubro-negras em algum lugar, porque Steve Yegge escreveu que é preciso conhecê-las de dentro para fora para se candidatar ao Google. Do ponto de vista de integração, manutenção e design geral, isso é tão ruim quanto escrever camadas de código espaguete dentro de um único monólito. Um exemplo artificial e comum? Isto é verdade. No entanto, isso pode acontecer na realidade.

Menos peças – menos compreensão

Aí surge naturalmente a questão de entender o sistema como um todo, seus processos e fluxos de trabalho, mas ao mesmo tempo você, como desenvolvedor, é responsável apenas por trabalhar no seu microsserviço isolado [95: login-101: updateUserProfile]. Harmoniza-se com o parágrafo anterior, mas dependendo da sua organização, nível de confiança e comunicação, isso pode levar a muita confusão, encolher de ombros e culpar se houver uma falha acidental na cadeia de microsserviços. E não há ninguém que assuma total responsabilidade pelo que aconteceu. E não é uma questão de desonestidade. Na verdade, é muito difícil conectar diferentes partes e compreender o seu lugar no quadro geral do projeto.

Comunicações e serviços

O nível de comunicação e serviço varia muito dependendo do tamanho da empresa. Contudo, a relação geral é óbvia: quanto mais, mais problemático.
  • Quem executa o microsserviço nº 47?
  • Eles acabaram de implantar uma versão nova e incompatível de um microsserviço? Onde isso foi documentado?
  • Com quem preciso falar para solicitar um novo recurso?
  • Quem dará suporte a esse microsserviço em Erlang, depois que o único que conhecia esse idioma saiu da empresa?
  • Todas as nossas equipes de microsserviços trabalham não apenas em diferentes linguagens de programação, mas também em diferentes fusos horários! Como coordenamos tudo isso corretamente?
Um guia para microsserviços Java.  Parte 3: questões gerais - 6O ponto principal é que, tal como acontece com o DevOps, uma abordagem completa aos microsserviços numa grande empresa, talvez até internacional, acarreta uma série de problemas de comunicação adicionais. E a empresa deve se preparar seriamente para isso.

conclusões

Depois de ler este artigo, você pode pensar que o autor é um fervoroso oponente dos microsserviços. Isso não é inteiramente verdade – estou basicamente tentando destacar pontos aos quais poucas pessoas prestam atenção na corrida louca por novas tecnologias.

Microsserviços ou monólito?

Usar microsserviços Java a qualquer hora e em qualquer lugar é um extremo. O outro acaba sendo algo como centenas de bons e velhos módulos Maven em um monólito. Sua tarefa é encontrar o equilíbrio certo. Isto é especialmente verdadeiro para novos projetos. Não há nada que impeça você de adotar uma abordagem mais conservadora e “monolítica” e criar menos módulos Maven bons, em vez de começar com vinte microsserviços prontos para nuvem.

Microsserviços geram complexidade adicional

Tenha em mente que quanto mais microsserviços você tiver e menos DevOps realmente poderoso você tiver (não, executar alguns scripts Ansible ou implantar no Heroku não conta!), mais problemas você terá posteriormente no processo. Até mesmo ler até o final da seção deste guia dedicada a questões gerais sobre microsserviços Java é uma tarefa bastante tediosa. Pense bem sobre a implementação de soluções para todos esses problemas de infraestrutura e de repente você perceberá que não se trata mais apenas de programação de negócios (o que você é pago para fazer), mas sim de fixar mais tecnologia em ainda mais tecnologia. Shiva Prasad Reddy resumiu perfeitamente em seu blog : “Você não tem ideia de como é terrível quando uma equipe passa 70% do tempo lutando com essa infraestrutura moderna e apenas 30% do tempo sobra para fazer a lógica de negócios real. ” Shiva Prasad Reddy

Vale a pena criar microsserviços Java?

Para responder a essa pergunta, gostaria de encerrar este artigo com um teaser muito atrevido, semelhante a uma entrevista do Google. Se você souber a resposta a essa pergunta por experiência própria, mesmo que ela não pareça ter nada a ver com microsserviços, você pode estar pronto para uma abordagem de microsserviços.

Cenário

Imagine que você tem um monólito Java rodando sozinho no menor servidor dedicado da Hetzner . O mesmo se aplica ao seu servidor de banco de dados, ele também está sendo executado em uma máquina Hetzner semelhante . E vamos supor também que seu monólito Java possa lidar com fluxos de trabalho, digamos, registro de usuário, e você não esteja criando centenas de consultas de banco de dados por fluxo de trabalho, mas um número mais razoável (<10).

Pergunta

Quantas conexões de banco de dados seu monólito Java (conjunto de conexões) deve abrir em seu servidor de banco de dados? Por que é que? Quantos usuários ativos simultâneos você acha que seu monólito pode escalar (aproximadamente)?

Responder

Deixe sua resposta a essas perguntas na seção de comentários. Estou ansioso por todas as respostas. Um guia para microsserviços Java.  Parte 3: questões gerais - 8Agora decida-se. Se você leu até o fim, ficamos muito gratos a você!
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION