Parallel execution of the same #Sceduled method - java

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"/>

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.

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

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
}
}

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

Query/Update Spring 3 Scheduled task cron expression

I have a scheduled job configured in an XML file:
<task:scheduled-tasks scheduler="taskScheduler">
<task:scheduled ref="scheduledJobs" method="doSomething" cron="30 * * * * ?"/>
</task:scheduled-tasks>
<bean id="scheduledJobs" class="com.xxx.ScheduledJobs"/>
<task:scheduler id="taskScheduler" pool-size="2" />
Is it possible to query the cron expression (to display to the user) and update it (not necessarily in the XML file but in the bean instance) with a new cron expression?
Or is there a better way of achieving this type of functionality?
I believe the answer is this is not possible using Spring/JDK scheduling but is possible if you use Quartz scheduling instead:
There is another thread covering a similar area:
Rescheduling a CronTriggerBean dynamically with same job details in Spring
You can achieve it in a programmatic way, shortened version:
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.support.CronTrigger;
public class ChangelogDeleterTask implements InitializingBean, Runnable {
#Autowired
private TaskScheduler scheduler;
private ScheduledFuture<?> scheduledTask;
private CronTrigger trigger;
private Date nextExecutionTime;
#Override
public void afterPropertiesSet() throws Exception {
schedule("0/5 * * * * *");
}
#Override
public void run() {/*do scheduled job*/}
public synchronized void schedule(String cronExpression) {
scheduledTask.cancel(false);
trigger = new MyCronTrigger(cronExpression);
scheduledTask = scheduler.schedule(this, trigger);
}
String getNextExecutionTime() {
return nextExecutionTime.toString();
}
String getExpression() {
return trigger.getExpression();
}
/**
* Class allows to remember nextExecutionTime.
*/
private class MyCronTrigger extends CronTrigger {
public ChangelogCronTrigger(String expression) {
super(expression);
}
#Override
public Date nextExecutionTime(TriggerContext triggerContext) {
Date date = super.nextExecutionTime(triggerContext);
nextExecutionTime = new Date(date.getTime());
return date;
}
}
}

Categories