paint-brush
Uma introdução ao modelo de encadeamento Spring WebFluxby@vladimirf
13,809
13,809

Uma introdução ao modelo de encadeamento Spring WebFlux

O pring WebFlux é uma estrutura da Web reativa e sem bloqueio que usa a biblioteca Reactor para implementar a programação reativa em Java. O modelo de encadeamento do WebFlux é diferente do modelo tradicional de encadeamento por solicitação usado em muitos frameworks da Web síncronos. O WebFlux usa um modelo sem bloqueio e orientado a eventos, no qual um pequeno número de threads pode lidar com um grande número de solicitações. Isso permite que o thread siga em frente para lidar com outras solicitações enquanto as tarefas são executadas em segundo plano. O uso de um agendador paralelo pode melhorar o desempenho e a escalabilidade, permitindo que várias tarefas sejam executadas simultaneamente em diferentes threads.
featured image - Uma introdução ao modelo de encadeamento Spring WebFlux
Vladimir Filipchenko HackerNoon profile picture
0-item

O Spring WebFlux é uma estrutura da Web reativa e sem bloqueio para criar aplicativos da Web modernos e escaláveis em Java. Faz parte do Spring Framework e usa a biblioteca Reactor para implementar a programação reativa em Java.


Com o WebFlux, você pode criar aplicativos da Web escalonáveis e de alto desempenho que podem lidar com um grande número de solicitações e fluxos de dados simultâneos. Ele oferece suporte a uma ampla variedade de casos de uso, desde APIs REST simples até streaming de dados em tempo real e eventos enviados pelo servidor.


O Spring WebFlux fornece um modelo de programação baseado em fluxos reativos, que permite compor operações assíncronas e sem bloqueio em um pipeline de estágios de processamento de dados. Ele também fornece um rico conjunto de recursos e ferramentas para criar aplicativos da Web reativos, incluindo suporte para acesso reativo a dados, segurança reativa e teste reativo.


Do documento oficial do Spring :

O termo “reativo” refere-se a modelos de programação construídos para reagir a mudanças — componentes de rede reagindo a eventos de E/S, controladores de IU reagindo a eventos de mouse e outros. Nesse sentido, o não-bloqueio é reativo, porque, em vez de estarmos bloqueados, estamos agora no modo de reagir a notificações à medida que as operações são concluídas ou os dados se tornam disponíveis.

Modelo de rosca

Um dos principais recursos da programação reativa é seu modelo de encadeamento, que é diferente do modelo tradicional de encadeamento por solicitação usado em muitos frameworks da Web síncronos.


No modelo tradicional, um novo thread é criado para lidar com cada solicitação recebida e esse thread é bloqueado até que a solicitação seja processada. Isso pode levar a problemas de escalabilidade ao lidar com grandes volumes de solicitações, pois o número de encadeamentos necessários para lidar com as solicitações pode se tornar muito grande e a troca de contexto de encadeamento pode se tornar um gargalo.


Em contraste, o WebFlux usa um modelo orientado a eventos sem bloqueio, onde um pequeno número de threads pode lidar com um grande número de solicitações. Quando uma solicitação chega, ela é tratada por um dos threads disponíveis, que então delega o processamento real a um conjunto de tarefas assíncronas. Essas tarefas são executadas de maneira não bloqueante, permitindo que o thread siga em frente para lidar com outras solicitações enquanto as tarefas são executadas em segundo plano.


No Spring WebFlux (e servidores sem bloqueio em geral), presume-se que os aplicativos não bloqueiam. Portanto, os servidores sem bloqueio usam um pool de encadeamentos pequeno e de tamanho fixo (operadores de loop de eventos) para manipular solicitações.


O modelo simplificado de segmentação de um contêiner Servlet clássico se parece com:

Embora o processamento da solicitação do WebFlux seja um pouco diferente:

Sob o capô

Vamos ver o que está por trás da teoria brilhante.

Precisamos de um aplicativo bem minimalista gerado pelo Spring Initializr . O código está disponível no repositório do GitHub .


Todos os tópicos relacionados a threads são muito dependentes da CPU. Normalmente, o número de threads de processamento que atendem às solicitações está relacionado ao número de núcleos da CPU . Para fins educacionais, você pode facilmente manipular a contagem de threads em um pool, limitando as CPUs ao executar o contêiner do Docker:

 docker run --cpus=1 -d --rm --name webflux-threading -p 8081:8080 local/webflux-threading

Se você ainda vir mais de um thread em um pool - tudo bem. Pode haver padrões definidos pelo WebFlux.

Nosso aplicativo é uma cartomante simples. Ao chamar /karma endpoint, você obterá 5 registros com balanceAdjustment . Cada ajuste é um número inteiro que representa um carma dado a você. Sim, somos muito generosos porque o app gera apenas números positivos. Não há mais azar!

Processamento padrão

Vamos começar com um exemplo bem básico. O próximo método do controlador retorna um Flux contendo 5 elementos karma.


 @GetMapping("/karma") public Flux<Karma> karma() { return prepareKarma() .map(Karma::new) .log(); } private Flux<Integer> prepareKarma() { Random random = new Random(); return Flux.fromStream( Stream.generate(() -> random.nextInt(10)) .limit(5)); }


