[JAVA] Implémentation du traitement asynchrone pour un seul locataire dans Tomcat

Implémentation du traitement asynchrone pour un seul locataire dans Tomcat

Cet article fait partie de "Implémentation du traitement asynchrone dans Tomcat".

introduction

Ici, nous allons implémenter une application Tomcat qui limite le flux des tâches asynchrones à l'aide d'un pool de threads dans un seul locataire.

Logiciel de contrôle de fonctionnement

Nous avons confirmé le fonctionnement dans les environnements logiciels et bibliothèques suivants. Pour les autres bibliothèques dépendantes, veuillez vous référer à pom.xml décrit plus loin.

Création de pom.xml

Créez un projet Maven à l'aide d'Eclipse ou Maven et ajoutez le contenu indiqué ci-dessous à pom.xml. La dépendance spring-boot-starter-actuator est une bibliothèque pour la surveillance Tomcat, mais elle pose un risque de sécurité dans l'environnement de production, alors supprimez-la.

<project xmlns=...>
    :
  <build>
      :
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <version>2.1.4.RELEASE</version>
      </plugin>
    </plugins>
      :
  </build>

  <dependencies>
      :
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <version>1.5.22.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
      <version>1.5.22.RELEASE</version>
    </dependency>
      :
  </dependencies>
</project>

** Figure 3.2 Ajouts à pom.xml **

Créer un fichier de configuration

Créez "application.properties" qui est un fichier de configuration de Spring Boot.

#Surveillance Tomcat
#Paramètres de surveillance de fonctionnement tels que la surveillance et l'arrêt du conteneur Tomcat
#Supprimez-le car il présente un risque de sécurité dans l'environnement de production
endpoints.shutdown.enabled=true
management.security.enabled=false
management.endpoints.web.exposure.include=**

#Pool de threads
#Spécifiez le nombre de tâches asynchrones acceptées et le nombre d'exécutions simultanées
#Interroge autant de tâches que queueCapacity et s'exécute en arrière-plan autant que maxPoolSize
#Maximum "maxPoolSize+Accepte autant de tâches asynchrones que "queueCapacity" et rejette lorsque ce nombre est dépassé(Pas accepté)
#  corePoolSize  =Numéro de filetage initial
#  queueCapacity =Numéro à mettre en file d'attente lorsque corePoolSize est plein
#  maxPoolSize   =Nombre maximum de threads lorsque queueCapacity est dépassé
threadPool.corePoolSize=1
threadPool.queueCapacity=2
threadPool.maxPoolSize=2
threadPool.keepAliveSeconds=1

#Spécifier le niveau de sortie du journal de journalisation
#Spécifiez le niveau de sortie de la journalisation
# https://docs.oracle.com/javase/jp/8/docs/api/java/util/logging/Level.html
logging.level.root=INFO
logging.level.org.springframework.web=INFO
logging.level.web01=INFO

** Figure 3.3 Contenu de application.properties **

--Les références

Création d'une classe POJO pour les paramètres du pool de threads

Créez une classe POJO contenant les paramètres du pool de threads. Spring Framework injecte automatiquement les paramètres commençant par "threadPool" dans application.properties dans cette classe.

@Component
@ConfigurationProperties(prefix = "threadPool")
public class ThreadPoolSettings {
	private String corePoolSize;
	private String queueCapacity;
	private String maxPoolSize;
	private String keepAliveSeconds;

	public void setCorePoolSize(String corePoolSize)         {this.corePoolSize = corePoolSize;}
	public void setQueueCapacity(String queueCapacity)       {this.queueCapacity = queueCapacity;}
	public void setMaxPoolSize(String maxPoolSize)           {this.maxPoolSize = maxPoolSize;}
	public void setKeepAliveSeconds(String keepAliveSeconds) {this.keepAliveSeconds = keepAliveSeconds;}

	public int getCorePoolSize()     {return Integer.valueOf(corePoolSize);}
	public int getQueueCapacity()    {return Integer.valueOf(queueCapacity);}
	public int getMaxPoolSize()      {return Integer.valueOf(maxPoolSize);}
	public int getKeepAliveSeconds() {return Integer.valueOf(keepAliveSeconds);}
}

** Figure 3.4 Contenu de la classe ThreadPoolSettings **

Créer une classe de lancement Tomcat

Créez une classe Application qui lance le conteneur Tomcat intégré.

