使用Nacos配置中心、Springboot实现动态任务调度

众所周知SpringBoot中通过@Scheduled即可使用定时任务,但是我们有需求需要动态的定时导出报表这可怎么办呢,当然市面上还有很多的任务调度器也是不错的.本文主要教大家使用Nacos配置中心和SpringBoot实现动态任务调度.
话不多说,代码奉上.

public interface AbstractDynamicSchedule extends Runnable {

    /**
     * 任务名称
     * @return 返回执行任务名称用于打日志
     */
    String taskName();

    /**
     * 任务的cron表达式yaml key
     * @return yaml key
     */
    String cronKey();

}

采用策略模式来实现我们的主体.动态的创建任务肯定却不了 Runnable 从Scheduled的源码中我们可以了解到 可以通过 .schedule(Runnable,Cron)来创建一个新的定时任务,通过 .cancel可以取消一个定时任务 那么我们就可以从这里入手来做更改操作了.

import cn.timesgroup.market.backend.common.schedule.AbstractDynamicSchedule;
import com.alibaba.nacos.spring.context.event.config.NacosConfigReceivedEvent;
import com.alibaba.nacos.spring.util.parse.DefaultYamlConfigParse;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;

@Component
@Slf4j
public class NacosCronDataIdChangeListener implements ApplicationListener<NacosConfigReceivedEvent>, SchedulingConfigurer {

    private static final String CRON_YAML = "application-dynamic-schedule.yml";

    private static final DefaultYamlConfigParse parse = new DefaultYamlConfigParse();


    private ScheduledTaskRegistrar taskRegistrar;
    private static final ConcurrentHashMap<String, ScheduledFuture<?>> SCHEDULED_FUTURES = new ConcurrentHashMap<>();
    private static final ConcurrentHashMap<String, CronTask> CRON_TASKS = new ConcurrentHashMap<>();
    @Resource
    private ApplicationContext applicationContext;
    /**
     * 存储所有动态定时任务处理类
     */
    @Resource
    private Map<String, AbstractDynamicSchedule> scheduleMap;

    @Override
    public void onApplicationEvent(NacosConfigReceivedEvent event) {

        if (!CRON_YAML.equals(event.getDataId())) {
            log.info("CRON_YAML is not {}", CRON_YAML);
            return;
        }

        Properties properties = parse.parse(event.getContent());

        Collection<AbstractDynamicSchedule> editCronScheduleMap = Lists.newArrayList();

        // 对比每一个配置
        scheduleMap.keySet().forEach(schedule -> {

            AbstractDynamicSchedule bean = (AbstractDynamicSchedule) applicationContext.getBean(schedule);
            String cronKey = bean.cronKey();

            CronTask cronTask = CRON_TASKS.get(cronKey);
            if (Objects.isNull(cronTask)) {
                // 新增
                editCronScheduleMap.add(scheduleMap.get(schedule));
                return;
            }

            String oldCronValue = cronTask.getExpression();
            String newCronValue = properties.getProperty(cronKey);

            if (Objects.equals(oldCronValue, newCronValue)) {
                log.info("task time not change , cronKey={}, oldCronValue={}", cronKey, oldCronValue);
                return;
            }

            // 发生了变化
            editCronScheduleMap.add(scheduleMap.get(schedule));
        });

        this.refreshTasks(editCronScheduleMap);
    }

