Cet article fait partie de "Implémentation du traitement asynchrone dans Tomcat".
Dans "Implémentation du traitement asynchrone dans Tomcat", [@Async](https://docs.spring.io/spring-framework/docs/current/ J'ai introduit que vous pouvez contrôler le flux des tâches asynchrones en combinant javadoc-api / org / springframework / scheduling / annotation / Async.html) et le pool de threads. Cependant, il existe une correspondance univoque entre @Async et le pool de threads. Par conséquent, il n'est pas possible de prendre en charge le multi-tenant.
Multi-tenant est un mécanisme permettant de fournir des services à plusieurs clients avec une seule instance d'application. Par exemple, lorsque l'ID de locataire est spécifié comme suit, les locataires «t1» et «t2» sont affectés à différents pools de threads et différentes ressources / actifs sont utilisés pour chaque locataire, comme l'accès à différents schémas du magasin de données. C'est une image que vous faites.
- 「GET http://localhost/async?tenantId=t1」
- 「GET http://localhost/async?tenantId=t2」
** Figure 4.0 Requête HTTP pour multi-tenant **
Alors, réfléchissons à la façon d'implémenter le traitement asynchrone pour le multi-tenant. Nous vous présenterons également que certaines parties ne relèvent pas de la gestion de Spring Framework et que vous devez implémenter vous-même la gestion du cycle de vie du pool de threads.
Créez une classe ThreadPoolTaskExecutors qui gère un pool de threads multi-locataires.
Dans cette classe, le type Map avec l'ID de locataire et le pool de threads comme valeur-clé est conservé dans le champ et getExecutor () renvoie le pool de threads correspondant à l'ID de locataire. Cependant, enregistrer cette classe en tant que bean ne signifie pas que Spring Framework reconnaîtra qu'elle contient un pool de threads.
Par conséquent, pour arrêter le pool de threads par vous-même, détruisez avec @PreDestroy ajouté. () Est ajouté. Puisque la méthode de @PreDestroy est rappelée lorsque Tomcat se termine normalement, le thread est threadé à ce moment. Il ferme la piscine. Cependant, [Create @Configuration class](https://qiita.com/sipadan2003/items/3277024b7da63c844663#configuration%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE% Notez que cet arrêt détruit également les tâches qui sont dans la file d'attente, comme mentionné dans E4% BD% 9C% E6% 88% 90).
public class ThreadPoolTaskExecutors {
private Map<String, ThreadPoolTaskExecutor> map = new HashMap<>();
public ThreadPoolTaskExecutors(ThreadPoolSettings settings) {
//Créer un pool de threads pour chaque client
map.put("Tenant1", newExecutor("Tenant1", settings));
map.put("Tenant2", newExecutor("Tenant2", settings));
map.put("Tenant3", newExecutor("Tenant3", settings));
}
//Créer un pool de threads
private static ThreadPoolTaskExecutor newExecutor(String tenantId, ThreadPoolSettings settings) {
final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(settings.getCorePoolSize());
executor.setQueueCapacity(settings.getQueueCapacity());
executor.setMaxPoolSize(settings.getMaxPoolSize());
executor.setKeepAliveSeconds(settings.getKeepAliveSeconds());
executor.setThreadNamePrefix("Executor2-"+tenantId+"-");
executor.setDaemon(false);
executor.initialize();
return executor;
}
//Renvoie le pool de threads correspondant à l'ID de locataire
public ThreadPoolTaskExecutor getExecutor(String tenantId) {
return map.get(tenantId);
}
//Arrêtez le pool de threads
@PreDestroy
private void destroy() {
this.map.values().forEach(ThreadPoolTaskExecutor::shutdown);
}
}
** Figure 4.1 Contenu de la classe ThreadPoolTaskExecutors **
[SampleConfig] mentionné ci-dessus (https://qiita.com/sipadan2003/items/3277024b7da63c844663#configuration%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE%E4%BD% 9C% E6% 88% 90) Ajoutez taskExecutors2 () à la classe comme indiqué ci-dessous. Cette méthode utilise @Bean pour [multi-tenant Enregistrez l'objet de gestion du pool de threads pour (#Add Thread Pool Management Class) en tant que bean.
public class SampleConfig {
:
@Bean
public ThreadPoolTaskExecutors taskExecutors2() {
return new ThreadPoolTaskExecutors(threadPoolSettings);
}
** Figure 4.2 Contenu de SampleConfig # taskExecutors2 () **
[Sample Service] mentionné ci-dessus (https://qiita.com/sipadan2003/items/3277024b7da63c844663#service%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE%E4%BD% 9C% E6% 88% 90) Ajoutez cmd2 () à la classe comme indiqué ci-dessous. Contrairement à cmd (), @Async n'est pas ajouté et retourne. La valeur est un objet String, pas un Future.
public class SampleService {
:
public String cmd2(final String[] command) {
final int exitValue = run(command);
return "exitValue="+exitValue;
}
:
}
** Figure 4.3 Contenu de SampleService # cmd2 () **
[Sample Controller] mentionné ci-dessus (https://qiita.com/sipadan2003/items/3277024b7da63c844663#controller%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE%E4%BD% 9C% E6% 88% 90) Remettez à neuf la classe. Tout d'abord, ajoutez le champ taskExecutors2. Beans enregistrés dans la classe SampleConfig car il existe @Autowired Est automatiquement injecté.
public class SampleController {
:
@Autowired
private ThreadPoolTaskExecutors taskExecutors2;
:
}
** Figure 4.4.1 Contenu de SampleService # taskExecutors2 **
Ensuite, ajoutez une méthode qui appelle cmd2 () de SampleService. Le flux de traitement de cette méthode est le même que celui de la méthode cmd (), sauf qu'elle obtient le pool de threads et effectue explicitement des appels asynchrones.
public class SampleController {
:
@RequestMapping(value="/cmd2", method=RequestMethod.GET)
@ResponseBody
public String cmd2(@RequestParam(value="id", required=true)final String id) {
final String tenantId = String.format("%s#%03d", id, counter.getAndIncrement());
//Obtenir ThreadPool
final ThreadPoolTaskExecutor executor = taskExecutors2.getExecutor(id);
if(executor == null) {
return "Invalid id: id="+id+"\n";
}
try{
final Future<String> future = executor.submit(()->{
return sampleService.cmd2(COMMAND); //Service téléphonique
});
synchronized(this.futures) {
this.futures.put(tenantId, future);
}
return "Accepted: id="+tenantId+"\n";
}catch(RejectedExecutionException e) {
final CompletableFuture<String> future = new CompletableFuture<>();
future.complete("Rejected");
synchronized(this.futures) {
this.futures.put(tenantId, future);
}
return "Rejected: id="+tenantId+"\n";
}
}
** Figure 4.4.2 Contenu de SampleService # cmd2 () **
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/cmd2?id=Tenant1
Accepted: id=Tenant1#001
$ curl -X GET http://localhost:8080/cmd2?id=Tenant1
Accepted: id=Tenant1#001
$ curl -X GET http://localhost:8080/cmd2?id=Tenant1
Accepted: id=Tenant1#001
$ curl -X GET http://localhost:8080/cmd2?id=Tenant1
$ curl -X GET http://localhost:8080/cmd2?id=Tenant1
Rejected: id=Tenant1#001
$ curl -X GET http://localhost:8080/cmd2?id=Tenant1
Rejected: id=Tenant1#001
#Le pool de threads est "maxPoolSize"=2」、「queueCapacity=Lorsque "2"
#Après avoir attendu un peu plus de 10 secondes/Lorsque vous obtenez le statut
#2 tâches dont l'exécution est terminée, 2 tâches en cours d'exécution, 2 tâches qui ont été rejetées
#Est sortie
$ curl -X GET http://localhost:8080/status
Tenant1#001: exitValue=0
Tenant1#002: Running
Tenant1#003: Running
Tenant1#004: exitValue=0
Tenant1#005: Rejected
Tenant1#006: Rejected
#Arrêt normal du conteneur Tomcat
$ curl -X POST http://localhost:8080/shutdown
** Figure 4.5 Confirmation du fonctionnement de la tâche asynchrone pour le multi-tenant **
Recommended Posts