Spring Boot: Can we have seperate thread pool for each request? - java

I've implemented asynchronous execution for a method in my application using Spring Boot #Async. I'm having a custom thread pool with 20 thread. In a for loop calling the async method for 30 times.
Each individual request is executing asynchronously but when i made two different requests at the same time to my API from browser, first request is executing and then second. Not both requests executing the same method parallely.
I thought when first request reach the app, it's started executing the async method and since it's being executing for 30 times and my pool is having 20 threads, all threads are busy in executing first request. So even second request came for execution also since the pool of threads busy, the other request is waiting till a thread becomes free in the pool.
Can we have separate thread pool for each individual request. or any way that we can make execution of each request separate independent of other request processing.
Here is my code sample.
#SpringBootApplication
#EnableAsync
public class AppBootStrap
{
public static void main(String[] args)
{
SpringApplication.run(AppBootStrap.class, args);
}
#Bean
public AsyncTaskService asyncTaskService() {
return new AsyncTaskService();
}
#Bean(name="customExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor poolExecutor = new ThreadPoolTaskExecutor();
poolExecutor.setCorePoolSize(10);
poolExecutor.setMaxPoolSize(20);
poolExecutor.setThreadNamePrefix("customPoolExecutor");
poolExecutor.initialize();
return poolExecutor;
}
}
**Controller Class:**
#RestController
public class TaskController
{
#Autowired
private TaskService taskService;
#RequestMapping("/performAction")
public void performAction() {
taskService.performAction();
}
}
**Service class**
#Service
public class TaskService
{
#Autowired
private AsyncTaskService asyncTaskService;
public void performAction()
{
for(int i = 0; i < 30; i++) {
asyncTaskService.greetings(i);
}
}
}
**Async Method Class**
public class AsyncTaskService
{
#Async
public void greetings(Integer i)
{
try
{
Thread.sleep(500 * (i + 10));
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("Welcome to Async Service: " + i);
}
}

#Bean(name = "apiTaskExecutor")
public ThreadPoolTaskExecutor apiTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(100);
executor.setQueueCapacity(50);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
#Bean(name = "lruTaskExecutor")
public ThreadPoolTaskExecutor lruTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(100);
executor.setMaxPoolSize(200);
executor.setQueueCapacity(500);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
The Way to do this is to create two Thread different Thread Pools using Different Qualifier names. (As shown above)
#Autowired
#Qualifier("apiTaskExecutor")
private ThreadPoolTaskExecutor apiTaskExecutor;
Then autowire the required pool using the qualifier you have given . You can also use the #Async annotation instead of autowiring . I prefer this way.
If I am not wrong this way of having different thread pools for different Tasks is known as bulkhead pattern.

I think the problem is that the customExecutor bean is a singleton. This means that when the second request tries to use this bean it finds all the threads of the thred pool busy. You can try to make the customExecutor bean non-singleton by using the #Scope("prototype") annotation which will cause a new one to be instantiated whenever a bean of this type is requested.

Related

How to run #PostConstruct non-blocking in Spring?

#PostConstruct
public void performStateChecks() {
throw new RuntimeException("test");
}
If I start a spring application with code above, it will prevent the application to start.
What I'm looking for is to execute a method directly after startup, but async. Means, it should not delay the startup, and it should not prevent the application to run even on failure.
How can I make the initialization async?
You can use EventListener instead of PostConstruct, it supports #Async:
#Service
public class MyService {
#Async
#EventListener(ApplicationStartedEvent.class)
public void performStateChecks() {
throw new RuntimeException("test");
}
}
Don't forget enable async support by #EnableAsync annotation
You can also use some other event, see inheritors of SpringApplicationEvent class
The easiest way I can see is by using EventListeners and async task executors.
Adding this code snippet would do the work:
#Component
public class AsyncStartupRunner {
#Bean(name = "applicationEventMulticaster")
public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
SimpleApplicationEventMulticaster eventMulticaster =
new SimpleApplicationEventMulticaster();
eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
return eventMulticaster;
}
#EventListener(ApplicationReadyEvent.class)
public void executeAfterStartup() {
throw new RuntimeException("Oops");
}
}
There are multiple ways this can be done.
First, simple one-liner solution is to create and start a new thread;
#PostConstruct
public void performStateChecks() {
new Thread(() -> { throw new RuntimeException("test"); }).start();
}
The thrown exception only interrupts the separate thread and doesn't block or prevent the application startup. This is useful if you are not interested in result or outcome of the task. Note this is not recommended as it starts separate thread outside spring managed context.
Second is to use executor service and submit a task to it. Spring provides a default ThreadPoolTaskExecutor which can be used to submit the tasks. This will allow you to have access to the future object of the task and do something with it later on;
private final ThreadPoolTaskExecutor executor; // inject via constructor
#PostConstruct
public void performStateChecks() {
Future<?> future = executor.submit(() -> {
throw new RuntimeException("test");
});
// do something with the future later on
}
If you have multiple such methods and requirements for various services/classes etc then create a new AsyncService class to do the actual work and annotate those methods with #Async. inject the AsyncService wherever you need via constructor and then call the required method;
#EnableAsync
#Component
public class AsyncService {
#Async
public void doActualTest() {
throw new RuntimeException("test");
}
}
Then use it like this;
private final AsyncService asyncService; // make sure to inject this via constructor
#PostConstruct
public void performStateChecks() {
asyncService.doActualTest();
}
You can remove #PostConstruct from your method and let that method be a normal method. You can then manualy invoke it when the ApplicatioinContext is already loaded and the application has already started.
#SpringBootApplication
public class ServiceLauncher {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplication(ServiceLauncher.class).run(args);
try {
context.getBean(YourBean.class).performStateChecks(); // <-- this will run only after context initialization finishes
} catch (Exception e) {
//catch any exception here so it does not go down
}
}
}
}