@SpringBootApplication est automatiquement ajouté à Spring Framework @Configuration et [@Component](https://docs.spring.io/ Il vous demande d'analyser les classes telles que spring-framework / docs / current / javadoc-api / org / springframework / stéréotype / Component.html). @Repository, [@Service](https: //docs.spring .io / spring-framework / docs / current / javadoc-api / org / springframework / stéréotype / Service.html), @Controller -api / org / springframework / stéréotype / Controller.html) est également @Component ), Donc ceux-ci sont également détectés. Cependant, veuillez noter que par défaut, la cible d'analyse est limitée au ou aux mêmes packages sous celle-ci.

@SpringBootApplication
public class Application {
    public static void main(final String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

** Figure 3.5 Contenu de la classe Application **

--Les références

@Configuration Création d'une classe

Créez une classe SampleConfig qui initialise l'application au démarrage de Tomcat.

Déclarez cette classe comme classe par défaut @Configuration /Configuration.html) et @EnableAsync pour activer les appels asynchrones Il y a EnableAsync.html). @Bean dans taskExecutor1 () est un Bean dans Spring Framework. Est de s'inscrire. Par défaut, le nom du bean est le même que le nom de la méthode, où "taskExecutor" est le nom du bean.

taskExecutor1 () crée un ThreadPoolTaskExecutor, un pool de threads avec des files d'attente. En modifiant la valeur de paramètre de [application.properties](#creation of setting file) mentionnée ci-dessus en fonction du nombre de cœurs et de la capacité mémoire de l'environnement d'exploitation, la capacité de la file d'attente et du pool de threads peut être réglée.

De plus, les threads détenus par le pool de threads doivent être "** non-demons **" afin que les threads en cours d'exécution ne soient pas terminés lorsque Tomcat est terminé. ThreadPoolTaskExecutor n'est pas damon par défaut, mais est explicitement spécifié ici. Cependant, l'arrêt du pool de threads détruit toutes les tâches qui se trouvent dans la file d'attente. Si vous souhaitez que les tâches bloquées dans la file d'attente soient exécutées lorsque vous redémarrez l'application Tomcat, une implémentation supplémentaire est requise, mais je l'omettrai ici.

@Configuration
@EnableAsync
public class SampleConfig {
	//application.de propriétés"threadPool"Variable contenant la valeur de réglage commençant par
	@Autowired
	ThreadPoolSettings threadPoolSettings;

	//@Enregistrer le pool de threads pour Async en tant que Spring Bean
	//Le nom du bean sera "taskExecutor1" qui est le même que le nom de la méthode.
	@Bean
	public ThreadPoolTaskExecutor taskExecutor1() {
		final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		executor.setCorePoolSize(threadPoolSettings.getCorePoolSize());
		executor.setQueueCapacity(threadPoolSettings.getQueueCapacity());
		executor.setMaxPoolSize(threadPoolSettings.getMaxPoolSize());
		executor.setKeepAliveSeconds(threadPoolSettings.getKeepAliveSeconds());
		executor.setThreadNamePrefix("Executor1-");
		executor.setDaemon(false);
		executor.initialize();
		return executor;
	}
}

** Figure 3.6 Contenu de la classe SampleConfig **

@Service Création d'une classe

Créez la classe SampleService illustrée ci-dessous.

@Service de cette classe est de DDD (Domain-Driven Design). C'est l'un des composants et porte la dette de traiter la logique métier.

cmd () lance la commande spécifiée par l'argument et renvoie un objet Future qui peut vérifier l'état d'exécution de la commande sans blocage. @Async pour déclarer qu'il est la cible d'un appel asynchrone ) Est ajouté et le paramètre "taskExcecutor1" est ajouté pour se lier au pool de threads défini dans la classe SampleConfig.

@Service
public class SampleService {
	@Async("taskExecutor1")
	public CompletableFuture<String> cmd(final String[] command) {
		final int exitValue = run(command);
		return CompletableFuture.completedFuture("exitValue="+exitValue);
	}

	//Exécuter une commande externe
	public static int run(final String[] command) {
		try{
			final Process p = new ProcessBuilder(command).redirectErrorStream(true).start();
			try(final BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()))){
				String str;
				while((str = r.readLine()) != null) {
					System.out.println("Command output: "+str);
				}
			}
			return p.waitFor();
		}catch(IOException | InterruptedException e) {
			e.printStackTrace();
			return -1;
		}
	}
}

** Figure 3.7 Contenu de la classe SampleService **

@Controller Création d'une classe

Créez la classe SampleController illustrée ci-dessous.

@Controller correspond au contrôleur du modèle MVC et provient du client. Assumez la dette pour répondre à la demande.

