我有一个调度程序,它以 5 秒的固定延迟触发。我计划拥有多个调度程序,但现在我们只使用一个调度程序。
要求:根据业务条件调度程序的fixedDelay应更改(例如,默认fixedDelay为5秒,但可以是6、8、10秒,根据条件)。
为了实现这一点,我正在尝试修改fixedDelay。但这对我不起作用。
接口,带有延迟方法
public abstract class DynamicSchedule{
/**
* Delays scheduler
* @param milliseconds - the time to delay scheduler.
*/
abstract void delay(Long milliseconds);
/**
* Decreases delay period
* @param milliseconds - the time to decrease delay period.
*/
abstract void decreaseDelayInterval(Long milliseconds);
/**
* Increases delay period
* @param milliseconds - the time to increase dela period
*/
abstract void increaseDelayInterval(Long milliseconds);
}
实现位于 spring-context 项目中的 org.springframework.scheduling 的 Trigger 接口。
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import java.util.Date;
import java.util.concurrent.ScheduledFuture;
public class CustomDynamicSchedule extends DynamicSchedule implements Trigger {
private TaskScheduler taskScheduler;
private ScheduledFuture<?> schedulerFuture;
/**
* milliseconds
*/
private long delayInterval;
public CustomDynamicSchedule(TaskScheduler taskScheduler) {
this.taskScheduler = taskScheduler;
}
@Override
public void increaseDelayInterval(Long delay) {
if (schedulerFuture != null) {
schedulerFuture.cancel(true);
}
this.delayInterval += delay;
schedulerFuture = taskScheduler.schedule(() -> { }, this);
}
@Override
public void decreaseDelayInterval(Long delay) {
if (schedulerFuture != null) {
schedulerFuture.cancel(true);
}
this.delayInterval += delay;
schedulerFuture = taskScheduler.schedule(() -> { }, this);
}
@Override
public void delay(Long delay) {
if (schedulerFuture != null) {
schedulerFuture.cancel(true);
}
this.delayInterval = delay;
schedulerFuture = taskScheduler.schedule(() -> { }, this);
}
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
Date lastTime = triggerContext.lastActualExecutionTime();
return (lastTime == null) ? new Date() : new Date(lastTime.getTime() + delayInterval);
}
}
配置:
@Configuration
public class DynamicSchedulerConfig {
@Bean
public CustomDynamicSchedule getDinamicScheduler() {
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.initialize();
return new CustomDynamicSchedule(threadPoolTaskScheduler);
}
}
测试类,测试使用情况。
@EnableScheduling
@Component
public class TestSchedulerComponent {
@Autowired
private CustomDynamicSchedule dynamicSchedule;
@Scheduled(fixedDelay = 5000)
public void testMethod() {
dynamicSchedule.delay(1000l);
dynamicSchedule.increaseDelayInterval(9000l);
dynamicSchedule.decreaseDelayInterval(5000l);
}
}
我尝试使用https://stackoverflow.com/a/51333059/4770397但此代码对我不起作用。
调度程序以 fixedDelay 运行,没有任何变化。
使用
@Scheduled
只允许使用静态计划。您可以使用属性来使时间表可配置,如下所示
@Scheduled(cron = "${yourConfiguration.cronExpression}")
// or
@Scheduled(fixedDelayString = "${yourConfiguration.fixedDelay}")
但是,一旦 Spring 上下文初始化(应用程序启动),结果时间表将被修复。
Trigger
- 类似于您已经所做的。与要执行的任务一起,可以通过使用 SchedulingConfigurer
在 @Configuration
类中实现 ScheduledTaskRegistrar.addTriggerTask
来注册此触发器:
@Configuration
@EnableScheduling
public class AppConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskScheduler());
taskRegistrar.addTriggerTask(() -> myTask().work(), myTrigger());
}
@Bean(destroyMethod="shutdown")
public Executor taskScheduler() {
return Executors.newScheduledThreadPool(42);
}
@Bean
public CustomDynamicSchedule myTrigger() {
new CustomDynamicSchedule();
}
@Bean
public MyTask myTask() {
return new MyTask();
}
}
但是不要在
CustomDynamicSchedule
中对任务进行任何注册,只需使用它来计算下一次执行时间:
public class CustomDynamicSchedule extends DynamicSchedule implements Trigger {
private long delayInterval;
@Override
public synchronized void increaseDelayInterval(Long delay) {
this.delayInterval += delay;
}
@Override
public synchronized void decreaseDelayInterval(Long delay) {
this.delayInterval += delay;
}
@Override
public synchronized void delay(Long delay) {
this.delayInterval = delay;
}
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
Date lastTime = triggerContext.lastActualExecutionTime();
return (lastTime == null) ? new Date() : new Date(lastTime.getTime() + delayInterval);
}
}
但请记住使
CustomDynamicSchedule
线程安全,因为它将由 spring 创建为单例,并且可能被多个线程并行访问。
Spring 的
@Scheduled
注解不提供此支持。
这是我使用基于队列的解决方案实现类似功能的首选,它允许灵活的计时以及非常强大的调度程序功能实现。
cron
和一个单线程执行器服务,负责根据 cron 将消息发布到队列。 task-cron
映射保留在 database
中,并在启动期间初始化。此外,我们还公开了一个 API
,用于在运行时更新任务的 cron。
我们只需关闭旧的计划执行器服务,并在每次通过 API 触发 cron 中的更改时创建一个服务。此外,我们也在数据库中更新了相同的内容。
这种方法有多种优点。这将调度程序与调度管理任务解耦。现在,调度程序可以只专注于业务逻辑。此外,我们可以编写任意数量的调度程序,所有调度程序都监听同一个队列并相应地执行操作。
使用注释,您只能通过找到公分母并对其进行民意调查来进行近似。稍后我会告诉你。如果您想要一个真正的动态解决方案,您不能使用注释,但可以使用编程配置。 此解决方案的优点是您甚至可以在运行时更改执行周期!以下是如何执行此操作的示例:
public initializeDynamicScheduledTAsk (ThreadPoolTaskScheduler scheduler,Date start,long executionPeriod) {
scheduler.schedule(
new ScheduledTask(),
new Date(startTime),period
);
}
class ScheduledTask implements Runnable{
@Override
public void run() {
// my scheduled logic here
}
}
有一种方法可以作弊并实际使用注释做一些事情。但只有当精度不重要时才可以这样做。精度是什么意思。如果你知道你想每 5 秒启动一次,但多或少 100 毫秒并不重要。如果您知道需要每 5-6-8 或 10 秒启动一次,您可以配置一个每秒执行一次的作业,并在一个 if 语句中检查自上次执行以来已经过去了多长时间。它非常蹩脚,但只要您不需要达到毫秒精度,它就可以工作:)。这是一个例子:
public class SemiDynamicScheduledService {
private Long lastExecution;
@Value(#{yourDynamicConfiguration})
private int executeEveryInMS
@Scheduled(fixedDelay=1000)
public semiDynamicScheduledMethod() {
if (System.currentTimeMS() - lastExecution>executeEveryInMS) {
lastExecution = System.currentTimeMS();
// put your processing logic here
}
}
}
我有点蹩脚,但对于简单的情况我会做的。
Spring 不直接支持使用变量作为调度程序的频率。编写自定义主调度程序(假设每秒执行一次)来控制其他调度程序是一个不错的选择。
这可以通过实现spring的Trigger类来实现。
由此我们可以推导出动态的下一个调度,限制是我们不能执行 nextSchedule 之前的调度。