Explore o novo chatbot do Developer Center! O MongoDB AI chatbot pode ser acessado na parte superior da sua navegação para responder a todas as suas perguntas sobre o MongoDB .

Junte-se a nós no Amazon Web Services re:Invent 2024! Saiba como usar o MongoDB para casos de uso de AI .
Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Idiomaschevron-right
Javachevron-right

Como otimizar o desempenho do Java com threads virtuais, programação reativa e MongoDB

Maxime Beugnet5 min read • Published Aug 29, 2024 • Updated Aug 29, 2024
SpringMongoDBJava
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Artigo
star-empty
star-empty
star-empty
star-empty
star-empty

Introdução

Quando ouvi pela primeira vez sobre Project Loom e threads virtuais, meu primeiro pensamento foi que isso era uma sentença de morte para aprogramação reativa. Não foi uma má notícia no início, porque a programação reativa vem com sua camada adicional de complexidade e usar a programação imperativa sem desperdiçar recursos era música para meus ouvidos.
Mas eu estava realmente errado e um pouco mais de leitura e aprendizado me ajudaram a entender por que pensar que isso era um erro.
Nesta publicação, exploraremos threads virtuais e programação reativa, suas diferenças e como podemos aproveitar ambas no mesmo projeto para obter o desempenho máximo de simultaneidade em Java.
Saiba mais sobre osuporte a threads virtuais com o MongoDB em minha postagem anterior sobre este tópico.

Threads virtuais

Modelo de thread tradicional em Java

Na simultaneidade Java tradicional, threads são entidades pesadas gerenciadas pelo sistema operacional. Cada thread do sistema operacional é envolvido por um thread da plataforma que é gerenciado pela máquina virtual Java (JVM) que executa o código Java.
Cada thread requer recursos significativos do sistema, o que leva a limitações na escalabilidade ao lidar com um grande número de tarefas simultâneas. A alternância de contexto entre threads também consome muitos recursos e pode deteriorar o desempenho.

Apresentando threads virtuais

Os threads virtuais, introduzidos pelo Projeto Loom no JEP 444, são leves por design e têm como objetivo superar as limitações dos threads tradicionais e criar aplicativos simultâneos de alta taxa de transferência. Eles implementam ojava.lang.Thread e são gerenciados pela JVM. Vários deles podem ser executados no mesmo thread de plataforma, tornando-os mais eficientes para trabalhar com um grande número de pequenas tarefas simultâneas.

Benefícios dos threads virtuais

Os threads virtuais permitem que o desenvolvedor Java use os recursos do sistema com mais eficiência e E/S sem bloqueio.
Mas com o JEP 453: Simultaneidade Estruturada e JEP 446: Valores com Escopo, os threads virtuais também suportam simultaneidade estruturada para tratar um grupo de tarefas relacionadas como uma única unidade de trabalho e dividir uma tarefa em subtarefas independentes menores para melhorar tempo de resposta e taxa de transferência.

Exemplo

