1.0版本是在0.9版本上进行重构和优化,具体关于0.9的文档可以见地址:https://gitee.com/hyxl-520/autojob/blob/master/README.md,由于变动相比0.9较大,因此此项目单独放在一个仓库。
如果你只是想直接拿来使用,可以直接跳过“变动”的内容,直接看“开始使用”
目标需求本框架主要是嵌入式的动态任务调度框架,类似于XXLJob但又不同于XXLJob。本框架无需作为一个web服务单独运行于一个进程或者一台服务器上,使用时可以直接嵌入到项目内部作为一个模块功能使用。框架提供任务的动态CURD,支持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}"
缩减后的参数暂不支持对象型的参数,以上的参数格式对应如下的参数列表
void exampleMethodV0_9(String str,Integer integer,Double decimal,Boolean bool,Task task){
//....
}
void exampleMethodV1_0(String str,Integer integer,Double decimal,Boolean bool){
//....
}
心跳监听器
0.9有个心跳监听器,主要就是一个守护线程对注册了的所有任务进行一个监视,如果异常断开就重启,在1.0版本中使用SingleThreadScheduledExecutor,因其本身具有异常恢复机制,则去掉了心跳检测器。
生命周期0.9版本的事件如下
在1.0版本中新增TaskForbiddenEvent事件,当任务被禁止执行时发布,主要原因是用户没有配置相关任务所在的类路径到白名单。
注解调度0.9版本只提供一个注解@AutoJob,用户可以将要被框架动态调度的方法上加上此注解,系统启动时将会自动注册。1.0版本新增@Conditionl注解,主要功能同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 # 重试次数
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类下看到有很多测试方法,如下面一个方法
@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看到我们任务的日志和调度日志
error_stack为任务运行失败的堆栈信息,出错时会保存。
拓展用法 API调度框架支持通过Rest接口进行调度,接口文档见地址:链接: https://www.apifox.cn/apidoc/shared-0aabd128-3ec2-49bb-8c31-03d837389029 访问密码 : autojob 。
框架内在com.example.autojob.api包下示列了API调度的方法,用户可以根据项目需要书写更多的调度方法,API调度的两个门户类分别是AutoJobAPIScheduler和AutoJobLogFacade,前者是API调度器,提供了任务的注册、查询、删除、编辑等相关方法,后者是任务日志门户,提供任务日志和运行日志的获取方法,包含全部日志获取和实时日志获取。
注解调度 @AutoJob注解可配置字段如下:
String startTime() default "";
StartTime defaultStartTime() default StartTime.EMPTY;
int repeatTimes() default 1;
Class extends IMethodObjectFactory> methodObjectFactory() default DefaultMethodObjectFactory.class;
Task.TaskType taskType() default Task.TaskType.FOR_NOW_TASK;
long cycle() default 0;
TimeUnit unit() default TimeUnit.MILLISECONDS;
boolean isEndurance() default false;
String attributes() default "";
1、启动时间
启动时间有两种配置方式,一种是startTime="yyyy-MM-dd HH:mm:ss"方式,一种是defaultStartTime,defaultStartTime是一个枚举类,包含如下三个变量
NOW,
THEN_HOUR,
THEN_HALF_HOUR;
如果没指定启动时间则会根据autoJob.annotation.defaultDelayTime的值延迟启动。
2、重复次数重复次数需要和周期一起使用,重复次数为-1时为永久性任务,此时必须指定非负数周期,如果任务执行时长大于周期,框架会在任务执行完成后立即启动下一次执行。
3、方法运行对象工厂方法的运行可能会依赖于类的全局变量,比如mapper,service等,这时就必须配置运行对象工厂,框架提供了工厂的一个默认实现,其会尝试从Spring的容器里获取对象,如果获取不到则会调用对象所属类的无参构造方法创建一个新的对象。
4、是否允许持久化在任务量很大的情况下,内存资源很是宝贵,有些任务的启动时间或者周期距离现在相对比较长,为了提高效率,可以将这些任务进行持久化到数据库,框架也会注册一个周期性的调度任务从数据库取出一个时间段的待执行任务,该任务会占用一个队列资源。
@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);
}
@AutoJob(defaultStartTime = StartTime.NOW, repeatTimes = -1, cycle = 24, unit = TimeUnit.HOURS)
@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 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一定不要吝啬,直接私聊我 希望我的努力对你有帮助(*^▽^*) 暑期编程PK赛 得CSDN机械键盘等精美礼品!


