fbpx
Resiliência de Microsserviços
Publicado em: quinta-feira, 6 de abr de 2023

Como construir microsserviços resilientes: dicas e estratégias para garantir a continuidade do seu serviço

Você já ouviu falar em resiliência? No contexto de software, ela se refere à capacidade de um sistema ou aplicação lidar e se adaptar a problemas e situações adversas. Para microsserviços, a resiliência é fundamental, especialmente considerando que eles dependem de outros serviços para funcionar. Neste post, exploremos os principais aspectos que você precisa saber para criar microsserviços resilientes, desde o gerenciamento de indisponibilidades até o uso de mensageria e processamento assíncrono.

A resiliência em microsserviços é um assunto mais complexo do que parece à primeira vista. Para garantir que um microsserviço seja resiliente, é necessário lidar com a indisponibilidade de suas dependências. Algumas indisponibilidades momentâneas podem ser resolvidas diretamente na aplicação, como falhas de rede breves. Porém, em casos de serviços offline por longos períodos, é preciso adotar outras abordagens.

Entendendo a resiliência:

Um microsserviço é considerado resiliente se, tecnicamente, ele puder tolerar a indisponibilidade de uma dependência por um longo período, como um dia, uma semana ou até um mês.

A partir desse ponto de vista, é possível considerar aspectos importantes para garantir a resiliência, como a necessidade de atualizações e a adoção de processamento assíncrono.

Eu não estou sugerindo que de fato você passe por isso. Mas seu ponto de partida for esse, você com certeza considerará coisas importantes, como a própria necessidade de deployment, como ofensor para um serviço que está esperando o outro voltar para processar algo.

Sua aplicação, quando é atualizada, não consegue informar que ainda está processando. Quando as tarefas são de curtíssima duração, gracefull shutdown ajuda nessa estratégia.

Entretanto, tarefas que estão apenas em memória, sem a ajuda de outros mecanismos externos como Mensageria e Stream Platforms, se tornam frágeis e com potencial de perda/descarte do processamento.

.NET e Polly:

Polly é uma biblioteca que pode ajudar a lidar com falhas de rede momentâneas. Com a utilização de Polly, é possível realizar retentativas em questão de segundos, minimizando o impacto no usuário. Embora a aplicação possa ficar lenta durante o processo, o fluxo de trabalho continua.

Usando Containers e Orquestradores:

Containers e orquestradores, como o Kubernetes, auxiliam na adoção de uma estratégia de redirecionamento de falhas de uma instância para outras saudáveis. Dessa forma, é possível garantir que o serviço continue funcionando mesmo quando uma instância específica encontra problemas.

Importância do API Gateway:

Um bom API Gateway, como o Kong, possui a capacidade de realizar retentativas em suas APIs. Isso significa que, em caso de falhas, o Gateway pode chamar outra instância saudável ou nova, garantindo a continuidade do serviço.

Processamento assíncrono e mensageria:

Para lidar com longos períodos de indisponibilidade, é fundamental adotar o processamento assíncrono e a mensageria. Essa abordagem permite que os microsserviços sejam resilientes, mesmo quando suas dependências não estão disponíveis. Serviços como RabbitMQ, Kafka e Azure Service Bus são exemplos de soluções que podem ser utilizadas nesse contexto.

Atenção aos Status Codes:

Em microsserviços, é crucial que os diversos componentes da arquitetura se comuniquem de maneira clara e eficiente. Isso inclui a troca de informações sobre o estado do sistema e o sucesso ou falha no processamento de requisições. Um dos detalhes que fazem toda diferença são os status codes HTTP.

Os status codes HTTP são códigos numéricos que representam o resultado do processamento de uma requisição e têm a função de informar ao cliente (ou outros componentes da arquitetura) se a operação foi bem-sucedida, se houve algum erro ou se o recurso solicitado está indisponível, entre outras informações relevantes. Utilizar os status codes corretamente é fundamental para garantir que todos os componentes envolvidos na comunicação compreendam o estado atual do sistema e tomem as ações adequadas em resposta a cada situação.

Os status codes são divididos em diferentes classes, cada uma delas com um propósito específico. Os códigos da família 2XX indicam que a requisição foi processada com sucesso e que a operação solicitada foi ou será concluída. Já os status codes da família 4XX denotam que houve um erro por parte do cliente, como uma solicitação malformada ou a tentativa de acessar um recurso inexistente. Por fim, os códigos da família 5XX sinalizam erros sistêmicos, ou seja, problemas no servidor ou no serviço que impedem o processamento correto da requisição.

