基于ThreadPoolTaskScheduler手写任务调度

背景

 

考虑到系统中使用的XXL-JOB太过重量级,且要部署当独的服务,故找了下轻量级的解决方案,发现Java多线程API中的ThreadPoolTaskScheduler即可实现我的需求,于是动手实践。

具体实现

  • 首先,对我们的目标任务进行包装
@Data
public class EbsTaskInfo {

    /**
     * 目标任务
     */
    private final Task task;

    private String params;

    /**
     * 执行结果
     */
    @Nullable
    volatile ScheduledFuture<?> future;

    public EbsTaskInfo(Task task) {
        this.task = task;
    }
}
  • 定一个一个执行器的处理器接口,通一个run方法即可
public interface IEbsJobHandler {
    ReturnT<String> run(String params);
}
  • 具体业务定时器基于IEbsJobHandler实现其run方法逻辑即可
  • 开始定时任务的关键代码
    /**
     * 关键方法,开始定时任务
     *
     * @param beanName
     * @param cron
     * @return
     */
    @Override
    public boolean scheduleTask(String beanName, String cron, String params) {
        IEbsJobHandler target = null;
        try {
            // 从 spring 容器中获取到执行器的bean
            target = (IEbsJobHandler) context.getBean(beanName);
        } catch (Throwable e) {
            throw new ServiceException("无法获取bean信息, 无法执行任务:" + e.getMessage());
        }

        Method method = null;
        try {
            // 通过该bean,反射获取到该bean的run方法,不带参数
            method = target.getClass().getMethod("run");
        } catch (NoSuchMethodException e) {
            throw new ServiceException("无法获取run方法, 无法执行任务:" + e.getMessage());
        }

        try {
            // 构建CronTask,将目标执行器 + cron表达式传入,设置时区为:系统默认时区
            CronTask cronTask = new CronTask(createRunnable(target, method),
                    new CronTrigger(cron, TimeZone.getTimeZone(ZoneId.systemDefault())));

            // 构建任务执行器
            EbsTaskInfo taskInfo = new EbsTaskInfo(cronTask);
            // 将参数保存起来,后面执行正真执行器方法时,需传入
            taskInfo.setParams(params);

            // 启动执行器
            ScheduledFuture<?> future = taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
            // 将执行结果异步放入执行任务对象中
            taskInfo.setFuture(future);

            // 调用取消任务
            // log.info("若该任务已存在,则先将已存在的任务先取消");
            cancelTask(beanName);

            // 以beanName为key,任务对象为value,将其放入taskMap中,核心业务逻辑完成
            taskMap.put(beanName, taskInfo);

            return true;
        } catch (Throwable e) {
            log.error("配置异常:{}", e.getMessage());
            throw new ServiceException("配置异常" + e.getMessage());
        }
    }

    /**
     * 关键方法
     *
     * @param target
     * @param method
     * @return
     */
    private Runnable createRunnable(Object target, Method method) {
        // 通过AopUtils获取到调用的方法对象
        Method invocableMethod = AopUtils.selectInvocableMethod(method, target.getClass());
        // 并将其包装成ScheduledMethodRunnable返回,用于创建定时任务
        // ScheduledMethodRunnable 实现了 Runnable run方法
        // 其线程run方法内部逻辑实现了通过传入的目标实例 + 目标方法进行执行
        return new ScheduledMethodRunnable(target, invocableMethod);
    }
  • 最好再配置下线程池
    @Bean("taskScheduler")
    public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setThreadNamePrefix("sync-pool-");
        scheduler.setPoolSize(8);
        scheduler.initialize();
        return scheduler;
    }
  • 取消任务
task.getFuture().cancel(true);
  • 有了以上知识,核心就实现了,剩下的就是优化
  • 考虑实现动态任务创建,需要创建一张数据表来保存任务基本信息和状态
  • 服务启动的时候,去读数据库表定时任务配置,并让任务run起来
  • 最好在执行器上加一个自定义的注解,好做切面,收集任务执行器的耗时、结果等
  • 另外,考虑一个bean可对应多个Task如何改造?
  • 还有,如果应用集群部署时,任务如何执行?如何保证任务执行的高可用

附上整体设计思路

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值