Run multiple instances of the SAME cron job in Spring - java

I want to be able to run the SAME scheduled job in Spring.
After searching on the Internet, I figured out how to run multiple different jobs at the same time.
I have a #Service annotated class which has only one method, annotated with #Scheduled. I want to have multiple instances of this job running at the same time.
I am not using Quartz or Spring Batch( I have seen a lot of examples with Spring Batch).
The documentation doesn't clearly say if this can be achieved.

Yes, it can be easily achieved, but not with #Scheduled annotation.
How? Let me first explain how Spring works.
Spring from every method annotated with #Scheduled creates a new Runnable object and then schedules it for execution to the TaskScheduler (ThreadPoolTaskScheduler to be precise).
To see the exact code look at ScheduledAnnotationBeanPostProcessor.processScheduled().
So to fulfill your requirement: have multiple instances of the same job, but without using Quartz or Spring Batch we need to abandon #Scheduled annotation and do something a bit different than what the ScheduledAnnotationBeanPostProcessor does by default.
#Configuration
public class SpringConfig {
#Autowired
private TaskScheduler scheduler;
#Autowired
private YourServiceAnnotatedClass service;
#PostConstruct
public void startJobs() {
int numOfJobInstances = 3;
List<ImportantJob> jobs = IntStream.range(0, numOfJobInstances)
.mapToObj(i -> new ImportantJob("job" + i, service))
.collect(Collectors.toList());
jobs.forEach(this::schedule);
}
private void schedule(ImportantJob job) {
scheduler.schedule(job, new CronTrigger("*/5 * * * * *"));
// Above CronTrigger with 5 seconds was used, but feel free to use other variants, e.g.
// scheduler.scheduleAtFixedRate()
// scheduler.scheduleWithFixedDelay()
}
#Bean(destroyMethod = "shutdown")
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(3); // Adjust it to your needs
return taskScheduler;
}
}
class ImportantJob implements Runnable {
private final String name;
private final YourServiceAnnotatedClass service;
public ImportantJob(String name, YourServiceAnnotatedClass service) {
this.name = name;
this.service = service;
}
#Override
public void run() {
service.doSth();
}
}
As you can see, although #Scheduled is useful and simple, it is not very flexible. But with some effort you can gain much more control over scheduled tasks.

Related

Right way of implementing a controllable scheduler