Quando os status codes são usados corretamente, os componentes da arquitetura podem identificar e lidar com problemas de maneira eficiente, evitando que falhas em um componente se propaguem para outros e comprometam a estabilidade do sistema todo. Além disso, o monitoramento e a análise dos status codes gerados pelo sistema ajudam a identificar padrões e tendências de comportamento, possibilitando a detecção e correção de problemas antes que eles afetem a experiência do usuário final.

No entanto, é importante ressaltar que o uso inadequado dos status codes pode levar a uma percepção equivocada do estado do sistema e dificultar a identificação e resolução de problemas. Suprimir erros ou retornar status codes incorretos pode fazer com que a infraestrutura de monitoramento e outros componentes da arquitetura não consigam identificar e lidar adequadamente com problemas, comprometendo a resiliência dos microsserviços.

Portanto, ao desenvolver e manter microsserviços resilientes, é essencial garantir que os status codes sejam usados de maneira apropriada, conforme as boas práticas e convenções estabelecidas. Isso não só contribuirá para uma comunicação mais eficiente entre os componentes da arquitetura, como também aumentará a resiliência e a confiabilidade do sistema como um todo.

Tratamento de exceções:

O tratamento adequado de exceções é um aspecto crítico para garantir a resiliência de qualquer software, incluindo os baseados em microsserviços. Quando uma exceção é gerada durante o processamento de uma requisição, é crucial capturá-la e lidar com ela de maneira apropriada, a fim de evitar impactos negativos na experiência do usuário e na integridade do sistema.

Em muitos casos, a falta de um tratamento de exceções eficiente pode levar a uma série de problemas, como a propagação de erros para outros componentes, o comprometimento da integridade dos dados e a falha no isolamento de componentes problemáticos. Por isso, é essencial implementar práticas robustas de tratamento de exceções que permitam identificar, registrar e tratar adequadamente as exceções geradas.

Uma estratégia eficaz para o tratamento de exceções envolve a categorização e a padronização das exceções. Ao categorizar as exceções em tipos específicos, como exceções de validação, exceções de negócio e exceções de infraestrutura, é possível direcionar o tratamento de cada exceção de forma mais granular e adequada às suas características. Além disso, padronizar as exceções permite uma melhor comunicação entre os componentes do sistema e facilita a identificação de problemas recorrentes.

O uso de mecanismos de Log é fundamental para garantir a rastreabilidade das exceções e auxiliar na análise e diagnóstico de problemas. Um Log de exceções bem estruturado permite a identificação rápida de padrões e a tomada de decisões informadas para aprimorar a resiliência do sistema.

Por fim, o tratamento de exceções deve ser integrado às práticas de monitoramento e alerta do sistema, permitindo que a equipe de desenvolvimento seja notificada rapidamente em caso de exceções críticas ou problemas recorrentes. Dessa forma, os desenvolvedores podem tomar medidas corretivas proativas, minimizando o impacto de erros e indisponibilidades no desempenho e na resiliência dos microsserviços.

Em breve teremos um material mais detalhado sobre exceções.

Implementando Disponibilidade Rápida:

A Disponibilidade Rápida (em inglês, Disposability) é um princípio abordado no The Twelve-Factor App, que enfatiza a importância de construir aplicações que possam ser iniciadas e finalizadas rapidamente e de forma confiável. Esse princípio facilita a escalabilidade, recuperação de falhas e implantação contínua.

Para alcançar a Disponibilidade Rápida, é essencial garantir que a aplicação lide adequadamente com sinais do sistema, como SIGTERM e SIGKILL, que são usados para interromper e encerrar processos. Implementar o Encerramento Suave (Graceful Shutdown) é uma prática recomendada que permite que a aplicação finalize as tarefas em andamento e libere os recursos apropriadamente antes de ser encerrada.

O ASP.NET lida bem com esse fluxo, entretanto para que você se beneficie da implementação, é necessário usar o recurso certo como os Worker Services do ASP.NET.

