背景:
目前定时任务都在各自服务中配置,每个服务至少有两个实例启用,定时任务冗余执行,造成资源浪费的情况。如果想修改任务的时间,需要重启服务,比较繁琐。并且任务执行成功与否只能通过生产日志查看,极为不便。为集中配置各个服务的定时任务,统一管理,便于查看任务执行情况,需要开发定时任务管理模块。
功能:
- 统一配置,管理定时任务
- 动态启用,停止定时任务
- 集中查看定时任务执行结果
技术实现图:
流程:
架构设计
分布式任务调度包含两个部分:
调度中心:
- 对定时任务进行配置,包括执行时间,执行的服务,方法,参数,是否记录正确结构
- 启用/停止定时任务
- 临时执行一次定时任务
- 查看执行结果:记录所有结果;只记录异常结果
执行器:
- 添加至服务依赖,通过@AcutatorMethod(name="methodName")注解获取需要定时执行的方法等配置
- 接收调度中心的请求并执行该方法
- 将执行结果反馈至调度中心
调度中心启用时,扫描已配置定时任务的表,如果是“已启用”状态,即读取相关配置建立定时任务。配置任务时,通过执行器读取各个服务需要执行的方法列表进行配置。
定时任务的录入:
通过调度中心配置定时任务:
- 选择服务,方法(对应描述),执行时间,参数。新增成功后点击启用即可激活定时任务。当一个节点的定时任务启用成功即认为成功。可在build表中查看定时任务启用信息,包括节点和启用状态。
在用户服务中调用调度中心创建定时任务:
- 场景:某些需要启用定时任务的功能,固定执行时间,不需要手动配置
- 新建ScheduleTaskBean,传入服务名,方法名,参数,通过restTemplate调用调度中心接口,插入定时任务记录并启用,成功后返回成功状态,用户服务即可知道调用成功。定时任务可在调度中心中查到,无法修改,删除,只可上/下线,以避免网络故障引起的状态不一致。
定时任务在调度中心的创建:
- 通过p/b通知所有调度中心启用定时器
- 调度中心通过事件订阅实现启用定时任务。即调度中心收到激活某个定时任务的请求时,发布启用消息。所有调度中心订阅该事件,建立定时任务。这样可实现一个服务收到请求,所以服务均可执行动作。
- 后续可优化为可以配置一个定时任务启用几个调度中心节点执行,保证高可用的同时避免资源浪费。
逻辑图:
定时任务的创建:
- 目前是基于redis的抢锁机制,也通过zookeeper抢锁
- 基于数据库抢锁
- 基于zookeeper进行任务协调
比较:
- 基于redis/zk的抢锁机制需要引入第三方中间件,增加系统的复杂度,但如果系统本身部署了这些组件,可以使用。
- 也可以基于zk进行协调,选择哪个节点为主节点执行任务调度,如果主节点挂了,由从节点执行定时任务。
- Actuator框架应该支持配置选择redis/redis cluster/zk/数据库的抢锁方式或由zk进行协调。
定时任务通知actuator:
抢到锁的调度中心通过restTemplate调用actuator,实现负载均衡
- 发送成功后插入日志并记录状态“任务正在执行”,收到actuator响应后,通过日志id找到对应日志,记录执行时间并更新结果
- restTemplate配置负载均衡策略,超时时间,最大重试次数等参数
- 也可以通过mq向queue发送消息,actuator服务订阅该queue,消费到消息的服务执行相应方法。这个方案的缺点就是每个acutator服务都需要添加mq依赖,增加了系统的复杂度
Actuator执行
- 集成到java服务中,在服务初始化的时候得到执行定时任务的bean及方法,保存到map中,供调度中心获取,进而通过画面配置
- 在执行定时任务时,从map中获取注入到spring的bean和method,通过反射执行具体方法
- 暴露接口/${service-name}/actuator:接收调度中心发出的定时任务请求,通过反射异步执行方法。异步执行方法的原因是尽快响应调度中心,避免堵塞线程。
- 执行完成后将执行结果通过feign发送至调度中心,更新执行结果
收获:
- 开发框架时,应尽可能减少系统对中间件的依赖,或者写成可扩展的接口
- 利用注解增强服务的行为
- 服务应该是无状态的,便于横向扩展及高可用
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。