For my design I need a controllable schedular. Spring boot offers an #Scheduled annotation but that is more simplified and I do not have granular control.
So I wanted to implement my own scheduler manually.
This is the class I created:
#Slf4j
#Service
public class JobExecutor {
private static ScheduledExecutorService jobExecutor;
private static Environment env;
#Autowired
private JobExecutor(Environment env) {
JobExecutor.env = env;
}
public static ScheduledExecutorService INSTANCE() {
if (null == jobExecutor) {
synchronized (JobExecutor.class) {
if (null == jobExecutor) {
jobExecutor = Executors.newScheduledThreadPool(
Integer.parseInt(Objects.requireNonNull(env.getProperty("scheduler.jobs"))));
}
}
}
return jobExecutor;
}
}
With this approach I could simply call the static method to get a single instance.
Is this correct approach for a schedular? I need to start and stop and shutdown the jobs. I tried guava AbstractScheduledService but that does not seem to be working.
This is not the correct approach for creating a singleton, because double checked locking is broken. You're using Spring, so a) your JobExecutor will be a singleton anyway, and b) will only be created if it is needed. You might as well, therefore, create your executor instance in the constructor and get rid of those static methods.
Even better, you could create schedulers as named beans, and then inject them into classes where you want them:
#Configuration
public class ExecutorConfiguration {
#Bean
public ScheduledExecutorService jobExecutor(#Value("${scheduler.jobs}") jobs) {
return Executors.newScheduledThreadPool(jobs);
}
}
This says that whenever another component needs a ScheduledExecutorService, Spring should call this jobExecutor() method; Spring will automatically populate the jobs parameter from the scheduler.jobs property because of the #Value.
You can then inject your executor wherever you need it, for example with constructor injection (handily you're already using Lombok, so the amount of boilerplate is minimised):
#Service
#AllArgsConstructor
public class MyThingThatNeedsAScheduler {
private final ScheduledExecutorService jobExecutor;
// methods here...
}
You can also use setter or member injection, if you want.

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.

Adding scheduler for #Scheduled annotation in spring without using xml annotations

I have several methods with the annotation #Scheduled. For each annotation or a group of them I want a different scheduler to be used. For example:
Group A has 3 methods with #Scheduled annotation which need to use Scheduler X.
Group B has 5 methods with #Scheduled annotation which need to use Scheduler Y.
From what I have read in Does spring #Scheduled annotated methods runs on different threads?, if the scheduler is not specified then only one of those methods will run at a time.
I know how this connection can be done using xml-based annotation. But is there a way that this can be done using Java-based annotation only?
It can be done using java config. But not using an annotation attributes.
You could have a look at the Spring API doc for some extended example.
For example:
#Configuration
#EnableScheduling
public class AppConfig implements SchedulingConfigurer {
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskScheduler());
}
#Bean(destroyMethod="shutdown")
public Executor taskScheduler() {
return Executors.newScheduledThreadPool(42);
}
}
#Scheduled group is not yet supported. See this open issue.
If you want use more than one scheduler you have to create and configure them programmatically. For example:
#Configuration
#EnableScheduling
public class AppConfig implements SchedulingConfigurer {
[...]
#Bean(destroyMethod="shutdown", name = "taskSchedulerA")
public Executor taskSchedulerA() {
return Executors.newScheduledThreadPool(42);
}
#Bean(destroyMethod="shutdown", name = "taskSchedulerB")
public Executor taskSchedulerA() {
return Executors.newScheduledThreadPool(42);
}
}
#Service
public class MyService {
#Autowired #Qualifier("taskSchedulerA")
private Executor taskSchedulerA;
#Autowired #Qualifier("taskSchedulerB")
private Executor taskSchedulerB;
#PostConstruct
public void schedule(){
Executors.newScheduledThreadPool(42).schedule(new Runnable() {
#Override
public void run() {
functionOfGroupA();
}
} , ..);
}
}

Configure Spring task scheduler to run at a fixeDelay or run at once based on a boolean

I have a code which runs at a regular interval.
Below is code for that
#EnableScheduling
#Component
public class Cer {
#Autowired
private A a;
#Autowired
private CacheManager cacheManager;
#Scheduled(fixedDelayString = "${xvc}")
public void getData() {
getCat();
getB();
return;
}
}
I want to change#Scheduled(fixedDelayString = "${xvc}") this based on a boolean say runOnce if runOnce is true #scheduled should run once only say on code startup.
Any advice how to achieve this.
Thanks in advance.
I would place the functionality that you want to schedule in a component:
#Component
public class ServiceToSchedule {
public void methodThatWillBeScheduled() {
// whatever
return;
}
}
And have two additional components that will be instantiated depending on a Profile. One of them schedules the task, and the other one just executes it once.
#Profile("!scheduled")
#Component
public class OnceExecutor {
#Autowired
private ServiceToSchedule service;
#PostConstruct
public void executeOnce() {
// will just be execute once
service.methodThatWillBeScheduled();
}
}
#Profile("scheduled")
#Component
#EnableScheduling
public class ScheduledExecutor {
#Autowired
private ServiceToSchedule service;
#Scheduled(fixedRate = whateverYouDecide)
public void schedule() {
// will be scheduled
service.methodThatWillBeScheduled();
}
}
Depending on which profile is active, your method will be executed just once (scheduled profile is not active), or will be scheduled (scheduled profile is active).
You set the spring profiles by (for example) starting your service with:
-Dspring.profiles.active=scheduled

Categories