其他分享
首页 > 其他分享> > 动态任务调度框架V1.0说明文档

动态任务调度框架V1.0说明文档

作者:互联网

动态任务调度框架(AutoJob)V1.0

1.0版本是在0.9版本上进行重构和优化,具体关于0.9的文档可以见地址:https://gitee.com/hyxl-520/autojob/blob/master/README.md,由于变动相比0.9较大,因此此项目单独放在一个仓库。

如果你只是想直接拿来使用,可以直接跳过“变动”的内容,直接看“开始使用”

文档变动

标题 日期 内容
配置框架 2022-7-18 新增错误重试间隔
参数解析 2022-7-18 修改对象型参数格式
拓展用法 2022-7-18 新增cron like表达式标题
SQL脚本 2022-7-18 新增cron_expression字段
注解调度 2022-7-18 新增@FactoryAutoJob注解

目标需求

本框架主要是嵌入式的动态任务调度框架,类似于XXLJob但又不同于XXLJob。本框架无需作为一个web服务单独运行于一个进程或者一台服务器上,使用时可以直接嵌入到项目内部作为一个模块功能使用。框架提供任务的动态CURD,支持cron like表达式;支持API调度和注解调度;提供任务白名单;提供完整的任务日志和运行日志记录,框架采用动态Appender实现,无需将日志写入文件,日志可以存入缓存或者数据库;提供任务完整的生命周期钩子,可以进行报警、提醒等各种拓展......更多功能说明见后文。

变动

执行器

0.9版本没有执行器这一概念,任务的启动、完成、事件发布均在一个类中由几个各司其职的线程完成,任务运行则是创建一个Callable对象丢到线程池执行。在1.0版本中,将0.9中的AutoJobExecutor拆分成调度器和执行器,并且执行器使用享元模式进行池化,一方面减少了内存对象的数目,另一方面也简化了任务运行过程。

调度器

调度器是1.0版本从0.9中剥离出的一个模块,简单说就是如何从任务队列取出任务,并且如何提交到执行器池运行。调度器主要功能是按照不同策略进行任务调度,如启动调度、完成调度、API调度等等,用户可以新增调度器来适应具体需求。

注册器

注册器在0.9中也有存在,主要功能是对任务队列进行操作。为了保证安全,1.0版本在注册器增加了一条处理器链和一条过滤器链,用户可以配置哪些任务能被注册进调度队列运行。

日志组件

日志组件1.0和0.9的实现方式基本相同,都是使用log4j2的动态Appender+filter实现。1.0对外提供日志门户,可以通过门户获取任务日志和运行日志,甚至能实时获取任务日志。

参数解析

0.9版本支持整数、小数、布尔、字符串和任意对象类型,1.0版本在原支持上提供长整型参数,并且简化了参数的格式,0.9版本你的参数格式如下:

attributes":[
    {
        "type":"string",
        "values":{
            "value":"这是一个字符串参数"
        },
        "type":"integer",
        "values":{
            "value":26
        },
        "type":"decimal",
        "values":{
            "value":3.1415926535
        },
        "type":"boolean",
        "values":{
            "value":true
        },
        "type":"com.example.autojob.skeleton.tq.Task",
        "values":{
            "id":123456,
            "startTime":"2020-05-20 13:14:20"
        },
    }
]

1.0版本以上的参数可以缩减成:

attributes:"{'这是一个字符串参数',26,3.1415926535,true}"

缩减后的参数暂不支持对象型的参数,以上的参数格式对应如下的参数列表

	/**
     * @param str	
     * @param integer	必须是包装类型
     * @param decimal	必须是包装类型
     * @param bool	必须是包装类型
     * @param task	对象参数,使用Gson的fromJson转化
     * @return void
     * @author Huang Yongxiang
     * @date 2022/1/26 15:29
    */
void exampleMethodV0_9(String str,Integer integer,Double decimal,Boolean bool,Task task){
    //....
}

void exampleMethodV1_0(String str,Integer integer,Double decimal,Boolean bool){
    //....
}