Outras técnicas que podem ser empregadas para aumentar a Disponibilidade Rápida incluem:

  • Uso de processos stateless, que não armazenam informações sobre o estado do sistema e, portanto, podem ser iniciados e encerrados de forma independente;
  • Adoção de mecanismos de armazenamento externo, como bancos de dados e caches distribuídos, para armazenar o estado da aplicação;
  • Implementação de sistemas de filas e mensagens assíncronas para comunicação entre microsserviços;
  • Uso de orquestradores, como Kubernetes, que podem gerenciar o ciclo de vida das aplicações e garantir a rápida recuperação em caso de falhas.

Ao seguir as orientações e práticas relacionadas à Disponibilidade Rápida, você estará no caminho certo para desenvolver aplicações resilientes que podem lidar com falhas, escalar eficientemente e se adaptar às mudanças nos requisitos do projeto.

Estratégias de deployment:

Blue Green e Canary são estratégias de deployment que ajudam aplicações e serviços de alta carga a reduzir riscos a cada atualização. Essas abordagens permitem validar novas versões da aplicação ou serviço com uma fração dos usuários antes de implementá-las em larga escala. Embora sejam complexas, essas estratégias podem reduzir danos em caso de versões instáveis e garantir maior resiliência e confiabilidade.

As estratégias de implantação Blue-Green e Canary são abordagens modernas para a atualização de microsserviços, visando minimizar o tempo de inatividade e o impacto nos usuários. Ambas as estratégias estão embasadas em literaturas técnicas e práticas recomendadas da indústria de software (Humble e Farley, 2010; Richardson, 2018).

A implantação Blue-Green consiste em manter duas versões do ambiente de produção em paralelo: o "blue" (atualmente em produção) e o "green" (nova versão a ser lançada). As mudanças são inicialmente implementadas no ambiente "green" e, após serem testadas e validadas, o tráfego é direcionado para este ambiente, tornando-o o novo ambiente "blue". Isso permite que os desenvolvedores revertam rapidamente para a versão anterior em caso de problemas, garantindo maior resiliência e confiabilidade.

A implantação Canary, por outro lado, é uma estratégia de liberação gradual na qual a nova versão da aplicação é disponibilizada para um subconjunto de usuários antes de ser lançada para todos. Essa abordagem permite que os desenvolvedores monitorem o desempenho e a estabilidade da nova versão em um ambiente de produção, detectando e corrigindo problemas antes que afetem a base de usuários em geral. A implantação Canary é especialmente útil para sistemas com muitas instâncias e alto risco associado a cada atualização.

Embora essas estratégias de implantação sejam complexas e exijam planejamento cuidadoso, elas são altamente eficazes na promoção de resiliência e confiabilidade dos microsserviços, reduzindo a probabilidade de interrupções e falhas no sistema.

Referências:

  • Humble, J., & Farley, D. (2010). Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation. Addison-Wesley Professional.
  • Richardson, C. (2018). Microservices patterns: With examples in Java. Manning Publications Co.

Mensageria e aumento de complexidade:

Mensageria e o processamento assíncrono são elementos cruciais para alcançar a resiliência em sistemas de microsserviços. Essas técnicas promovem a comunicação não bloqueante e independente entre os serviços, permitindo que continuem funcionando mesmo quando uma ou mais dependências enfrentam falhas, ou atrasos temporários.

Segundo a literatura técnica, ferramentas e plataformas como RabbitMQ, Kafka e Azure Service Bus são amplamente utilizadas em arquiteturas de microsserviços devido à sua capacidade de gerenciar mensagens e eventos de forma confiável e escalável. Esses sistemas de mensageria garantem a entrega de mensagens entre os serviços, mesmo diante de falhas e indisponibilidades, melhorando significativamente a resiliência do sistema todo.

No entanto, é importante observar que a introdução da mensageria e do processamento assíncrono pode aumentar a complexidade do sistema. Isso pode exigir um investimento adicional em monitoramento, rastreamento e gerenciamento de mensagens, bem como uma mudança na forma como os desenvolvedores projetam e implementam os serviços. Portanto, é fundamental avaliar cuidadosamente se essa abordagem é adequada para seu caso específico e estar preparado para lidar com os desafios e implicações relacionados ao aumento da complexidade.

Dicas extras:

Além das estratégias e dicas mencionadas anteriormente, aqui estão algumas dicas extras que podem ajudá-lo a aprimorar ainda mais a resiliência dos seus microsserviços:

Monitoramento e alertas: Implementar um sistema de monitoramento eficiente e configurar alertas para casos de indisponibilidade, erros ou desempenho abaixo do esperado é fundamental para garantir a resiliência dos seus microsserviços. Essa abordagem permite identificar problemas rapidamente e tomar medidas corretivas antes que afetem a experiência do usuário.

Arquitetura baseada em eventos: Adotar uma arquitetura baseada em eventos permite que os microsserviços se comuniquem de forma assíncrona e independente, aumentando a resiliência geral do sistema. Além disso, essa abordagem facilita a escalabilidade e a adaptação a mudanças nos requisitos do projeto.

Padrões de resiliência: Existem padrões de resiliência comprovados, como Circuit Breaker, Bulkhead e Timeout, que podem ser aplicados aos seus microsserviços para melhorar a resiliência. Esses padrões ajudam a isolar falhas, limitar o impacto de erros e garantir que o sistema continue operando mesmo quando enfrenta problemas.

Versionamento de APIs: Adotar práticas adequadas de versionamento de APIs permite que você faça alterações nos microsserviços sem afetar negativamente os clientes e outros serviços que dependem deles. Isso contribui para a resiliência geral do sistema e facilita a evolução do projeto.

Escalabilidade: Assegurar que seus microsserviços sejam escaláveis é importante para a resiliência. Isso inclui projetar o sistema para lidar com aumento de carga e demanda, bem como adotar soluções de autoescalabilidade, como o uso de orquestradores e soluções em nuvem.

Conclusão:

Construir microsserviços resilientes é essencial para garantir a continuidade e o sucesso do seu serviço. Ao seguir as dicas e estratégias apresentadas neste post, é possível criar aplicações saudáveis, que funcionam corretamente e não "mentem" sobre seu estado. Lembre-se de prestar atenção aos status codes, utilizar bibliotecas como Polly, adotar containers e orquestradores, implementar API Gateway, tratar exceções adequadamente, aplicar práticas de disposability, adotar estratégias de deployment e considerar o uso de mensageria e processamento assíncrono. Com esses cuidados, você estará no caminho certo para desenvolver microsserviços verdadeiramente resilientes.

Ao considerar e aplicar as dicas e estratégias apresentadas neste post, você estará no caminho certo para desenvolver microsserviços verdadeiramente resilientes. Lembre-se de que a resiliência é um objetivo contínuo e exige um esforço constante de aprendizado e adaptação. Investir tempo e recursos na construção de microsserviços resilientes resultará em sistemas mais confiáveis e eficientes, capazes de atender às demandas dos usuários e enfrentar os desafios do ambiente de TI em constante evolução.

O Cloud Native .NET é meu principal projeto.

Onde empenho energia para ajudar, acompanhar, direcionar Desenvolvedores, Líderes Técnicos e jovens Arquitetos na jornada Cloud Native.

Conduzo entregando a maior e mais completa stack de tecnologias do mercado.

Ao trabalhar com desenvolvedores experientes, eu consigo usar seu aprendizado com .NET, banco de dados, e arquitetura para encurtar a jornada.

Ao restringir à desenvolvedores .NET eu consigo usar do contexto de tecnologias e problemas do seu dia-a-dia, coisas que você conhece hoje, como WCF, WebForms, IIS e MVC, por exemplo, para mostrar a comparação entre o que você conhece e o que está sendo apresentado.

É assim que construímos fundamentos sólidos, digerindo a complexidade com didática, tornando o complexo, simples.

É assim que conseguimos tornar uma jornada densa, em um pacote de ~4 meses.

Eu não acredito que um desenvolvedor possa entender uma tecnologia sem compreender seus fundamentos. Ele no máximo consegue ser produtivo, mas isso não faz desse desenvolvedor um bom tomador de decisões técnicas.

É preciso entender os fundamentos para conseguir tomar boas decisões.

0 comentários

Enviar um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.

Luiz Carlos Faria

Mensagem do Autor

Espero que goste desse post. Não deixe de comentar e falar o que achou.

Se acha que esse post pode ajudar alguém que você conheça, compartilhe!

 

Lives

Fique de olho nas lives

Fique de olho nas lives no meu canal do Youtube, no Canal .NET e nos Grupos do Facebook e Instagram.

Aceleradores

Existem diversas formas de viabilizar o suporte ao teu projeto. Seja com os treinamentos, consultoria, mentorias em grupo.