No agendamento preemptivo, o sistema define a duração de execução de cada thread, sem que a thread tenha a capacidade de decidir quando realizar a troca de contexto. Esse modelo evita que uma única thread sobrecarregue o processo ou até mesmo o sistema inteiro. O Java adota esse agendamento preemptivo, o que significa que o agendamento das threads no Java é feito automaticamente pelo sistema, embora o programador possa sugerir ao kernel alocar mais ou menos tempo para uma thread específica.
Entretanto, a priorização das threads não é uma forma extremamente confiável de coordenação, pois, na prática, os threads Java, dentro da Máquina Virtual Java (JVM), são mapeados para threads do sistema operacional, e é o kernel que determina o agendamento das threads. Embora o kernel ofereça um mecanismo de priorização de threads, essa priorização nem sempre corresponde à maneira como as threads Java são priorizadas. A JVM adota tradicionalmente o modelo de mapeamento um a um entre threads Java e threads do sistema operacional, o que leva a custos elevados de troca de contexto e agendamento, limitando severamente o número de threads que o sistema pode comportar.
Esse modelo de threads tem funcionado adequadamente em aplicativos monolíticos, onde a duração de uma única requisição era relativamente longa e o custo de troca de contexto não era significativo. No entanto, com a ascensão dos microsserviços e a diminuição da duração de cada requisição, o custo associado às trocas de contexto tem se tornado cada vez mais impactante. Nos sistemas modernos, onde milhões de requisições são enviadas simultaneamente, a perda associada às trocas de contexto pode ser considerável, mesmo quando o sistema é capaz de processá-las eficientemente. Isso ocorre porque o custo de agendar uma thread do kernel envolve a troca entre o espaço de usuário e o espaço do kernel, o que implica, principalmente, no custo de responder a interrupções e restaurar as informações de execução.
Em resposta a esses desafios, surgem as threads virtuais. As threads virtuais são projetadas para serem mais baratas em termos de custo de troca de contexto, pois seu modelo reduz drasticamente o tamanho da pilha de memória (que pode ser de até 2 MB em threads tradicionais) e, consequentemente, o movimento de dados durante as trocas de contexto. Além disso, a criação dessas threads requer uma comunicação com o kernel para agendamento, mas essa comunicação é menos dispendiosa comparada com as threads tradicionais, pois elas utilizam chamadas ao sistema de maneira reduzida.
A principal inovação das threads virtuais é que elas mantêm um modelo de relação N:1 com as threads Java. Isso significa que, se uma thread virtual bloqueia, a thread Java à qual ela está associada pode continuar a executar outras threads virtuais. Esse modelo elimina as trocas de contexto entre o espaço de usuário e o kernel, tornando o agendamento mais eficiente. Por isso, quando as threads virtuais são utilizadas, a JVM consegue gerenciar seu agendamento internamente, diminuindo ainda mais o custo das trocas de contexto.
No entanto, há limitações. Em alguns casos, as threads virtuais podem ficar "presas" em uma thread Java e não aproveitar as vantagens de agendamento eficientes. Isso pode ocorrer quando se utiliza sincronização dentro de uma thread virtual ou quando são feitos chamados nativos via JNI (Java Native Interface), o que dificulta o uso eficiente do modelo de threads virtuais.
Além disso, o ciclo de vida e os estados de uma thread virtual podem ser monitorados, mas isso nem sempre é simples. A tecnologia das threads virtuais avançou de forma tão rápida que as ferramentas de monitoramento e observabilidade nem sempre conseguem acompanhar essas mudanças. Isso ocorre porque a maioria das soluções comerciais de observabilidade ainda está desenvolvendo ou planejando implementar suporte total para threads virtuais. Para quem já utiliza threads virtuais, é aconselhável monitorá-las utilizando utilitários do sistema, em vez de depender das ferramentas tradicionais de observabilidade, que podem não fornecer uma visão completa ou precisa da execução dessas threads.
Com a introdução de threads virtuais, o modelo de concorrência do Java foi significativamente alterado. As threads virtuais permitem um número muito maior de threads em execução simultânea, o que é especialmente vantajoso para sistemas com alta concorrência, como microsserviços. Por exemplo, o Spring Boot, a partir da versão 3.2, oferece suporte para threads virtuais, permitindo que as aplicações sejam mais escaláveis sem aumentar excessivamente os custos de gerenciamento de threads. Para utilizar threads virtuais em uma aplicação Spring Boot, basta configurar alguns parâmetros no arquivo application.properties, como spring.threads.virtual.enabled=true e spring.threads.virtual.enabled.manually=false.
Ao processar requisições de forma assíncrona, o Spring Boot pode usar threads virtuais para lidar com métodos anotados com @Async, o que melhora a eficiência do sistema. A introdução das threads virtuais não substitui as threads tradicionais, mas oferece uma nova abordagem para lidar com tarefas assíncronas de forma mais eficaz. Isso é demonstrado em exemplos simples de aplicações, onde é possível observar a criação de uma thread virtual para cada requisição processada, o que permite um gerenciamento de threads muito mais eficiente.
No entanto, a implementação de threads virtuais também traz desafios, especialmente em termos de monitoramento e observabilidade. Enquanto ferramentas como OpenTelemetry começam a fornecer insights sobre o comportamento das threads virtuais, ainda há muito a ser feito para melhorar o suporte a essas novas tecnologias. Portanto, é fundamental que os desenvolvedores compreendam que, embora as threads virtuais tragam uma grande inovação, o ecossistema de ferramentas e práticas ao seu redor ainda está em desenvolvimento.
Em sistemas de alta concorrência, como os que usam microsserviços, é crucial entender como as threads virtuais podem ser vantajosas. No entanto, é igualmente importante estar ciente de suas limitações e de que as ferramentas tradicionais de monitoramento podem não ser suficientes para oferecer uma visão precisa do desempenho dessas threads. O tempo de espera por soluções mais completas de observabilidade e monitoramento pode exigir cautela ao adotar essas novas tecnologias em ambientes de produção.
Como a Instrumentação de Rastreamento Pode Melhorar a Observabilidade em Microserviços
A instrumentação de rastreamento é uma das chaves para uma observabilidade eficaz em sistemas distribuídos, especialmente quando lidamos com microserviços. Sua complexidade, embora crescente, traz benefícios essenciais para a análise e resolução de problemas que, de outra forma, seriam difíceis de detectar ou corrigir. Quando configuramos a observabilidade, nos deparamos com vários desafios, mas a correlação entre dados de diferentes fontes pode ser a solução para muitos desses problemas.
O primeiro obstáculo a ser superado é quando a instrumentação do rastreamento falha. Isso pode ser resolvido utilizando perfis para identificar os métodos que o agente tentou instrumentar, permitindo assim localizar onde o rastreamento falhou. Além disso, quando o rastreamento se quebra, a solução pode ser encontrada ao utilizar logs para comparar e verificar os dados do rastreamento, proporcionando uma maneira de validar e corrigir falhas. Dessa forma, a observabilidade não é apenas uma ferramenta para monitoramento, mas também um mecanismo para diagnosticar e corrigir problemas internos de forma eficaz.
A observabilidade moderna, que combina uma variedade de sinais, é mais complexa de configurar do que a monitoração centrada em métricas e logs, prática comum no passado. Com as configurações erradas sendo uma ocorrência comum, é essencial entender que, para se obter rastreamentos eficazes, são necessários mais do que os simples dashboards. A análise de dados vai além da simples visualização no painel de controle, envolvendo cálculos mais sofisticados como MTTR (Mean Time to Repair) e relatórios detalhados.
Outro aspecto fundamental é a necessidade de projetar cuidadosamente os atributos de "span", os links, anotações e o "baggage" dos rastreamentos. Esses componentes devem ser meticulosamente planejados durante a fase de análise e design para melhorar a observabilidade. Vale ressaltar que os agentes de rastreamento não configuram automaticamente os atributos de "span" e "baggage", sendo esta uma tarefa que deve ser realizada por uma equipe de SRE (Site Reliability Engineering) ao longo do tempo. A instalação do agente é apenas uma tarefa inicial; a verdadeira complexidade surge quando se trata de melhorar e refinar esses atributos.
Para entender como o rastreamento funciona em microserviços, é necessário considerar três etapas principais: validação do cabeçalho, propagação e transferência. Quando uma mensagem é propagada de um microserviço para outro, o cabeçalho da mensagem deve conter o contexto do rastreamento, incluindo o ID do rastreamento e os IDs dos "spans" relacionados. Se o cabeçalho estiver ausente ou mal formatado, um erro é gerado. Quando um microserviço A faz uma solicitação para o microserviço B, o cabeçalho da mensagem deve definir esse contexto de rastreamento, incluindo o ID do rastreamento, o ID do "span" pai e o ID do "span" atual.
Além disso, a transferência do contexto do rastreamento para o backend de observabilidade é um processo distinto da propagação. Enquanto a propagação visa microserviços downstream, a transferência envolve o envio das informações para o backend de observabilidade, sem a necessidade de um cabeçalho. Isso é importante para esclarecer que a propagação e a transferência não são intercambiáveis, apesar de ambos envolverem a passagem de informações entre sistemas diferentes.
Embora a instrumentação automática seja o ideal, ela nem sempre é possível em ambientes que utilizam APIs ou protocolos personalizados. Nesses casos, a instrumentação manual utilizando APIs para injetar e extrair o contexto do rastreamento nos cabeçalhos das mensagens torna-se necessária. A principal dificuldade dessa abordagem é que, ao ser aplicada em um grande número de microserviços, exige modificações no código e pode se tornar ineficiente. No entanto, com o tempo, a instrumentação manual pode ser automatizada por meio da extensão OpenTelemetry, uma solução cada vez mais comum.
Nos cenários onde há conversão entre diferentes padrões de rastreamento, como no caso de microserviços que utilizam formatos B3 ou W3C, a configuração correta dos propagadores é crucial. OpenTelemetry suporta diversos formatos de propagação, como o W3C Trace Context e W3C Baggage, e também oferece suporte a propagadores como B3 e OpenTracing. Essa conversão entre formatos de rastreamento pode se tornar complexa, mas é uma parte fundamental para garantir que os dados fluam corretamente entre os microserviços e o backend de observabilidade.
Além disso, a implementação de rastreamento automático em middleware, como servidores API e plataformas EAI, pode ser mais desafiadora do que o desenvolvimento de integração de sistemas legados. O rastreamento exige uma compreensão mais profunda da rede de conexões entre os microserviços e os protocolos que interagem entre si. Em alguns casos, pode ser difícil até mesmo para sistemas comerciais de observabilidade funcionar corretamente sem ajustes finos, o que leva à recomendação de utilizar o OpenTelemetry para instrumentação.
Por fim, quando se trabalha com sistemas legados e middleware, a propagação do rastreamento pode ser uma tarefa especialmente desafiadora. A implementação de rastreamentos de ponta a ponta (E2E) e a imposição de uma observabilidade eficaz em organizações com sistemas diversos exigem esforços contínuos e ajustes constantes. Mesmo com agentes de observabilidade comerciais, pode haver dificuldades que exigem uma abordagem mais flexível e customizada, como a utilização de agentes OpenTelemetry.
Como os Sensores Acústicos com Ondas Superficiais Estão Revolucionando a Detecção de Gases e Substâncias Químicas
Como a Teoria Espectral dos Grafos Aplica-se nas Ciências Químicas?
Como Implementar Caching e Limitação de Taxa Usando Redis em Aplicações FastAPI

Deutsch
Francais
Nederlands
Svenska
Norsk
Dansk
Suomi
Espanol
Italiano
Portugues
Magyar
Polski
Cestina
Русский