同时1.0为了保证安全,对象型参数取消了type字段指定对象路径,对象型参数1.0版本设置如下

0.9版本如下=>
{
    "type":"com.example.autojob.skeleton.tq.Task",
    "values":{
       "id":123456,
       "startTime":"2020-05-20 13:14:20"
    }
}

1.0版本如下=>
{
    "type":"object",
    "values":{
       "id":123456,
       "startTime":"2020-05-20 13:14:20"
    }
}

调度配置

任务调度主要需要描述什么时候启动任务,在0.9版本中只提供一种配置方式,即通过对启动时间、重复次数以及周期来决定一个任务如何调度。在1.0版本中,引入了强大的cron like表达式来进行任务调度,用户可以根据需求决定使用属性配置的方式来决定调度规则还是通过cron like表达式来决定调度规则。

心跳监听器

0.9有个心跳监听器,主要就是一个守护线程对注册了的所有任务进行一个监视,如果异常断开就重启,在1.0版本中使用SingleThreadScheduledExecutor,因其本身具有异常恢复机制,则去掉了心跳检测器。

生命周期

0.9版本的事件如下

https://gitee.com/hyxl-520/autojob/raw/master/images/event.png

在1.0版本中新增TaskForbiddenEvent事件,当任务被禁止执行时发布,主要原因是用户没有配置相关任务所在的类路径到白名单。

注解调度

0.9版本只提供一个注解@AutoJob,用户可以将要被框架动态调度的方法上加上此注解,系统启动时将会自动注册。1.0版本新增@Conditionl注解和@FactoryAutoJob注解,前者主要功能同Spring的Conditional注解,主要用于实现条件注册,比如有一个任务你希望其满足一定条件才注册则可以使用该注解;后者是允许你能使用自定义生成任务对象的工厂来注册任务。

开始使用

下载源码

框架在码云上已开源,可以在地址:https://gitee.com/hyxl-520/auto-job-plus下载源码。

导入项目

项目本身是一个Spring web项目,使用Maven管理依赖。你可以直接导入IDEA或者Eclipse以及其他IDE,也可以导入到自己项目的一个包下使用。

建表

框架本身是支持内存运行,只是部分配置下需要使用的数据库,主要是用于保存任务的日志和运行日志,以及持久化过长的任务,具体的SQL文件见auto_job.sql。

配置框架

框架提供了较为完善的配置内容,所有相关配置内容如下

autoJob:
  context:  # 任务队列配置
    length: 100 # 任务调度队列的长度
    allowCheckExist: true # 在插入任务时是否验证任务的存在性,存在的任务将阻止插入
    
  annotation: # 注解配置
    enable: true # 是否开启注解调度,开启后框架会扫描所有被@AutoJob注解的方法并将其注册进调度队列
    defaultDelayTime: 30 # 如果注解没有指定启动时间时默认延迟启动的时间:min
    
  executor: # 执行器配置
    poolSize: 100 # 执行器池的执行器数目
    waitQueueLength: 1000 # 执行器池的等待队列长度,超长时会拒绝执行或者根据配置的拒绝处理器处理
    
  register: # 注册器配置
    filter:
      enable: true # 是否开启注册过滤器
      # 重要:允许执行的类路径,支持正则表达式,多个路径逗号分割,为空时所有方法均会被拦截
      classPath: .*.skeleton.model.support.*,.*.job.Jobs 
      
  scheduler: # 调度器配置
    finished:
      error:
        retry:
          enable: true # 任务发生错误时是否运行重试
          retryCount: 3 # 重试次数
          interval: 5 # 重试间隔,min,支持小数
    longTask:
      # 是否启用过长任务持久化调度器,开启后框架会启动一个周期性持久化任务调度任务,会周期的从数据库调出持久化的任务.
      enable: false 
      threshold: 24 # 认为一个任务为长任务的阈值:hour,允许小数
    api:
      enable: true # 是否启用API调度,启用后框架可以通过Rest接口调用,框架将会启用一个API调度器和日志门户
      
  logging: # 任务日志配置
    enable: true  # 是否运行日志捕获
    taskLog: # 任务日志配置
      #      pattern: "%d{yyyy-MM-dd HH:mm:ss,SSS} - %p - [%t] %c - [%F,%L] - %m%n"
      pattern: "%m%n" # 捕获的日志格式,同log4j2的格式
      handler:
        poolSize: 100 # 日志处理器数目,最好和执行器数目相同
        waitQueueLength: 1200 # 日志处理器的等待队列长度,超长时会拒绝执行或者根据配置的拒绝处理器处理
      memory:
        enable: true # 是否启用内存运行模式,启用后日志将会放入内存缓存而不会存入数据库
        length: 100 # 日志缓存的最大长度,缓存超过长度后会将插入时间最早的日志过期
        defaultExpireTime: 3 # 日志缓存的过期时间:min
    runLog: # 任务调度日志配置
      memory: # 配置内容同autoJob.logging.taskLog.memory
        enable: true
        length: 100
        defaultExpireTime: 3

