分布式定时任务系列3:任务执行引擎设计https://blog.csdn.net/weigeshikebi/article/details/123145716?spm=1001.2014.3001.5502在前面一节讨论中,设想基于xxl-job包装一个业务执行引擎出来,达到以下几个目的:
-
并发问题
-
重试机制
-
统一的编程模型
-
充分利用分布式系统的能力
系统设计
根据上面的几点,这节就想办法动手实现一个出来。
DB存储
由于目的是先把架子搭起来,而不用考虑其它性能方面,所以选用DB进行存储。初步的表设计如下
表主要字段解释如下
字段 | 类型 | 说明 |
task_type | int | 这个字段用于区分不同的业务/功能,比如短信发送,日终处理,权限清理等 |
sub_type | int | 这个字段的目的,是在同一个task_type下,如果有多个步骤都要进行定时任务处理,比如短信发送时:捞取符合条件的待发送账户,余额结算,短信发送等 |
task_status | int | 任务状态,表示任务的执行过程,比如:INIT,PROCESSING,FAIL,SUCCESS等 |
flow_id | string | 用来跟踪同一个task_type下,多个sub_type的执行过程的辅助字段 |
类图
对于异步任务来说, 使用者理论上只关心业务逻辑,所以要尽量只关注业务代码,而其它的都交给引擎去处理。所以最好是提供一个接口,让使用者来实现业务逻辑
- Task接口是任务接口,任何一个业务处理都需要实现这个接口:比如MsgTaskImpl,BalanceTaskImpl等
- MsgTaskImpl,BalanceTaskImpl是具体的业务实现,如果需要使用异步任务引擎,需要实现Task接口
- TaskEngine是异步任务的入口,也是引擎被刺的核心类,里面有个execute方法,业务处理调用此方法驱动任务执行
- TaskFactory是任务的工厂类,维护任务类型与任务实现之间的关系:当注册一个任务时,调用register方法设置到内部的一个Map变量,TaskEngine执行时,通过getByType获取对应的task实现执行,也就做到动态扩展
- TaskStatusEnum,是任务的状态,任务是由业务产生,初始状态为INIT,被引擎执行之后变成PROCESSING,执行完成后,根据结果变为终态:SUCCESS/FAIL
- InitializingBean,Spring的实例化接口,用它来将task实现注册到TaskFactory中
- JobHandler,里面配置异步任务的规则,@XxlJob注解对应xxl-job平台任务
执行流程图
现在来看下一个任务执行的整体顺序,假设是短信发送的业务
- 业务系统产生异步任务,存储到DB,调用task引擎提供的接口
- xxl-job定时任务触发,通过taskEngine扫描到对应的处理任务
- 将任务从INIT更新为PROCESSING,表示正在处理中
- taskEngine根据taskType,subType从TaskFactory中获取对应的处理实例,执行对应的业务逻辑:业务逻辑由task实现的业务处理编写
- 执行完成,更新任务状态为终态:如果成功为SUCCESS,失败为FAIL
并发的处理
这里要考虑并发的问题,即多台实例执行同一个任务时,只能有一个线程在执行,自然而然想到锁,对于分布式的情况,可以用分布式锁来控制,所以这里还需要依赖redis之类的做分布式锁
异常的处理
这里还要考虑,如果是业务处理发生异常,任务状态该怎么处理?直接更新为FAIL,是最简单且直接的处理,但是如果异常不是业务异常,是网络抖动之类的,是不是应该考虑兼容,做到自动重试?所以这里面还要涉及到重试的处理
基于此,上面的类图,应该增加对redis的依赖
且最终和执行逻辑为