Começando com Virtual Threadas
Entender como usar Virtual Threads com Spring Boot, Batch e aplicações standalone
Ignorado
From Oracle Virtual Threads são threads leves que reduzem o esforço de gravação, manutenção e depuração de aplicativos simultâneos de alto rendimento.
Para obter informações básicas sobre threads virtuais, consulte JEP 444.
Uma Thread é a menor unidade de processamento que pode ser agendada. Funciona simultaneamente com e em grande parte independente de outras unidades semelhantes. É uma instância de java.lang.Thread.
É recomendado quando seu sistema não está utilizando a CPU, ou seja, para I/O bound.
O que significa I/O bound?
From Wikipedia, refere-se a uma condição na qual o tempo necessário para concluir um cálculo é determinado principalmente pelo período gasto aguardando a conclusão das operações de entrada/saída, que pode ser justaposto ao limite da CPU. Esta circunstância surge quando a velocidade a que os dados são solicitados é mais lenta do que a velocidade com que são consumidos ou, por outras palavras, é gasto mais tempo a solicitar dados do que a processá-los.
Exemplo de cenários baseado nos vídeos do Dan Vega
- Transferência de dinheiro
- Processador de pagamento
Para executar esses projetos localmente você deve ter o Java 21 instalado. Minha recomendação é usar SDKMAN!.
Exemplo da minha versão, Amazon Corretto 21.0.2, instalado via SKDMAN!.
sdk list java | grep installed
Corretto | >>> | 21.0.2 | amzn | installed | 21.0.2-amzn
- Baixe o projeto através do GitHub usando Git. Se você não tem o Git instalado, leia this tutorial first.
git clone https://github.com/luizgustavocosta/virtual-threads.git
cd virtual-threads
- Navegando pelos projetos
# | Nome | Tipo | Depende do item | Como executar? |
---|---|---|---|---|
1 | bank-service | Microsserviço | 5,7 | Aqui |
2 | batch-processing | Batch | - | Aqui |
3 | k6 | Teste | 1 | Aqui |
4 | resources | Arquivos para documentação | - | N/A |
5 | risk-service | Microsserviço | - | Aqui |
6 | standalone | Programa | - | Aqui |
7 | transfer-service | Microsserviço | - | Aqui |
- Somente para os microsserviços
- Build the image described in each microservice README.md file and then
docker-compose -f docker-compose.yaml -p virtual-threads up
# | Nome | Versão |
---|---|---|
1 | Java | Amazon Corretto 21.0.2.13.1 |
2 | IntelliJ | 2023.2.5 (Ultimate Edition) |
3 | k6 | 0.39.0 |
4 | Spring Boot | 3.2.2 |
5 | Maven | 3.6.3 |
6 | Archimate | 3.2 |
7 | Docker Engine | 20.10.21 |
8 | Git | 2.24.3 |
9 | ApacheBench | 2.3 |
50 usuários, i.e., 50 requisições usando o Thread-Per-Request Model, e o servidor pode tratar 10 simutaneamente. Se você precisar de uma visão geral sobre o Thread-Per-Request-Model, verifique os diagramas abaixo.
Para cada solicitação do cliente, ou seja, o mesmo cliente envia a enésima solicitação, um thread será alocado pelo Tomcat Web Server para tratar a solicitação/resposta.
No modelo de thread tradicional, um thread do aplicativo está vinculado ao thread do sistema operacional.
Quando Client A chamar o aplicativo, a Thread 1 será vinculada a esta solicitação/resposta. O mesmo acontece com o Client B, a sua solicitação será tratada pela Thread 10, usando como exemplo a nossa aplicação com 10 Threads.
Este modelo funciona bem, até que o servidor fique sem threads disponíveis. Quando todos as threads do servidor estão ocupados, o Client X precisa aguardar a próximo thread seja liberada. Ninguém gosta disso.
Com Virtual Threads, todas as solicitações serão tratadas na chegada, aumentando a capacidade de resposta do servidor.
Abaixo você pode encontrar o mesmo teste para 2 cenários usando 2 ferramentas diferentes, k6 e Apache Benchmarking (aprendido no vídeo do Dan Vega no YouTube)
- Abra o arquivo application.yaml e faça as seguintes alterações:
- Defina o número de threads do Tomcat como
10
. O valor padrão é200
- Defina a propriedade
spring.threads.virtual.enabled
paratrue
- Defina o número de threads do Tomcat como
server:
tomcat:
threads:
max: '10'
spring:
threads:
virtual:
enabled: 'true' #'false' dependendo do cenário de teste
- k6
Lembre-se de definir a propriedade spring.threads.virtual.enabled
como false
e iniciar os serviços bank, risk e transfer.
luizcosta@MacBook-Pro-de-Luiz k6 % k6 run --out json=test.json load_test.js
/\ |‾‾| /‾‾/ /‾‾/
/\ / \ | |/ / / /
/ \/ \ | ( / ‾‾\
/ \ | |\ \ | (‾) |
/ __________ \ |__| \__\ \_____/ .io
execution: local
script: load_test.js
output: json (test.json)
scenarios: (100.00%) 1 scenario, 50 max VUs, 10m30s max duration (incl. graceful stop):
* default: 400 iterations shared among 50 VUs (maxDuration: 10m0s, gracefulStop: 30s)
running (02m41.2s), 00/50 VUs, 400 complete and 0 interrupted iterations
default ✓ [======================================] 50 VUs 02m41.2s/10m0s 400/400 shared iters
INFO[0162] [k6-reporter v2.3.0] Generating HTML summary report source=console
✓ status was 201
checks.........................: 100.00% ✓ 400 ✗ 0
data_received..................: 65 kB 402 B/s
data_sent......................: 87 kB 541 B/s
errorRate......................: 0.00% ✓ 0 ✗ 400
http_req_blocked...............: avg=385.35µs min=2µs med=6µs max=4.43ms p(90)=2.11ms p(95)=3.25ms
http_req_connecting............: avg=227.6µs min=0s med=0s max=2.56ms p(90)=1.55ms p(95)=1.94ms
http_req_duration..............: avg=19.13s min=4.01s med=20.12s max=28.5s p(90)=24.15s p(95)=24.18s
{ expected_response:true }...: avg=19.13s min=4.01s med=20.12s max=28.5s p(90)=24.15s p(95)=24.18s
http_req_failed................: 0.00% ✓ 0 ✗ 400
http_req_receiving.............: avg=112.41µs min=27µs med=75µs max=2.71ms p(90)=168µs p(95)=242.14µs
http_req_sending...............: avg=49.65µs min=15µs med=34µs max=559µs p(90)=89.1µs p(95)=143.14µs
http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
http_req_waiting...............: avg=19.13s min=4.01s med=20.12s max=28.5s p(90)=24.15s p(95)=24.18s
http_reqs......................: 400 2.481883/s
iteration_duration.............: avg=19.14s min=4.01s med=20.13s max=28.5s p(90)=24.15s p(95)=24.18s
iterations.....................: 400 2.481883/s
vus............................: 10 min=10 max=50
vus_max........................: 50 min=50 max=50% luizcosta@MacBook-Pro-de-Luiz k6 %
Lembre-se de definir a propriedade spring.threads.virtual.enabled
como true
e reiniciar o serviço do bank.
luizcosta@MacBook-Pro-de-Luiz k6 % k6 run --out json=test.json load_test.js
/\ |‾‾| /‾‾/ /‾‾/
/\ / \ | |/ / / /
/ \/ \ | ( / ‾‾\
/ \ | |\ \ | (‾) |
/ __________ \ |__| \__\ \_____/ .io
execution: local
script: load_test.js
output: json (test.json)
scenarios: (100.00%) 1 scenario, 50 max VUs, 10m30s max duration (incl. graceful stop):
* default: 400 iterations shared among 50 VUs (maxDuration: 10m0s, gracefulStop: 30s)
running (00m33.1s), 00/50 VUs, 400 complete and 0 interrupted iterations
default ✓ [======================================] 50 VUs 00m33.0s/10m0s 400/400 shared iters
INFO[0033] [k6-reporter v2.3.0] Generating HTML summary report source=console
✓ status was 201
checks.........................: 100.00% ✓ 400 ✗ 0
data_received..................: 65 kB 2.0 kB/s
data_sent......................: 87 kB 2.6 kB/s
errorRate......................: 0.00% ✓ 0 ✗ 400
http_req_blocked...............: avg=506.84µs min=1µs med=4µs max=5.24ms p(90)=3.42ms p(95)=4.26ms
http_req_connecting............: avg=266.33µs min=0s med=0s max=2.69ms p(90)=1.94ms p(95)=2.25ms
http_req_duration..............: avg=4.12s min=4.01s med=4.04s max=4.7s p(90)=4.63s p(95)=4.65s
{ expected_response:true }...: avg=4.12s min=4.01s med=4.04s max=4.7s p(90)=4.63s p(95)=4.65s
http_req_failed................: 0.00% ✓ 0 ✗ 400
http_req_receiving.............: avg=76.85µs min=16µs med=51.5µs max=879µs p(90)=118.1µs p(95)=173.54µs
http_req_sending...............: avg=44.65µs min=7µs med=24µs max=402µs p(90)=86.3µs p(95)=186.49µs
http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
http_req_waiting...............: avg=4.12s min=4.01s med=4.04s max=4.69s p(90)=4.63s p(95)=4.65s
http_reqs......................: 400 12.102768/s
iteration_duration.............: avg=4.12s min=4.01s med=4.04s max=4.7s p(90)=4.64s p(95)=4.66s
iterations.....................: 400 12.102768/s
vus............................: 31 min=31 max=50
vus_max........................: 50 min=50 max=50% luizcosta@MacBook-Pro-de-Luiz k6 %
Comparando os 2 logs, podemos ver claramente que como Virtual Threads resolvem todas as requisições 4,5x mais rápido que no cenário com Platform Threads.
Lembre-se de que apenas aplicar as configurações
# | Configuração | Requisições | Concorrente | N Threads no servidor | Tempo gasto para os testes em segundos |
---|---|---|---|---|---|
1 | Platform Threads | 400 | 50 | 10 | 181.2 |
2 | Virtual Threads | 400 | 50 | 10 | 33 |
- Apache Benchmarking
Mesmas recomendações das etapas k6
O comando abaixo é para chamar o endpoint http://localhost:8080/v1/transfers
400 vezes usando 50 solicitações simultâneas e aguardar 30 segundos de tempo limite
ab -n 400 -c 50 -s 30 -p 'resources/payload/transfer.json' -T 'application/json' http://localhost:8080/v1/transfers
Lembre-se de definir a propriedade spring.threads.virtual.enabled
como false
e iniciar os serviços bank, risk e transfer.
luizcosta@MacBook-Pro-de-Luiz virtual-threads % ab -n 400 -c 50 -s 30 -p 'resources/payload/transfer.json' -T 'application/json' http://localhost:8080/v1/transfers
This is ApacheBench, Version 2.3 <$Revision: 1903618 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Finished 400 requests
Server Software:
Server Hostname: localhost
Server Port: 8080
Document Path: /v1/transfers
Document Length: 37 bytes
Concurrency Level: 50
Time taken for tests: 165.570 seconds
Complete requests: 400
Failed requests: 0
Total transferred: 68000 bytes
Total body sent: 86800
HTML transferred: 14800 bytes
Requests per second: 2.42 [#/sec] (mean)
Time per request: 20696.242 [ms] (mean)
Time per request: 413.925 [ms] (mean, across all concurrent requests)
Transfer rate: 0.40 [Kbytes/sec] received
0.51 kb/s sent
0.91 kb/s total
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 0.9 1 5
Processing: 4046 19101 3414.3 20135 20225
Waiting: 4046 19100 3414.4 20134 20221
Total: 4048 19102 3414.0 20135 20225
Percentage of the requests served within a certain time (ms)
50% 20135
66% 20146
75% 20155
80% 20168
90% 20198
95% 20211
98% 20219
99% 20222
100% 20225 (longest request)
luizcosta@MacBook-Pro-de-Luiz virtual-threads
Lembre-se de definir a propriedade spring.threads.virtual.enabled
como true
e reiniciar o serviço do bank.
luizcosta@MacBook-Pro-de-Luiz virtual-threads % ab -n 400 -c 50 -s 30 -p 'resources/payload/transfer.json' -T 'application/json' http://localhost:8080/v1/transfers
This is ApacheBench, Version 2.3 <$Revision: 1903618 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Finished 400 requests
Server Software:
Server Hostname: localhost
Server Port: 8080
Document Path: /v1/transfers
Document Length: 37 bytes
Concurrency Level: 50
Time taken for tests: 36.677 seconds
Complete requests: 400
Failed requests: 0
Total transferred: 68000 bytes
Total body sent: 86800
HTML transferred: 14800 bytes
Requests per second: 10.91 [#/sec] (mean)
Time per request: 4584.575 [ms] (mean)
Time per request: 91.692 [ms] (mean, across all concurrent requests)
Transfer rate: 1.81 [Kbytes/sec] received
2.31 kb/s sent
4.12 kb/s total
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 1.3 1 8
Processing: 4011 4047 26.8 4038 4233
Waiting: 4010 4046 26.8 4037 4230
Total: 4011 4049 27.1 4040 4233
Percentage of the requests served within a certain time (ms)
50% 4040
66% 4058
75% 4067
80% 4069
90% 4092
95% 4101
98% 4117
99% 4120
100% 4233 (longest request)
luizcosta@MacBook-Pro-de-Luiz virtual-threads
Comparando os 2 logs, podemos ver claramente como Virtual Threads resolvem a situação simultânea 4,5x mais rápido. Lembre-se de que apenas aplicar uma configuração
# | Configuração | Requisições | Concorrentes | Server Threads | Time taken for tests in seconds |
---|---|---|---|---|---|
1 | Platform Threads | 400 | 50 | 10 | 165.570 |
2 | Virtual Threads | 400 | 50 | 10 | 36.677 |
Dentro do Step, chame o taskExecutor e envie como parâmetro o código abaixo. Voilà, Virtual Threads em Ação para Spring Batch
.taskExecutor(new VirtualThreadTaskExecutor("VirtualThread-"))
- JEP-444
- Spring Blog
- YouTube - Java 21 new feature: Virtual Threads
- YouTube - Devoxx Belgium - Venkat Subramaniam
- AirHacks Podcast
- YouTube - Spring Batch
- Embracing Virtual Threads
- YouTube - Dan Vega
- YouTube - TheDevConf - Eder Moraes
- Apache Benchmarking
- Unlocking High Performance with Java Virtual Threads