创建第一个动态任务

如果是直接打开项目源码的话可以在com.example.autojob.job.Jobs类下看到有很多测试方法,如下面一个方法

    /**
     * 参数注入演示
     *
     * @return void
     * @author Huang Yongxiang
     * @date 2022/7/14 14:57
     */
    @AutoJob(attributes = "{'我爱你,心连心'}", startTime = "2022-7-6 00:00:00")
    public void print(String str) {
        log.info("print方法成功执行:{},这是一条任务日志", str);
    }

将代码中@AutoJob所在行的注释取消,启动项目,可以看到如下输出

2022-07-14 15:00:51,994 - INFO - 任务:com.example.autojob.job.Jobs.print,准备在执行器:printExecutor中执行
2022-07-14 15:00:51,994 - INFO - 任务:com.example.autojob.job.Jobs.print的日志将由处理器:printHandler处理
2022-07-14 15:00:51,994 - INFO - print方法成功执行:我爱你,心连心,这是一条任务日志
2022-07-14 15:00:51,995 - INFO -  任务:com.example.autojob.job.Jobs.print,在执行器:printExecutor中成功执行完成
2022-07-14 15:00:51,997 - DEBUG - 任务:print的日志:print方法成功执行:我爱你,心连心,这是一条任务日志
2022-07-14 15:00:52,007 - INFO - 任务:com.example.autojob.job.Jobs.print的日志由处理器:printHandler处理已完成
2022-07-14 15:00:52,104 - INFO -  成功保存任务:com.example.autojob.job.Jobs.print的日志:1条

如果我们的日志的内存模式都是关闭的情况下的话,我们可以在表run_log和job_log看到我们任务的日志和调度日志

1657782326202

1657782345502

error_stack为任务运行失败的堆栈信息,出错时会保存。

拓展用法

cron like表达式

框架从1.0开始支持cron like表达式,配置cron like表达式后就无需再配置启动时间、重复次数以及周期等参数。如下使用方式

	/**
     * 参数注入演示
     *
     * @return void
     * @author Huang Yongxiang
     * @date 2022/7/14 14:57
     */
    @AutoJob(attributes = "{'我爱你,心连心'}", cronExpression = "5,10,15,20,50 * * * * ?")
    public void print(String str) {
        log.info("print方法成功执行:{},这是一条任务日志", str);
    }

print方法会在每一分钟的5、10、15、20和50秒分别执行一次。

API调度

框架支持通过Rest接口进行调度,接口文档见地址:链接: https://www.apifox.cn/apidoc/shared-0aabd128-3ec2-49bb-8c31-03d837389029 访问密码 : autojob 。

框架内在com.example.autojob.api包下示列了API调度的方法,用户可以根据项目需要书写更多的调度方法,API调度的两个门户类分别是AutoJobAPISchedulerAutoJobLogFacade,前者是API调度器,提供了任务的注册、查询、删除、编辑等相关方法,后者是任务日志门户,提供任务日志和运行日志的获取方法,包含全部日志获取和实时日志获取。