How to pause a scheduled task started using springboot's #Scheduled annotation?

I have a simple scheduled task that was created using the #Scheduled annotation. Something like this -
public void doSomething() {
// My code
}
I also have a blue-green upgrade scenario where this scheduled task may potentially run in both the blue and green clusters. And given this scheduled task modifies the DB, there is a possibility that one of the nodes might overwrite the data from the other node - or worst case race conditions.
I want to pause all the scheduled tasks on the green cluster until it is ready to accept traffic. I already have the code wired to listen to changes to the application state.
I explored a few options -
Simply adding a boolean at the start of the task.
Adding a decorator to the ThreadPoolTaskScheduler.
The problem with these approaches is they skip the task for that particular schedule and not pause it. So, if the task is skipped, say at T, and the application becomes immediately, the application will have to wait T+30mins to refresh data.
Is there a way in springboot to pause schedulers and resume immediately?
It looks like natively spring does not have the ability to pause scheduled tasks. I tend to use Quartz Scheduler when running schedule tasks with spring.
In order to do that we have to do a couple configurations.
First we have to setup our context aware component:
#Component
public final class ApplicationContextHolder extends SpringBeanJobFactory implements ApplicationContextAware {
private static ApplicationContext context;
private transient AutowireCapableBeanFactory beanFactory;
#Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
if (context == null) {
beanFactory = ctx.getAutowireCapableBeanFactory();
context = ctx;
}
}
#Override
protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
final Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job);
return job;
}
/**
* Get the current application context
* #return the current context
*/
public static ApplicationContext getContext() {
return context;
}
}
Then our configuration:
#Configuration
public class QuartzSchedulerConfig {
#Autowired
private ApplicationContext applicationContext;
/**
* Create the job factory bean
* #return Job factory bean
*/
#Bean
public JobFactory jobFactory() {
ApplicationContextHolder jobFactory = new ApplicationContextHolder();
jobFactory.setApplicationContext(applicationContext);
return jobFactory;
}
/**
* Create the Scheduler Factory bean
* #return scheduler factory object
*/
#Bean
public SchedulerFactoryBean schedulerFactory() {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setAutoStartup(true);
factory.setSchedulerName("Scheduler");
factory.setOverwriteExistingJobs(true);
factory.setJobFactory(jobFactory());
return factory;
}
}
Then our actual service:
#Service
public class SchedulerService {
#Autowired
private SchedulerFactoryBean schedulerFactory;
private Scheduler scheduler;
private ScheduledExecutorService executor;
/**
* Initialize the scheduler service
*/
#PostConstruct
private void init() {
scheduler = schedulerFactory.getScheduler();
executor = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors());
}
...//Other Scheduling tasks can go here
public void pauseJob(String triggerName, String groupName) {
TriggerKey tk = new TriggerKey(triggerName, groupName);
scheduler.pauseJob(tk);
}
}
Quartz Scheduling gives a lot of flexibility when it comes to scheduling tasks
http://www.quartz-scheduler.org/overview/
Customize the task scheduler to wait until it finishes the job and allow spring boot to do the graceful shutdown
#Bean
TaskSchedulerCustomizer taskSchedulerCustomizer() {
return taskScheduler -> {
taskScheduler.setAwaitTerminationSeconds(60);
taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
};
}
another opinion
Use Shedlock to lock the scheduler for the given amount of time(upgradable time). It also solves the scheduler running on all/multiple instances instead of one instance.

Autowiring multiple SimpleJobLaunchers

