以下为动态加载启动延时、周期参数的scheduler实例。
首先需要了解几个知识:
1. springboot有个SchedulingConfigurer接口,实现它便可以实现注册自定义的scheduler。有个函数configureTasks:
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar)
2. 基于1,我们创建的scheduler可以使用1的函数configureTasks中的Trigger来注册表现行为。Trigger是每一次执行触发完成后被调用,来设置下一次的执行时间点。在这个行为中,我们可以控制它的执行周期。
3. 基于2,我们可以在trigger中,把控制scheduler行为(如执行周期)的重要参数变为一定范围内的共享变量,然后再写一个程序去修改这个变量,从而实现影响到当前scheduler行为的效果。
了解了上面的知识后,实现逻辑:
1. 首先。将配置信息存在数据库。
2. 配置信息作为一个独立的对象(下面简称configData)存储在jvm中,被scheduler以及会修改configData的代码共享。scheduler读取其内容,修改程序负责更改内容。
3. 然后,程序启动后创建schuduler,创建时先执行初始化操作,该操作去数据库中查找配置信息回来,赋予到configData。scheduler使用configData的内容开始按照configData的内容执行。
4. scheduler周期执行的逻辑中,每一次执行都会先校验当前是否需要去数据库获取最新的配置信息,如果需要就去加载新的配置信息。校验的方式是,我的设计是,数据库中存储一个数值用于告诉周期函数经过多久需要获取一次配置信息,然后程序周期执行的过程中,每一此执行被触发时,都会先校验当前的时间与上一次获取配置信息的时间差,如果大于等于配置的时间差,就去获取最新的配置信息,刷新configData,否则不获取,依旧使用缓存的信息。
5. 程序继续实行,执行时使用configData中的参数进行执行(比如fixed(周期)时间: 程序每次都会使用configData中的fixed时间,后边的代码会有展示,我这里的fixed时间是使用trigger来实现的)。基于4的刷新机制,保证了配置信息的最新,因此scheduler就会按照我们最新的信息来执行。实现了对scheduler的动态控制
6. 上述的逻辑有个缺点:就是设定的更新配置信息的周期,会受到设定给scheduler的fixed参数的影响。比如,我们设定获取配置信息的周期为10s(也就是我们希望10s刷新一次配置信息),而fixed的时间为20s(scheduler触发周期为20s),由于更新逻辑只有在周期触发启动后才会执行,那么就等于实际上是20s才会被刷新一次。
这样的解决办法也很简单,直接写一个controller,再编写刷新配置信息的service代码,想要刷新了就请求一下controller即可。
yml:
scheduler:
jfqqqq:
name: jfqqqScheduler
config类
@ConfigurationProperties(prefix = "scheduler.jfqqqq")
public class SchedulerJFqqqqConfig {
private String name;
private SchedulerConfig schedulerConfig;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public SchedulerConfig getSchedulerConfig() {
return schedulerConfig;
}
public void setSchedulerConfig(SchedulerConfig schedulerConfig) {
this.schedulerConfig = schedulerConfig;
}
}
SchedulerJFqqqqConfig 类被springboot自动装配,并读取配置在yml中的信息,这里写这个yml的作用是方便定义多个scheduler(本例子只写一个),每个scheduler可以在yml中配置名字,然后程序用这个名字去数据库中寻找自己的配置信息。
数据表
其中的name字段就是yml中要配置的scheduler的名字。min_interval只是我的一个小功能的字段,虽然程序有用它,但与本文无关且影响不大,在此就不做说明了。
scheduler类
这个类实现知识点1中说的SchedulingConfigurer 接口,并在configureTasks函数中注册trigger,将参数对象(代码中叫taskRateConfig)共享。
@Component
public class JFqqqScheduler2 implements SchedulingConfigurer {
private Logger logger = LoggerFactory.getLogger(TaskRateScheduler2.class);
@Autowired
private DaemonServiceAdapter daemonServiceAdapter;
@Autowired
private SchedulerJFqqqqConfig taskRateConfig;
@Autowired
private SchedulerConfigService schedulerConfigService;
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadNamePrefix("ThreadPoolTaskScheduler");
scheduler.setPoolSize(1);
scheduler.initialize();
scheduledTaskRegistrar.setScheduler(scheduler);
//for get configuration from db, i DIY-made a fix-delay scheduler(偶尔喜欢写英文,练手嘛不一定对)
JFqqqTaskRunnable runnable = new JFqqqTaskRunnable (taskRateConfig, schedulerConfigService, daemonServiceAdapter);
scheduledTaskRegistrar.addTriggerTask(runnable, triggerContext -> {
Date lastActualExecutionTime = triggerContext.lastActualExecutionTime();
boolean lastActualIsNull = lastActualExecutionTime == null ? true : false;
Integer delayMillis;
if (lastActualIsNull) {
delayMillis = taskRateConfig.getSchedulerConfig().getInitialDelay();
} else {
delayMillis = taskRateConfig.getSchedulerConfig().getFixedDelay();
}
Calendar nextExecutionTime = new GregorianCalendar();
nextExecutionTime.setTime(new Date());//get current time , this can help us scheduling as fixed-delay model
nextExecutionTime.add(Calendar.MILLISECOND, delayMillis);
return nextExecutionTime.getTime();
});
}
}
上面代码的trigger中,主要逻辑是计算delayMillis的值,它决定了第一次启动的延时时间,以及周期长度(获取上一次执行时间,为null说明没执行过,于是delayMillis就是启动后的延时时间;如果执行过,那么就是周期时间,这里每次都从配置信息taskRateConfig中获取,从而具备了每次执行后更改下一次执行时间的能力)。
JFqqqTaskRunnable 是一个runnable的实现类,在创建之初就会去先获取一次配置信息
RateTaskRunnable类
实现Runnable接口
public class JFqqqTaskRunnable implements Runnable {
private Logger logger = LoggerFactory.getLogger(JFqqqTaskRunnable.class);
private SchedulerJFqqqqConfig taskRateConfig;
private SchedulerConfigService schedulerConfigService;//用于获取配置新息的service
private AtomicReference<Long> time;
private DaemonServiceAdapter daemonServiceAdapter;
public JFqqqTaskRunnable (SchedulerJFqqqqConfig taskRateConfig, SchedulerConfigService schedulerConfigService, DaemonServiceAdapter daemonServiceAdapter) {
this.taskRateConfig = taskRateConfig;
this.schedulerConfigService = schedulerConfigService;
this.daemonServiceAdapter = daemonServiceAdapter;
//创建后,立即获取一次配置信息(这是启动立即执行获取信息的意思)
updateConfiguration();
}
@Override
public void run() {
business();
}
/**
* 如果配置的数据已经过时,就需要更新,从而改变周期控制效果
*/
public void updateConfiguration() {
if (time == null) {//the first turn
time = new AtomicReference<>(System.currentTimeMillis());
SchedulerConfig fromDB = getFromDB();
if (fromDB == null) {
logger.error("could not find the scheduler config-information from db!");
} else {
this.taskRateConfig.setSchedulerConfig(fromDB);
logger.debug("update configuration of scheduler-{} finished! now is {}", fromDB.getName(), taskRateConfig.toString());
}
} else {
Long aLong = time.get();
long interval = System.currentTimeMillis() - aLong.longValue();
if (interval >= taskRateConfig.getSchedulerConfig().getMinInterval()) {
time.compareAndSet(aLong, System.currentTimeMillis());
SchedulerConfig fromDB = getFromDB();
if (fromDB == null) {
logger.error("could not find the scheduler config-information from db!");
} else {
this.taskRateConfig.setSchedulerConfig(fromDB);
logger.debug("update configuration of scheduler-{} finished! now is {}", fromDB.getName(), taskRateConfig.toString());
}
}
}
}
/**
* 从数据库获取配置信息
*/
public SchedulerConfig getFromDB() {
SchedulerConfigEntity entity = schedulerConfigService.getSchedulerConfig(taskRateConfig.getName());
if (entity == null) {
return null;
}
SchedulerConfig config = new SchedulerConfig();
CopyUtil.copyAttrsIfNotNullWithSuperClass(entity, SchedulerConfigEntity.class, false, config, SchedulerConfig.class, true);
return config;
}
public void business() {
//do something...
//完成
logger.debug("scheduler!");
}
}
SchedulerConfigService在本文就不做展示了,它就是一个使用mapper去查询数据库的service而已。
JFqqqTaskRunnable 实例被创建时立即查询获取配置信息。
完成
至此就完成了,核心代码都已贴出。
重点在于思路,不是代码本身,上述代码是我经过手动修改后粘贴的,可能会有类名称不匹配的情况,但不匹配的那个名称肯定是很相近了,肯定可以看懂。
有问题可以留言,欢迎探讨