背景:
目前定时任务都在各自服务中配置,每个服务至少有两个实例启用,定时任务冗余执行,造成资源浪费的情况。如果想修改任务的时间,需要重启服务,比较繁琐。并且任务执行成功与否只能通过生产日志查看,极为不便。为集中配置各个服务的定时任务,统一管理,便于查看任务执行情况,需要开发定时任务管理模块。

功能:

  • 统一配置,管理定时任务
  • 动态启用,停止定时任务
  • 集中查看定时任务执行结果

技术实现图:
image.png

流程:
image.png

架构设计
分布式任务调度包含两个部分:

  • 调度中心:

    • 对定时任务进行配置,包括执行时间,执行的服务,方法,参数,是否记录正确结构
    • 启用/停止定时任务
    • 临时执行一次定时任务
    • 查看执行结果:记录所有结果;只记录异常结果
  • 执行器:

    • 添加至服务依赖,通过@AcutatorMethod(name="methodName")注解获取需要定时执行的方法等配置
    • 接收调度中心的请求并执行该方法
    • 将执行结果反馈至调度中心

调度中心启用时,扫描已配置定时任务的表,如果是“已启用”状态,即读取相关配置建立定时任务。配置任务时,通过执行器读取各个服务需要执行的方法列表进行配置。

定时任务的录入:

  • 通过调度中心配置定时任务:

    • 选择服务,方法(对应描述),执行时间,参数。新增成功后点击启用即可激活定时任务。当一个节点的定时任务启用成功即认为成功。可在build表中查看定时任务启用信息,包括节点和启用状态。
  • 在用户服务中调用调度中心创建定时任务:

    • 场景:某些需要启用定时任务的功能,固定执行时间,不需要手动配置
    • 新建ScheduleTaskBean,传入服务名,方法名,参数,通过restTemplate调用调度中心接口,插入定时任务记录并启用,成功后返回成功状态,用户服务即可知道调用成功。定时任务可在调度中心中查到,无法修改,删除,只可上/下线,以避免网络故障引起的状态不一致。

定时任务在调度中心的创建:

  • 通过p/b通知所有调度中心启用定时器
  • 调度中心通过事件订阅实现启用定时任务。即调度中心收到激活某个定时任务的请求时,发布启用消息。所有调度中心订阅该事件,建立定时任务。这样可实现一个服务收到请求,所以服务均可执行动作。
  • 后续可优化为可以配置一个定时任务启用几个调度中心节点执行,保证高可用的同时避免资源浪费。

逻辑图:
image.png

定时任务的创建:

  • 目前是基于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发送至调度中心,更新执行结果

收获:

  • 开发框架时,应尽可能减少系统对中间件的依赖,或者写成可扩展的接口
  • 利用注解增强服务的行为
  • 服务应该是无状态的,便于横向扩展及高可用

吃水果毫不费力zz
7 声望0 粉丝

啦啦啦