springboot自定义动态加载配置信息的scheduler

以下为动态加载启动延时、周期参数的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 实例被创建时立即查询获取配置信息。

完成

至此就完成了,核心代码都已贴出。

重点在于思路,不是代码本身,上述代码是我经过手动修改后粘贴的,可能会有类名称不匹配的情况,但不匹配的那个名称肯定是很相近了,肯定可以看懂。

有问题可以留言,欢迎探讨

Spring Boot与Quartz的结合,可以实现动态配置定时任务。下面是一种实现方式: 1. 首先,在pom.xml文件中添加Quartz和Spring Boot的相关依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> ``` 2. 创建一个Quartz配置类,用于配置Quartz的相关属性。可以使用@Configuration注解将该类声明为配置类,并使用@Bean注解将SchedulerFactoryBean实例化: ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.quartz.SchedulerFactoryBean; @Configuration public class QuartzConfig { // 其他配置属性 @Bean public SchedulerFactoryBean schedulerFactoryBean() { SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); // 配置其他属性 return schedulerFactoryBean; } } ``` 3. 创建定时任务类,实现Job接口,并重写execute方法,用于定义具体的定时任务逻辑: ```java import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; public class MyJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { // 定时任务逻辑 } } ``` 4. 在需要动态配置的地方,使用@Autowired注解注入SchedulerFactoryBean,并使用schedulerFactoryBean.getScheduler()获取Scheduler实例。然后,通过Scheduler实例来动态配置定时任务: ```java import org.quartz.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class MyScheduler { @Autowired private SchedulerFactoryBean schedulerFactoryBean; public void scheduleJob(String jobName, String cronExpression) throws SchedulerException { Scheduler scheduler = schedulerFactoryBean.getScheduler(); JobDetail jobDetail = JobBuilder.newJob(MyJob.class) .withIdentity(jobName) .build(); Trigger trigger = TriggerBuilder.newTrigger() .withIdentity(jobName + "Trigger") .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression)) .build(); scheduler.scheduleJob(jobDetail, trigger); } } ``` 通过调用MyScheduler的scheduleJob方法,可以动态配置定时任务的名称和cron表达式。注意,这的cron表达式用于定义定时任务的触发规则。 以上就是使用Spring Boot和Quartz实现动态配置定时任务的基本步骤。可以根据具体需求进行扩展和调整。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值