注解调度

@AutoJob注解

可配置字段如下:

	/**
     * 启动时间
     */
    String startTime() default "";

    /**
     * 默认启动时间,和startTime同时存在时以startTime为准
     */
    StartTime defaultStartTime() default StartTime.EMPTY;

	/**
     *cron like表达式
     */
    String cronExpression() default "";

    /**
     * 重复次数,-1表示为永久性任务,永久性任务必须指定任务周期
     */
    int repeatTimes() default 1;

    /**
     * 方法运行对象工厂
     */
    Class<? extends IMethodObjectFactory> methodObjectFactory() default DefaultMethodObjectFactory.class;

    /**
     * 任务类型,暂无特别作用
     */
    Task.TaskType taskType() default Task.TaskType.FOR_NOW_TASK;

    /**
     * 周期:默认为毫秒,当重复次数大于1时该值必须大于0
     */
    long cycle() default 0;

    /**
     * 周期的时间单位,默认为毫秒
     */
    TimeUnit unit() default TimeUnit.MILLISECONDS;

    /**
     * 执行时间距离现在过长时是否允许先进行持久化
     */
    boolean isEndurance() default false;

    /**
     * 参数,支持简单参数和复合参数
     */
    String attributes() default "";

1、启动时间

启动时间有种配置方式,一种是startTime="yyyy-MM-dd HH:mm:ss"方式,一种是defaultStartTimedefaultStartTime是一个枚举类,包含如下三个变量

	/**
     * 现在时间
     */
    NOW,
    /**
     * 现在时间后的一个小时
     */
    THEN_HOUR,
    /**
     * 现在时间后的半小时
     */
    THEN_HALF_HOUR;

如果没指定启动时间则会根据autoJob.annotation.defaultDelayTime的值延迟启动。

2、重复次数

重复次数需要和周期一起使用,重复次数为-1时为永久性任务,此时必须指定非负数周期,如果任务执行时长大于周期,框架会在任务执行完成后立即启动下一次执行。

3、cron like表达式

cron like表达式在任务调度中经常使用,1.0版本也引入了这种强大的表达式。在配置cron表达式后,不用再配置启动时间、重复次数以及周期。

4、方法运行对象工厂

方法的运行可能会依赖于类的全局变量,比如mapper,service等,这时就必须配置运行对象工厂,框架提供了工厂的一个默认实现,其会尝试从Spring的容器里获取对象,如果获取不到则会调用对象所属类的无参构造方法创建一个新的对象。

5、是否允许持久化

在任务量很大的情况下,内存资源很是宝贵,有些任务的启动时间或者周期距离现在相对比较长,为了提高效率,可以将这些任务进行持久化到数据库,框架也会注册一个周期性的调度任务从数据库取出一个时间段的待执行任务,该任务会占用一个队列资源。

@FactoryAutoJob注解

某些时候,可能@AutoJob注解并不能满足你的需求,比如你希望某个任务的周期能够在系统文件中配置,这时你就可以通过继承ITaskFactory接口来自己实现一个任务工厂对象,如com.example.autojob.skeleton.model.support.SystemSupportTask类中的scheduleTaskPeriodic方法,

	@FactoryAutoJob(ScheduleTaskPeriodicFactory.class)
    @Conditional(ScheduleTaskCondition.class)
    public void scheduleTaskPeriodic() {
        log.info("周期化长任务调度器启动");
        String from = DateUtils.getTime();
        String to = Convert.addTime(from, "yyyy-MM-dd HH:mm:ss", (long) (config.getLongTaskThreshold() * 60 * 60 * 1000), "yyyy-MM-dd " + "HH:mm:ss");
        List<Task> taskList = mapper.getUnFinishedLongJobBetween(from, to);
        int count = 0;
        if (taskList.size() > 0) {
            count = (int) taskList.stream().filter(item -> register.registerTask(item)).count();
        }
        log.info("周期任务调度器成功调度:{}条持久化的任务进入调度队列", count);
    }