    public void refreshTasks(Collection<AbstractDynamicSchedule> tasks) {

        tasks.forEach(schedule -> {

            String cronKey = schedule.cronKey();

            // 取消已经删除的策略任务
            if ("-".equals(cronKey) || StringUtils.isBlank(cronKey)) {
                cancel(cronKey);
                log.info("取消已经删除的策略任务, taskName={}, cronKey={}", schedule.taskName(), schedule.cronKey());
                return;
            }

            String cronNewValue = applicationContext.getEnvironment().getProperty(cronKey);

            // 新的值为取消定时任务
            if ("-".equals(cronNewValue) || StringUtils.isBlank(cronNewValue)) {
                cancel(cronKey);
                log.info("定时任务关闭, taskName={}, cronKey={}", schedule.taskName(), schedule.cronKey());
                return;
            }

            // 定时任务没有发生任何变化
            if (SCHEDULED_FUTURES.containsKey(cronKey) && CRON_TASKS.get(cronKey).getExpression().equals(cronNewValue)) {
                log.info("定时任务没有发生任何变化, taskName={}, cronKey={}", schedule.taskName(), schedule.cronKey());
                return;
            }

            // 如果策略执行时间发生了变化,则取消当前策略的任务
            boolean isUpdate = SCHEDULED_FUTURES.containsKey(cronKey) && cancel(cronKey);

            CronTask task = new CronTask(schedule, cronNewValue);
            CRON_TASKS.put(cronKey, task);
            ScheduledFuture<?> future = Optional.ofNullable(taskRegistrar.getScheduler())
                    .orElse(new ConcurrentTaskScheduler()).schedule(task.getRunnable(), task.getTrigger());
            SCHEDULED_FUTURES.put(cronKey, future);

            if (isUpdate) {
                log.info("定时任务修改, taskName={}, cronKey={}, taskNewCron={}",
                        schedule.taskName(), cronKey, cronNewValue);
            } else {
                log.info("定时任务新增, taskName={}, cronKey={}, taskCron={}",
                        schedule.taskName(), cronKey, cronNewValue);
            }

        });
    }

    private boolean cancel(String cronKey) {
        ScheduledFuture<?> future = SCHEDULED_FUTURES.get(cronKey);
        if (Objects.nonNull(future)) {
            SCHEDULED_FUTURES.get(cronKey).cancel(false);
        }
        SCHEDULED_FUTURES.remove(cronKey);
        CRON_TASKS.remove(cronKey);
        return true;
    }

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {

        this.taskRegistrar = taskRegistrar;

        this.refreshTasks(scheduleMap.values());
    }
}

这里监听NacosConfigReceivedEvent事件即可得知什么时候配置文件发生了更改,在更改的时候我们将对应的定时任务也进行匹配并更改即可.这里我们通过cronKey来进行匹配. 当然不要忘了 监听配置更改会监听到所有 这里我们只关注该关注的即可.
而SchedulingConfigurer只是用于来拿取所需的ScheduledTaskRegistrar这样即可进行对应的监听

现在我们可以来测试下

@Component
@Slf4j
public class TestSchedule1 implements AbstractDynamicSchedule {


    @Override
    public String taskName() {
        return "测试定时任务";
    }

    @Override
    public String cronKey() {
        return "test-dynamic-schedule";
    }

    @Override
    public void run() {
        log.debug(new Date() + "----1");
    }
}

Yaml文件:

test-dynamic-schedule: 0 * * * * ? 

Run Info:

本文到这结束啦.
原作: imyzt 感恩思为~ 感恩imyzt~
灵感来自job
思路来自这: https://my.oschina.net/serge/blog/864162

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
nacos配置中心是一个用于集中管理和动态配置应用程序的配置的开源项目。在Spring Boot应用程序中使用nacos配置中心,需要进行一些配置和依赖。首先,需要在application.properties或application.yaml文件中配置nacos相关的属性,比如设置应用的名称、nacos服务的地址、命名空间、分组等。其次,在项目的依赖中添加spring-cloud-alibaba-nacos-config的依赖,比如在pom.xml文件中添加对应的依赖项。最后,可以添加spring.config.import=nacos:的属性来导入nacos配置中心的配置,如果不需要配置,可以使用spring.config.import=optional:nacos:来进行配置。如果想要禁用这个检查,可以设置spring.cloud.nacos.config.import-check.enabled=false。总之,通过这些配置和依赖,可以方便地使用nacos配置中心来管理和更新应用程序的配置。123 #### 引用[.reference_title] - *1* *2* [SpringBoot+Nacos实现配置中心](https://blog.csdn.net/pzjtian/article/details/107804346)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}} ] [.reference_item] - *3* [记录一次SpringBoot3+Nacos Config做配置中心时,No spring.config.import property has been defined的...](https://blog.csdn.net/qq_27047215/article/details/129726096)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值