Un champ avec @Autowired ajouté à cette classe Il existe @Component et [@Bean](https: / Demande à Spring Framework d'injecter automatiquement les pièces définies dans (/docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html) etc. Chose.

cmd () appelle cmd () de SampleService en réponse à "GET http: //.../ cmd". Cela ressemble à un appel synchrone, mais l'appelé est [@Async](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/Async. Depuis que html) est ajouté, Spring Framework le change automatiquement en appel asynchrone.

Si la file d'attente ou les threads du pool de threads sont pleins et ne peuvent pas accepter le traitement asynchrone, une RejectedExecutionException sera levée. Cela permet de contrôler le débit du traitement asynchrone. Ici, par souci de simplicité, la chaîne "Rejected" est renvoyée au client, mais à l'origine le HTTP "429 Too Many Requests" Ce serait une bonne idée de renvoyer un code de statut tel que ".

En réponse à "GET http: //.../ status", status () analyse l'objet Future renvoyé par cmd () de SampleService et renvoie l'état d'exécution de l'appel asynchrone sous forme de chaîne de caractères.

@Controller
public class SampleController {
	private static final String[] COMMAND = new String[] {"cmd", "/c", "ping", "-n", "10", "localhost", ">", "nul"};
	private AtomicInteger counter = new AtomicInteger(1);
	private HashMap<String, Future<String>> futures = new HashMap<>();

	@Autowired
	private SampleService sampleService;

	@RequestMapping(value="/cmd", method=RequestMethod.GET)
	@ResponseBody
	public String cmd() {
		final String id = String.format("<noTenantId>#%03d", counter.getAndIncrement());
		try{
			final CompletableFuture<String> future = sampleService.cmd(COMMAND)
				.exceptionally(ex -> id+": Exception: "+ex.getMessage()+"\n");
			synchronized(this.futures) {
				this.futures.put(id, future);
			}
			return "Accepted: id="+id+"\n";
		}catch(RejectedExecutionException e) {
			final CompletableFuture<String> future = new CompletableFuture<>();
			future.complete("Rejected");
			synchronized(this.futures) {
				this.futures.put(id, future);
			}
			return "Rejected: id="+id+"\n";
		}
	}

	@RequestMapping(value="/status", method=RequestMethod.GET)
	@ResponseBody
	public String status() {
		final Map<String, Future<String>> map;
		synchronized(this.futures) {
			map = (Map<String, Future<String>>)this.futures.clone();
		}
		return map.entrySet().stream()
			.map(entry->SampleController.futureGet(entry.getKey(), entry.getValue()))
			.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
			.toString();
	}

	//Convertir l'état des tâches asynchrones en chaînes
	private static String futureGet(String tenantId, Future<String> future) {
		if(future.isDone()==false && future.isCancelled()==false) {
			return tenantId+": Running\n";
		}else if(future.isCancelled()) {
			return tenantId+": Canceled\n";
		}
		try {
			return tenantId+": "+future.get()+"\n";
		} catch (InterruptedException | ExecutionException | CancellationException e) {
			return tenantId+": Exception\n";
		}
	}
}

** Figure 3.8 Contenu de la classe SampleController **

Contrôle de fonctionnement

Vous pouvez démarrer le conteneur Tomcat localement et exécuter la commande comme suit pour vérifier l'opération.

$ curl -X GET http://localhost:8080/cmd
Accepted: id=<noTenantId>#001

#Après avoir attendu un peu plus de 10 secondes/OBTENIR l'état
$ curl -X GET http://localhost:8080/status
<noTenantId>#001: exitValue=0

#Terminaison normale du conteneur Tomcat
$ curl -X POST http://localhost:8080/shutdown

** Figure 3.9 Confirmation du fonctionnement de la tâche asynchrone **

Passez à "Implémentation du traitement asynchrone multi-tenant dans Tomcat" ...

Recommended Posts

Implémentation du traitement asynchrone pour un seul locataire dans Tomcat
Implémentation du traitement asynchrone dans Tomcat
Implémentation du traitement asynchrone compatible multi-tenant dans Tomcat
Implémentation Java de tri-tree
Implémentation de HashMap avec kotlin
[Rails] Implémentation asynchrone de la fonction similaire
Ordre de traitement dans le programme
Implémentation d'une fonction similaire en Java
Implémentation de la méthode de clonage pour Java Record
Implémentation de DBlayer en Java (RDB, MySQL)
[Pour moi-même] Transfert du traitement de classe de servlet
Rails [Pour les débutants] Implémentation de la fonction de commentaire
Traitement des threads de l'interface utilisateur asynchrone Android
Comment implémenter le traitement asynchrone dans Outsystems
[Swift, un incontournable pour les débutants! ] Comprenons la mise en œuvre du traitement des communications de l'API Web
Implémentation du regroupement de chiffres dans l'application Furima
Mémo d'implémentation SKStoreReviewController dans l'interface utilisateur Swift d'iOS14
[Rails] Mise en œuvre de "notifier la notification d'une manière ou d'une autre"
Traitement de lecture / actualisation / mise à jour des données dans TableView