Aqui está um exemplo básico de Java.
1import java.util.concurrent.ExecutorService;
2import java.util.concurrent.Executors;
3
4public class VirtualThreadsExample {
5
6 public static void main(String[] args) {
7 try (ExecutorService virtualExecutor = Executors.newVirtualThreadPerTaskExecutor()) {
8 for (int i = 0; i < 10; i++) {
9 int taskNumber = i + 1;
10 Runnable task = () -> taskRunner(taskNumber);
11 virtualExecutor.submit(task);
12 }
13 }
14 }
15
16 private static void taskRunner(int number) {
17 System.out.println("Task " + number + " executed by virtual thread: " + Thread.currentThread());
18 }
19}
Saída desse programa:
1Task 6 executed by virtual thread: VirtualThread[#35]/runnable@ForkJoinPool-1-worker-6
2Task 2 executed by virtual thread: VirtualThread[#31]/runnable@ForkJoinPool-1-worker-2
3Task 10 executed by virtual thread: VirtualThread[#39]/runnable@ForkJoinPool-1-worker-10
4Task 1 executed by virtual thread: VirtualThread[#29]/runnable@ForkJoinPool-1-worker-1
5Task 5 executed by virtual thread: VirtualThread[#34]/runnable@ForkJoinPool-1-worker-5
6Task 7 executed by virtual thread: VirtualThread[#36]/runnable@ForkJoinPool-1-worker-7
7Task 4 executed by virtual thread: VirtualThread[#33]/runnable@ForkJoinPool-1-worker-4
8Task 3 executed by virtual thread: VirtualThread[#32]/runnable@ForkJoinPool-1-worker-3
9Task 8 executed by virtual thread: VirtualThread[#37]/runnable@ForkJoinPool-1-worker-8
10Task 9 executed by virtual thread: VirtualThread[#38]/runnable@ForkJoinPool-1-worker-9
Podemos ver que as tarefas executadas em paralelo — cada uma em um thread virtual diferente, gerenciado por um único ForkJoinPool e seus trabalhadores associados.

programação reativa

Em primeiro lugar, aprogramação reativa é um paradigma de programação, enquanto os threads virtuais são ", apenas ", uma solução técnica. A programação reativa gira em torno de princípios de programação assíncrona e orientada por eventos, oferecendo soluções para gerenciar fluxosde dados e operações assíncronas com eficiência.
Em Java, a programação reativa é Tradicionalmente implementada com o padrão observador.
Os colunas da programação reativa são:
  • Non-blocking I/O.
  • Comunicação assíncrona baseada em fluxo.
  • Manuseio de pressão de retorno para evitar componentes downstream sobrecarregados com mais dados do que eles podem gerenciar.
O único ponto de interesse comum com os threads virtuais é o primeiro: E/S sem bloqueio.

Frameworks de programação reativas

As principais estruturas em Java que seguem os princípios de programação reativa são:

Exemplo

O MongoDB também oferece uma implementação da API Reactive Streams: o MongoDB Reactive Streams Driver.
Aqui está um exemplo em que inserir um documento no MongoDB e, em seguida, recuperá-lo.
1import com.mongodb.client.result.InsertOneResult;
2import com.mongodb.quickstart.SubscriberHelpers.OperationSubscriber;
3import com.mongodb.quickstart.SubscriberHelpers.PrintDocumentSubscriber;
4import com.mongodb.reactivestreams.client.MongoClient;
5import com.mongodb.reactivestreams.client.MongoClients;
6import com.mongodb.reactivestreams.client.MongoCollection;
7import org.bson.Document;
8
9public class MongoDBReactiveExample {
10
11 public static void main(String[] args) {
12 try (MongoClient mongoClient = MongoClients.create("mongodb://localhost")) {
13 MongoCollection<Document> coll = mongoClient.getDatabase("test").getCollection("testCollection");
14
15 Document doc = new Document("reactive", "programming");
16
17 var insertOneSubscriber = new OperationSubscriber<InsertOneResult>();
18 coll.insertOne(doc).subscribe(insertOneSubscriber);
19 insertOneSubscriber.await();
20
21 var printDocumentSubscriber = new PrintDocumentSubscriber();
22 coll.find().first().subscribe(printDocumentSubscriber);
23 printDocumentSubscriber.await();
24 }
25 }
26}
Observação: as SubscriberHelpers.OperationSubscriber SubscriberHelpers.PrintDocumentSubscriber classes e vêm do Guia de início rápido do Reactive Streams. Você pode encontrar o SubscriberHelpers.java nos exemplos de códigodo repositório do driver Java do MongoDB .

Threads virtuais e programação reativa trabalhando juntas

Como você deve ter entendido, threads virtuais e programação reativa não estão competindo entre si, e eles certamente concordam em uma coisa: bloquear operações de E/S é mau!
Quem disse que precisvamos fazer uma escolha? Por que não usar os dois para atingir o desempenho máximo e evitar o bloqueio de E/S de uma vez por todas?
Boas notícias: a biblioteca reactor-coreadicionou suporte a threads virtuais no 3.6.0. Project Reactor é a biblioteca que fornece uma implementação rica e funcional do Reactive Streams APIs no Spring Boot e WebFlux.
Isso significa que podemos usar threads virtuais em um projeto Spring Boot que esteja usando o MongoDB Reactive Streams Driver e Webflux.
No entanto, existem algumas condições:
  • Use o Tomcat porque — enquanto estou escrevendo esta publicação — Netty (usado por padrão pelo Webflux) não suporta threads virtuais.Consulte os problemas12848 e 39425do GitHub para obter mais detalhes.
  • Ativar threads virtuais: spring.threads.virtual.enabled=true em application.properties.

Vamos testar

No repositório, meu colega Wen Jie Teo e eu atualizamos o pom.xml e oapplication.properties para que pudéssemos usar threads virtuais nesse projeto reativo.
Você pode executar os comandos a seguir para executar esse projeto rapidamente e testar se ele está sendo executado corretamente com threads virtuais. Você pode obter mais detalhes no arquivoREADME.md, mas aqui está a essência.
Aqui estão as instruções em inglês:
  • Clone o repositório e acesse a pasta.
  • Atualize o nível de registro em application.properties para info.
  • Inicie uma instância local de conjunto de réplicas de nó único do MongoDB ou use o MongoDB Atlas.
  • Execute o script setup.jspara inicializar a coleçãoaccounts.
  • Inicie o aplicativo Java.
  • Teste uma das APIs disponíveis.
Aqui estão as instruções convertidas para o Bash.
Primeiro terminal:
1git clone git@github.com:mongodb-developer/mdb-spring-boot-reactive.git
2cd mdb-spring-boot-reactive/
3sed -i 's/warn/info/g' src/main/resources/application.properties
4docker run --rm -d -p 27017:27017 -h $(hostname) --name mongo mongo:latest --replSet=RS && sleep 5 && docker exec mongo mongosh --quiet --eval "rs.initiate();"
5mongosh --file setup.js
6mvn spring-boot:run
Observação: no macOS, talvez seja necessário usar sed -i '' 's/warn/info/g' src/main/resources/application.properties se não estiver usando gnu-sed, ou você pode simplesmente editar o arquivo manualmente.
Segundo terminal
1curl 'localhost:8080/account' -H 'Content-Type: application/json' -d '{"accountNum": "1"}'
Se tudo funcionar conforme o planejado, você deverá ver esta linha no primeiro terminal (onde está executando o Spring).
1Stack trace's last line: java.base/java.lang.VirtualThread.run(VirtualThread.java:309) from POST /account
Esta é a última linha no traçado de pilha que estamos registrando. Isso prova que estamos usando threads virtuais para lidar com nossa query.
Se desabilitarmos os threads virtuais no arquivoapplication.properties e tentarmos novamente, leremos:
1Stack trace's last line: java.base/java.lang.Thread.run(Thread.java:1583) from POST /account
Desta vez, estamos usando uma instânciajava.lang.Threadclássica para lidar com nossa consulta.

Conclusão

Threads virtuais e programação reativa não são inimigas fatais. A verdade está muito longe disso.
A combinação das vantagens dos threads virtuais em relação aos threads da plataforma padrão com as melhores práticas de programação reativa abre novas fronteiras de escalabilidade, capacidade de resposta e utilização eficiente de recursos para seus aplicativos. Desapareçam, bloqueando I/Os!
O MongoDB Reactive Streams Driver está totalmente preparado para se beneficiar das otimizações de threads virtuais com o Java 21 e, como sempre, dos princípios de programação reativa e das melhores práticas.
Esperemos que esta publicação o motivou a tentar. Implemente seu cluster no MongoDB Atlas e dê uma volta norepositório .
Para obter mais orientação e suporte, e para interagir com uma comunidade ativa de desenvolvedores, acesse o Fórum do MongoDB, onde você pode encontrar ajuda, compartilhar insights e fazer perguntas. Vamos continuar ultrapassando os limites do desenvolvimento Java juntos!
Principais comentários nos fóruns
Ainda não há comentários sobre este artigo.
Iniciar a conversa

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Artigo
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Tutorial

Desenvolvimento sem servidor com AWS Lambda e MongoDB Atlas usando Java


Jul 20, 2023 | 6 min read
Início rápido

Pipeline de agregação Java


Oct 01, 2024 | 8 min read
Tutorial

Spring Data Unlocked: Começando com Java e MongoDB


Nov 11, 2024 | 5 min read
Início rápido

Java - Change Streams


Oct 01, 2024 | 10 min read
Sumário