//ScheduleTaskPeriodicFactory.java
public class ScheduleTaskPeriodicFactory implements ITaskFactory {

    @Override
    public Task newTask(Environment environment, Class<?> methodClass, String methodName) {
        try {
            double cycle = Double.parseDouble(Objects.requireNonNull(environment.getProperty("autoJob.scheduler.longTask.threshold")));
            Task task = TaskFactory.createTask(methodClass, methodName, null, false, new DefaultMethodObjectFactory());
            task.setStartTime(DateUtils.getDateTime()).setRepeatTimes(-1).setCircle((long) (cycle * 60 * 60 * 1000)).setIsEndurance(false);
            return task;
        } catch (Exception e) {
            e.printStackTrace();
            log.error("创建任务:{}.{}发生异常:{}", methodClass.getName(), methodName, e.getMessage());
        }
        return null;
    }
}

@Conditional注解

其功能类似于Spring的同名注解,实现条件注册。可能有的任务你希望满足某种条件才允许执行,这时你就可以创建一个类实现IAutoJobCondition接口,并且将其在对应方法上标注出来即可实现按需注册。如com.example.autojob.skeleton.model.support.SystemSupportTask类中的scheduleTaskPeriodic方法,这个方法就是周期性从数据取出待执行的持久化任务的方法。

@Component
@Slf4j
public class SystemSupportTask extends AbstractSystemSupportTask {

    @Autowired(required = false)
    public SystemSupportTask(AutoJobRegister register, AutoJobMapper mapper, AutoJobConfig config, AutoJobLogConfig logConfig) {
        super(register, mapper, config, logConfig);
    }

    @FactoryAutoJob(ScheduleTaskPeriodicFactory.class)
    @Conditional(ScheduleTaskCondition.class)
    public void scheduleTaskPeriodic() {
        log.info("周期化长任务调度器启动");
        String from = DateUtils.getTime();
        String to = Convert.addTime(from, "yyyy-MM-dd HH:mm:ss", (long) (config.getLongTaskThreshold() * 60 * 60 * 1000), "yyyy-MM-dd " + "HH:mm:ss");
        List<Task> taskList = mapper.getUnFinishedLongJobBetween(from, to);
        int count = 0;
        if (taskList.size() > 0) {
            count = (int) taskList.stream().filter(item -> register.registerTask(item)).count();
        }
        log.info("周期任务调度器成功调度:{}条持久化的任务进入调度队列", count);
    }

}


public class ScheduleTaskCondition implements IAutoJobCondition {
    @Override
    public boolean matches(Environment environment) {
        return Boolean.parseBoolean(environment.getProperty("autoJob.scheduler.longTask.enable"));
    }
}

白名单

白名单功能为1.0开发的,主要依赖于autoJob.register.filter.classPath配置项,配置可被调度方法所在的类路径,同时拦截其他方法的执行,保证系统安全。如框架的原始配置

classPath: .*.skeleton.model.support.*,.*.job.Jobs 

第一个路径是框架的一些支撑任务,第二个是测试类所在的路径。

其他功能

用户可以自定义自己的注册过滤器、处理器,也可以新增更多的参数解析器,具体更多拓展各位大佬可以阅读代码。

对啦!框架的内还有本人写的一些公共小组件可供各位大佬参考,比如可以根据流量动态调整线程池核心线程数和最大线程数的动态线程池ThreadPoolExecutorHelper,以及支持消息过期的内存消息队列MessageQueueContext等等。

如果对你有帮助或者能解决你目前所遇到的一些问题,恳请 点赞 收藏 关注 如果有BUG一定不要吝啬,直接私聊我 希望我的努力对你有帮助(*^▽^*)

标签:1.0,调度,V1.0,任务,文档,注解,日志,任务调度
来源: https://www.cnblogs.com/hyxl-top/p/16491077.html