Adopter les threads virtuels dans Spring Boot


Dans ce blog, nous verrons comment nous pouvons tirer parti des threads virtuels de métier à tisser de projet dans Spring-Boot. Nous ferons également des tests de charge avec l’aide de Jmètre et voyez le temps de réponse pour les threads virtuels et les threads normaux.
Configurons rapidement notre projet de démarrage de printemps.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.0</version>
<relativePath/>
</parent>
<groupId>org.anil</groupId>
<artifactId>virtualthread</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>virtualthread</name>
<description>virtualthread</description>
<properties>
<java.version>20</java.version>
<tomcat.version>11.0.0-M4</tomcat.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<arg>--enable-preview</arg>
</compilerArgs>
<source>20</source>
<target>20</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Nous devons activer les fonctionnalités de prévisualisation puisque Project Loom est en phase de prévisualisation. Nous devons attendre la sortie de Java 21 pour qu’il devienne une fonctionnalité finale.
package org.anil.virtualthread;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer;
import org.springframework.context.annotation.Bean;
import java.util.concurrent.Executors;
@SpringBootApplication
@Slf4j
public class VirtualthreadApplication {
public static void main(String[] args) {
SpringApplication.run(VirtualthreadApplication.class, args);
}
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
return protocolHandler -> {
log.info("Configuring " + protocolHandler + " to use VirtualThreadPerTaskExecutor");
protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
};
}
}
À partir de maintenant, nous devons configurer les paramètres de thread virtuel pour le serveur Tomcat. À l’avenir, cela pourra être pris en charge dans la configuration automatique elle-même.
package org.anil.virtualthread;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class HomeController {
@Autowired
ProductRepository productRepository;
@GetMapping("/thread")
public List<Product> checkThread() throws InterruptedException {
Thread.sleep(1000);
return productRepository.findAll();
}
@PostMapping("/save")
public String saveProduct() throws InterruptedException {
for(int i=0; i< 1000; i++){
Product product = new Product();
product.setProductName(RandomStringUtils.randomAlphanumeric(5));
product.setPrice(RandomUtils.nextLong(10,1000));
product.setPrice(1L);
productRepository.save(product);
}
return "anil";
}
}
Nous avons un GetMapping qui renvoie tous les produits ; nous avons 1000 Des produits dans notre base de données.
Nous avons fait dormir notre fil pendant 1 seconde. Voyons notre Produit entité et référentiel de produits aussi.
package org.anil.virtualthread;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter
@Setter
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String productName;
private Long price;
}
package org.anil.virtualthread;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProductRepository extends JpaRepository<Product,Long> {
}
Voyons notre application.yaml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
maxIdle: 1
timeBetweenEvictionRunsMillis: 60000
hikari:
connection-timeout: 60000
maximum-pool-size: 10
minimum-idle: 5
url: jdbc:mysql://localhost:3306/todos
testWhileIdle: true
username: root
password: root1234
validationQuery: SELECT 1
flyway:
baseline-version: 0
enabled: true
validate-on-migrate: false
jpa:
database: mysql
generate-ddl: true
hibernate:
ddl-auto: none
format_sql: true
show-sql: true
Exécutons d’abord l’application en commentant la ligne suivante : Cela exécutera notre application sur des threads normaux.
package org.anil.virtualthread;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer;
import org.springframework.context.annotation.Bean;
import java.util.concurrent.Executors;
@SpringBootApplication
@Slf4j
public class VirtualthreadApplication {
public static void main(String[] args) {
SpringApplication.run(VirtualthreadApplication.class, args);
}
}
Configurons maintenant notre Jmètre. Nous aurons 1000 requêtes, qui augmenteront en 3 secondes. Et cela continuera ainsi pendant une durée de 200 secondes. Toutes les 3 secondes, 1000 requêtes GET (« /thread ») seront déclenchées. Nous avons également ajouté un écouteur de graphique de temps de réponse.

Maintenant, lançons notre test et attendons 200 secondes.

Dans le graphique, nous pouvons voir qu’une fois que tout le pool de threads de Tomcat est utilisé, le temps de réponse passe de 3600 ms à 5200 ms. Depuis lors, il est resté comme ça uniquement lorsque les fils précédents sont publiés.
Exécutons maintenant un test de charge avec la fonctionnalité de thread virtuel activée.
package org.anil.virtualthread;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer;
import org.springframework.context.annotation.Bean;
import java.util.concurrent.Executors;
@SpringBootApplication
@Slf4j
public class VirtualthreadApplication {
public static void main(String[] args) {
SpringApplication.run(VirtualthreadApplication.class, args);
}
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
return protocolHandler -> {
log.info("Configuring " + protocolHandler + " to use VirtualThreadPerTaskExecutor");
protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
};
}
}
Maintenant, lançons notre test et attendons 200 secondes.

De toute évidence, maintenant, le temps de réponse pour 1000 requêtes simultanées est presque juste au-dessus de 1000 ms et à un moment donné, il atteint 1400 secondes, ce qui est bien meilleur que lorsque nous utilisions des threads normaux.
De toute évidence, lorsque nous devons utiliser au maximum le processeur sous-jacent, nous devons commencer à adopter des threads virtuels dans notre application, et soudain, nous pouvons voir que le débit de notre application a augmenté considérablement pour le même matériel.
C’est bien mieux que de passer à la programmation réactive, ce qui signifie réécrire tout votre code, ce qui est très difficile à maintenir et encore plus difficile à déboguer.
En termes simples, davantage d’utilisateurs peuvent utiliser l’application et obtenir leur réponse simultanément en tant que premier utilisateur.
Vous recherchez une analyse approfondie et des avis d’experts? Découvrez nos autres ressources maintenant.
Le lien du message d’origine est https://medium.com/@anil.java.story/embracing-virtual-threads-in-spring-boot-4140d3b8a5a
TROUVÉ CELA UTILE ? PARTAGEZ-LE
Source link