O Spring é 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. WebFlux 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 . O código está disponível no . Spring Initializr 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 Para fins educacionais, você pode facilmente manipular a contagem de threads em um pool, limitando as CPUs ao executar o contêiner do Docker: núcleos da CPU . 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 definidos pelo WebFlux. padrões Nosso aplicativo é uma cartomante simples. Ao chamar endpoint, você obterá 5 registros com . 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! /karma balanceAdjustment 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)); } método é uma coisa crucial aqui. Ele observa todos os sinais de fluxos reativos e os rastreia em logs no nível INFO. log A saída de logs em curl é a seguinte: localhost:8081/karma Como podemos ver, o processamento está acontecendo no pool de threads de E/S. O nome do tópico significa . 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. ctor-http-nio-2 reactor-http-nio-2 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 aqui porque ele já foi declarado na chamada original. log karma() Nos logs podemos ver a seguinte foto: Desta vez, apenas o primeiro elemento foi recebido no IO thread . O processamento dos 4 restantes foi dedicado a um pool de threads . reactor-http-nio-4 parallel Javadoc de confirma isso: delayElements Os sinais são atrasados e continuam no Agendador padrão paralelo Você pode obter o mesmo efeito sem demora especificando em qualquer lugar na cadeia de chamadas. .subscribeOn(Schedulers.parallel()) O uso do agendador 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. parallel 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 deve ser baseada nos requisitos específicos e compensações do aplicativo. parallel 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 e fazer uma cartomante mais . 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. flatMap justa @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 deve estar inscrito em um pool de threads . Vamos verificar o que temos em logs para os dois primeiros Karmas: makeFair's boundedElastic O Reactor inscreve o primeiro elemento com no thread IO balanceAdjustment=9 Em seguida, o pool funciona com justiça de Karma emitindo ajustes e no thread boundedElastic 90 -90 boundedElastic-1 Os elementos após o primeiro são inscritos no pool de threads paralelos (porque ainda temos na cadeia) delayedElements ? 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 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 boundedElastic tem Usando um pool de encadeamento assíncrono como , 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. boundedElastic Outros tipos de pools de threads Existem mais dois tipos de pools fornecidos pela classe pronta para uso, como: Scheduler : 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. single : 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. immediate 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.