log método é uma coisa crucial aqui. Ele observa todos os sinais de fluxos reativos e os rastreia em logs no nível INFO.


A saída de logs em curl localhost:8081/karma é a seguinte:


Como podemos ver, o processamento está acontecendo no pool de threads de E/S. O nome do tópico ctor-http-nio-2 significa reactor-http-nio-2 . As tarefas foram executadas imediatamente em um thread que as enviou. O Reactor não viu nenhuma instrução para agendá-los em outro pool.

Atraso e processamento paralelo

A próxima operação vai atrasar a emissão de cada elemento em 100ms (também conhecida como emulação de banco de dados)


 @GetMapping("/delayedKarma") public Flux<Karma> delayedKarma() { return karma() .delayElements(Duration.ofMillis(100)); }


Não precisamos adicionar o método log aqui porque ele já foi declarado na chamada karma() original.


Nos logs podemos ver a seguinte foto:


Desta vez, apenas o primeiro elemento foi recebido no IO thread reactor-http-nio-4 . O processamento dos 4 restantes foi dedicado a um pool de threads parallel .


Javadoc de delayElements confirma isso:

Os sinais são atrasados e continuam no Agendador padrão paralelo


Você pode obter o mesmo efeito sem demora especificando .subscribeOn(Schedulers.parallel()) em qualquer lugar na cadeia de chamadas.


O uso do agendador parallel pode melhorar o desempenho e a escalabilidade, permitindo que várias tarefas sejam executadas simultaneamente em diferentes threads, o que pode utilizar melhor os recursos da CPU e lidar com um grande número de solicitações simultâneas.


No entanto, ele também pode aumentar a complexidade do código e o uso de memória e potencialmente levar ao esgotamento do pool de encadeamentos se o número máximo de encadeamentos de trabalho for excedido. Portanto, a decisão de usar o pool de encadeamentos parallel deve ser baseada nos requisitos específicos e compensações do aplicativo.


Subcadeia

Agora vamos dar uma olhada em um exemplo mais complexo. O código ainda é bastante simples e direto, mas a saída é muito mais interessante.


Vamos usar um flatMap e fazer uma cartomante mais justa . Para cada instância de Karma, ele multiplicará o ajuste original por 10 e gerará os ajustes opostos, criando efetivamente uma transação balanceada que compensa a original.


 @GetMapping("/fairKarma") public Flux<Karma> fairKarma() { return delayedKarma() .flatMap(this::makeFair); } private Flux<Karma> makeFair(Karma original) { return Flux.just(new Karma(original.balanceAdjustment() * 10), new Karma(original.balanceAdjustment() * -10)) .subscribeOn(Schedulers.boundedElastic()) .log(); }


Como você pode ver, o Flux makeFair's deve estar inscrito em um pool de threads boundedElastic . Vamos verificar o que temos em logs para os dois primeiros Karmas:


  1. O Reactor inscreve o primeiro elemento com balanceAdjustment=9 no thread IO


  2. Em seguida, o pool boundedElastic funciona com justiça de Karma emitindo ajustes 90 e -90 no thread boundedElastic-1


  3. Os elementos após o primeiro são inscritos no pool de threads paralelos (porque ainda temos delayedElements na cadeia)


O que é um agendador boundedElastic ?

É um pool de encadeamentos que ajusta dinamicamente o número de encadeamentos de trabalho com base na carga de trabalho. Ele é otimizado para tarefas vinculadas a E/S, como consultas de banco de dados e solicitações de rede, e foi projetado para lidar com um grande número de tarefas de curta duração sem criar muitos encadeamentos ou desperdiçar recursos.


Por padrão, o pool de threads boundedElastic tem um tamanho máximo do número de processadores disponíveis multiplicado por 10, mas você pode configurá-lo para usar um tamanho máximo diferente, se necessário


Usando um pool de encadeamento assíncrono como boundedElastic , você pode descarregar tarefas para encadeamentos separados e liberar o encadeamento principal para lidar com outras solicitações. A natureza limitada do pool de threads pode evitar a falta de threads e o uso excessivo de recursos, enquanto a elasticidade do pool permite que ele ajuste o número de threads de trabalho dinamicamente com base na carga de trabalho.


Outros tipos de pools de threads

Existem mais dois tipos de pools fornecidos pela classe Scheduler pronta para uso, como:


  • single : Este é um contexto de execução serializado de encadeamento único projetado para execução síncrona. É útil quando você precisa garantir que uma tarefa seja executada em ordem e que duas tarefas não sejam executadas simultaneamente.


  • immediate : Esta é uma implementação trivial e não operacional de um agendador que executa tarefas imediatamente no thread de chamada sem nenhuma troca de thread.


Conclusão

O modelo de encadeamento no Spring WebFlux foi projetado para não bloquear e ser assíncrono, permitindo o tratamento eficiente de um grande número de solicitações com uso mínimo de recursos. Em vez de depender de encadeamentos dedicados por conexão, o WebFlux usa um pequeno número de encadeamentos de loop de eventos para lidar com solicitações recebidas e distribuir trabalho para encadeamentos de trabalho de vários pools de encadeamentos.


No entanto, é importante escolher o pool de encadeamentos certo para seu caso de uso para evitar a falta de encadeamento e garantir o uso eficiente dos recursos do sistema.