This article is part of "Implementing Asynchronous Processing in Tomcat".
-"Implementation of asynchronous processing in Tomcat" -"Implementation of asynchronous processing for single tenant in Tomcat" --"Implementation of multi-tenant asynchronous processing in Tomcat" ← This article
In "Implementation of asynchronous processing in Tomcat", [@Async](https://docs.spring.io/spring-framework/docs/current/ I introduced that you can control the flow of asynchronous tasks by combining javadoc-api / org / springframework / scheduling / annotation / Async.html) and thread pool. However, there is a one-to-one correspondence between @Async and the thread pool. Therefore, it is not possible to support multi-tenant.
Multi-tenancy is a mechanism that provides services to multiple customers with an instance of one application. For example, when the tenant ID is specified as follows, tenants "t1" and "t2" are assigned to different thread pools, and different resources / assets are used for each tenant, such as accessing different schemas of the data store. It is an image that does.
- 「GET http://localhost/async?tenantId=t1」
- 「GET http://localhost/async?tenantId=t2」
** Figure 4.0 Multi-tenant HTTP request **
So, let's think about how to implement asynchronous processing for multi-tenancy. It also introduces that there are some parts that are out of the management of Spring Framework, and you need to implement thread pool lifecycle management yourself.
Create a ThreadPoolTaskExecutors class that manages a multi-tenant thread pool.
In this class, Map type with tenant ID and thread pool as key-value is held in the field, and getExecutor () returns the thread pool corresponding to the tenant ID. However, registering this class as a bean does not mean that the Spring Framework will recognize that it contains a thread pool.
Therefore, in order to shut down the thread pool by yourself, destroy with @PreDestroy added. () Is added. Since the method of @PreDestroy is called back when Tomcat normally ends, the thread is threaded at that timing. It shuts down the pool. However, [Create @Configuration class](https://qiita.com/sipadan2003/items/3277024b7da63c844663#configuration%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE% Note that this shutdown also destroys the tasks that are in the queue, as mentioned in E4% BD% 9C% E6% 88% 90).
public class ThreadPoolTaskExecutors {
private Map<String, ThreadPoolTaskExecutor> map = new HashMap<>();
public ThreadPoolTaskExecutors(ThreadPoolSettings settings) {
//Create a thread pool for each tenant
map.put("Tenant1", newExecutor("Tenant1", settings));
map.put("Tenant2", newExecutor("Tenant2", settings));
map.put("Tenant3", newExecutor("Tenant3", settings));
}
//Create thread pool
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;
}
//Returns the thread pool corresponding to the tenant ID
public ThreadPoolTaskExecutor getExecutor(String tenantId) {
return map.get(tenantId);
}
//Shut down the thread pool
@PreDestroy
private void destroy() {
this.map.values().forEach(ThreadPoolTaskExecutor::shutdown);
}
}
** Figure 4.1 Contents of ThreadPoolTaskExecutors class **
[SampleConfig] mentioned above (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) Add taskExecutors2 () to the class as shown below. This method uses @Bean to [multi-tenant For Thread Pool Management Object](#Add Thread Pool Management Class) is registered as a bean.
public class SampleConfig {
:
@Bean
public ThreadPoolTaskExecutors taskExecutors2() {
return new ThreadPoolTaskExecutors(threadPoolSettings);
}
** Figure 4.2 Contents of SampleConfig # taskExecutors2 () **
[Sample Service] mentioned above (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) Add cmd2 () to the class as shown below. Unlike cmd (), @Async is not added and it returns. The value is a String object, not a Future.
public class SampleService {
:
public String cmd2(final String[] command) {
final int exitValue = run(command);
return "exitValue="+exitValue;
}
:
}
** Figure 4.3 Contents of SampleService # cmd2 () **
[SampleController] mentioned above (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) Refurbish the class. First, add the taskExecutors2 field. Beans registered in SampleConfig class because there is @Autowired Is automatically injected.
public class SampleController {
:
@Autowired
private ThreadPoolTaskExecutors taskExecutors2;
:
}
** Figure 4.4.1 Contents of SampleService # taskExecutors2 **
Next, add a method that calls cmd2 () of SampleService. The processing flow of this method is the same as the cmd () method, except that it gets the thread pool and explicitly makes asynchronous calls.
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());
//Get 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); //Call Service
});
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 Contents of SampleService # cmd2 () **
You can start the Tomcat container locally and execute the command as follows to check the operation.
$ 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
#The thread pool is "maxPoolSize"=2」、「queueCapacity=When "2"
#After waiting a little over 10 seconds/When you get the status
#2 tasks that have finished executing, 2 tasks that are running, 2 tasks that have been rejected
#Is output
$ 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
#Normal termination of tomcat container
$ curl -X POST http://localhost:8080/shutdown
** Figure 4.5 Multi-tenant support Asynchronous task operation check **
Recommended Posts