I have a class which I want to run jobs from asynchronously. To do this I have the following code:
#Resource
private SimpleJobLauncher jobLauncher;
#PostConstruct
public void initLauncher(){
jobLauncher.setTaskExecutor(taskExecutor());
}
However, there is a situation where I need to do this synchronously. So, what I have done is added the following variable:
#Resource
private SimpleJobLauncher synchronousJobLauncher;
Which I hoped wouldn't have the taskExecutor on it making it synchronous. I then pass the synchronousJobLauncher to the places where I want to do things synchronously. However, using the synchronousJobLauncher gives me the same error as when I use the asynchronous one which leads me to believe that you cannot autowire the same variable twice like I am trying to do. If I do not do the #PostConstruct part of the code, the synchronous part works like I expect but not the asynchronous part, even though they use, what I think are, different job launchers.
Any ideas? I have tried using the #Resource annotation instead of #Autowired.
I haven't used SimpleJobLaunchers, but normally, in Spring I always used the #Async annotation, which make asynchronous execution quite simple. All you have to do is add this annotation, #EnableAsync in any configuration file, just like the one below.
#Configuration
#EnableAsync
public class MvcConfig extends WebMvcConfigurerAdapter { ... }
Now, is all about adding the #Async to any method that will run asynchronously.
#Component
class AsyncTask {
#Async
public Future<String> run() throws InterruptedException {
return new AsyncResult<String>("return value");
}
}
And if you want to wait for the result, you can do the following.
public void callAsyncTask() {
try {
Future<String> future = asyncTask.run();
// Do some other things while the async task is running.
// future.get() will block the function and wait for the result.
String asyncResult = future.get();
} catch(InterruptedException e) {
e.printStackTrace();
// Do something else.
}
}
Hope this helps. I know is not directly what you were asking for, but maybe this solution can facilitate your problem.
You can create two job launchers (one synchronous and one asynchronous) then inject the one you want using a qualifier. By default, the SimpleJobLauncher uses a synchronous task executor, so you need to configure a task executor only for the asynchronous one. Here is an example:
#Bean
public SimpleJobLauncher synchronousJobLauncher() {
return new SimpleJobLauncher();
}
#Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
return new ThreadPoolTaskExecutor();
}
#Bean
public SimpleJobLauncher asynchronousJobLauncher() {
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setTaskExecutor(threadPoolTaskExecutor());
return jobLauncher;
}
Then, inject the job launcher you want with a #Qualifier("synchronousJobLauncher") or #Qualifier("asynchronousJobLauncher") according to your needs.
Hope this helps.

How to define multiple qualified thread pools with AsyncConfigurer

Spring framework has an interface AsyncConfigurer (and AsyncConfigurerSupport) for configuring everything necessary for having a thread pool executor and being able to run methods asynchronously (with #Async).
But usually, it's not a good practice to share the same thread pool among different functionalities, so usually I qualified them with a name, and I specify that name in Async annotation for it to use one specific thread pool.
Thing is, I would like to configure them through this convenient interface AsyncConfigurer and not loosing qualification, but I'm not able to.
Trying this:
#Configuration
#EnableAsync
public class PusherAsyncConfigurerSupport extends AsyncConfigurerSupport {
#Autowired
ExecutorConfig config;
#Override
#Qualifier(value = "executorOne")
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(config.getCorePoolSize());
...
executor.initialize();
return executor;
}
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new MyAsyncUncaughtExceptionHandler();
}
}
That thread pool is still not recognized when put in an Async annotation.
So, what is it needed for configuring several thread pools this way? Or isn't it the way for that at all?
mark with #Bean annotation
#Bean
#Qualifier(value = "executorOne")
public Executor getAsyncExecutor() {
//..
return executor;
}

How to run spring-batch jobs threadpooled?

I'm starting spring-batch jobs using JobLauncher.run().
Question: how can I threadpool those invocations? So that eg a maximum of 4 job threads may be running concurrently, and any further jobs are just queued?
#Autowired
private JobRegistry registry;
#Autowired
private JobLauncher launcher;
Job job = registry.getJob("jobname");
launcher.run(job, params); //immediately starts the job
You can set ThreadPoolTaskExecutor as the task executor used by the SimpleJobLauncher (the class that actually launches the jobs). This executor has some properties you can set, especially maxPoolSize.
public JobLauncher createJobLauncher() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(4);
taskExecutor.setMaxPoolSize(4);
taskExecutor.afterPropertiesSet();
SimpleJobLauncher launcher = (SimpleJobLauncher) super.createJobLauncher();
launcher.setTaskExecutor(taskExecutor);
return launcher;
}
Please take a look at the docs.
Possibly a duplicate of Spring Batch Multiple Threads and How to set up multi-threading in Spring Batch?.
Update:
I use the following code to start a batch job asynchronously. But I don't know how to limit the maximum number of executions as you mentioned.
TaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
taskExecutor.execute(new Runnable() {
public void run() {
try {
jobLauncher.run(importJob, jobParametersBuilder.toJobParameters());
} catch (Exception e){
e.printStackTrace();
}
}
});

Categories