How to force Spring Scheduled run parallel execution of single method? - java

I'm trying to use #Scheduled method to process some regular work (every second). Body of this method can process during more than one second and I see that if this happens next execution is not started. Does Spring support it or I should change it to any other concurrent solution?
I have tried to change Scheduler to ConcurrentTaskScheduler, but looks like it helps only if we have few schedules methods.
#Service
public class MainService {
#Scheduled(cron = "* * * * * *")
public void doSomething() {
//some logic which can takes more than 1 second
}
}
#Configuration
public class SchedulingConfig implements SchedulingConfigurer {
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
}
#Bean
public Executor taskExecutor() {
return new ConcurrentTaskScheduler(
Executors.newScheduledThreadPool(100));
}
}
Once the first execution is taking extra time the second execution will not be started. Otherwise, all works fine. How can I set up parallel execution of one scheduled method?

You can introduce an async component so it does not take 1 second https://www.baeldung.com/spring-async
#Service
public class MainService {
#Autowired
private SomethingService somethingService;
#Scheduled(cron = "* * * * * *")
public void doSomething() {
somethingService.doSomething();
}
}
//Introduce an async component so it does not take 1 second. runs doSomething() in a separate thread
#Component
public class SomethingService {
#Async
public void doSomething() {
//some logic which can takes more than 1 second
}
}

Related

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.

Run springboot application every 1 minut and with synchronise every controller

I developed spring-boot application with 4 controllers and I need to run the first one after is finished the seconde controller launched to do some work and the there and finally the fourth one, I do #Scheduled(fixedRate = 30000) for every controller, but with this solution, I don't have a synchro between the 4 controllers, I need to help me with a solution to run it with synchro or another solution to automatically run all controller together with priority. and i attached the code architecture
#CrossOrigin("*")
#RestController
public class collector{
#Autowired
DataSource datasource;
#Scheduled(fixedRate = 40000)
public void collector( )
{
// the methodes
}
}
#CrossOrigin("*")
#RestController
public class Loader {
#Autowired
DataSource datasource;
#Scheduled(fixedRate = 40000)
public void loador( )
{
// the methodes
}
}
#CrossOrigin("*")
#RestController
public class export{
#Autowired
DataSource datasource;
#Scheduled(fixedRate = 40000)
public void export( )
{
// the methodes
}
}
#CrossOrigin("*")
#RestController
public class send{
#Autowired
DataSource datasource;
#Scheduled(fixedRate = 40000)
public void send( )
{
// the methodes
}
}
This is not how schedulers in Spring work. Since you want to execute each method synchronously right after the previous one has finished, schedule just the one that triggers the chain of methods and feel free to call them inside the scheduled method.
#Scheduled(fixedRate = 40000)
public void collector() {
// the methods // body of the 1st one
this.loader.loador(); // 2nd one
this.export.export(); // 3rd one
this.send.send(); // 4th one
}

Parallel execution of the same #Sceduled method

I have e method which is annotated with #scheduled. Its a fairly long running method. I need to run the same method in parallel using a threadpool. Is it possible? The code is:
#Scheduled(fixedRate=100)
public void run() {
Job job = QUEUE.take();
job.run(); //Takes a long time
}
The QUEUE has many jobs and I would like to run them in parallel using Spring's Scheduled annotation.
I think you can change the Job.run method to an Asynchronous methods by use spring's "#Async",or another way you can create yourself threadpool to do the Job.
/**
* Created by roman.luo on 2016/9/14.
*/
#Component
#Scope("prototype")
public class JobDelegate implements Job {
private Job job;
public JobDelegate(Job job) {
this.job = job;
}
#Async
public void run(){
job.run();
}
}
/**
* Created by roman.luo on 2016/9/14.
*/
#Component
public class Sceduled extends ApplicationObjectSupport{
#Scheduled(fixedRate = 100)
public void run(){
Job job = QUEUE.take();
Job jobDelegate = getApplicationContext().getBean(JobDelegate.class,job);
jobDelegate.run();
}
}
remember config the spring xml file:
<task:executor id="myexecutor" pool-size="5" />
<task:annotation-driven executor="myexecutor"/>

Run multiple instances of the SAME cron job in Spring

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.

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