1 背景
随着贝壳的业务功能的不断扩大,具有复杂功能的单体应用随之进入了微服务开发的迭代模式。项目需要作业调度模块是个常见需求,在之前单体系统中,集成了 quartz 完成作业调度模块,因为单体应用集成一次后,单从技术层面看几乎没有新的工作量,而且整体还是比较稳定的,但是当单体应用进行微服务拆分后,很多微服务项目都需要集成作业调度模块,常规的一些作业调度实现,已经无法满足公司级的微服务项目拓张,一种轻量级、分布式、统一管理的作业调度框架势在必行。
2 需求
2.1 从公司角度的需求
由于单体应用改为微服务项目,微服务项目的作业调度功能百花齐放,作业调度系统在整体项目体系中属于底层基础组件项目,整体需易于接入公司的监控和规范,原理需完全掌握,如任务执行出现问题,要有及时的报警以便及时跟进,定时日报,汇总整体微服务系统作业调度情况。作业调度原理要 100%的掌握,不允许线上出现技术原理不清而阻塞故障的修复。当接入作业越来越多时,要有负载机制,监控整体系统运行状态。
2.2 从业务系统角度的需求
作业调度系统要非常易于接入,且接入问题需要有专项小组提供解决方案,不能阻塞业务系统开发,作业需要灵活配置,且调度要准确。而且用户手册要足够强大,作业调度专项小组在必要的情况下要持续支援有些快速开发的业务项目,因为这种突击项目着重关心业务实现,根本不关心基础组件实现细节,更不想遇到组件使用的阻塞。即:投入低,回报高。
3 技术调研
在 github 上调研了目前比较流行作业调度项目,其大致可以分为一下几个形态:
3.1 深度优化定制 Quartz 形态
深度了解学习改造 Quartz 源码,将 Quartz 使用 DB 迁移至使用 zk,通过开发后端管理系统操作 zk,从而来管理整个作业调度任务的配置,这种形态的作业调度,原理上作业调度实现还是在客户端完成,管理系统很弱化。这样的作业调度系统有个与生俱来的优势是:在管理系统出现故障时候也不会影响作业的继续作业。因为客户端项目几乎集成全量的作业调度代码实现。
3.2 Quartz 服务端形态
将 Quartz 抽象并封装成服务端,客户端通过基础 client-refence.jar 完成任务的勾子方法暴露,服务端完成作业配置任务,当作业有任务触发时,通过一种协议触发客户端项目任务的勾子方法,完成调度。这种形态的作业调度框架,客户端一定程度减少了作业调度代码量,和项目性能消耗,但是带来了很多分布式调度问题,需要深入了解整体调度的实现。
3.3 自实现调度系统
这种形态的作业调度系统基本都是功能非常多的,功能自实现一部分,也可通过 SPI 集成一部分,比如调度可以用 Quartz 实现,或是框架自身通过解析语法如:CronExpression 实现 Cron 作业解析出未来一定时间内的任务,当时间到达触发时间。触发可支持客户端集成,或通过一种协议触发客户端项目任务,完成调度。学习这类形态的作业调度架构,可以发人深省,无论从广度或者深度,都可以使学习者对作业调度架构的认识更上一层楼。
4 思考与实践
通过对作业系统的背景需求的认识,和在 github 进行的技术调研学习,结合我们自身特点,在实现自己的作业调度过程中,我们思考了如下几点:
4.1 调度设计与实现
客户端暴露任务,通过注册发现将任务与服务端数据同步,在服务端将发现到的任务自定义的组装成作业,完成整体作业调度功能。服务端集成公司监控和规范。
1)整体架构设计
2)作业调度原理实现
4.2 作业调度服务端高可用的实现方案
服务端通过 zk 主备选举,完成服务端自身高可用实现,另外服务端暴露出健康接口,这里我们公司有对服务健康接口的持续监测,如果集群中,某个健康接口出现异常,会立刻报警给值班人员。
4.3 作业调度服务端实现
这里我们参考了大量技术调研第三种形态中功能实现,通过对调度底层实现方式的学习,实现任务在服务端可配置作业。
### 4.4 如何做到客户端极简实现
作业调度需求项目通过集成 client-refence.jar 将任务方法暴露到注册中心,并内部实现任务触发方法,以便完成任务调度的触发。这里我们通过反射机制,通过在方法上增加特定注解,识别出此注解方法为任务方法。
4.5 任务方法的注册发现
前面说了客户端的极简实现,任务的注册发现我们通过将识别出的任务方法,放到注册中心上面,且通过心跳完成本地暴露任务与注册中心的任务持续的一致性。
4.6 项目权限设计
随之客户端项目越来越多的接入作业平台,肯定是需要一种权限体系完成客户端接入项目的逻辑隔离,这里我们参考 git 项目人员管理体系,项目名称为唯一标识,创建项目的同学为此项目的 owner,可根据系统号邀请其他同学加入此项目,共同维护此项目作业配置。
4.7 异常报警实现
定义异常事件类型,和内容,如:任务失败事件,内容是异常信息等,通过观察者模式结合项目人员完成异常事件的实时报警。结合上面的权限成员,立刻发送给任务所在项目组成员。
4.8 依赖、分片任务的实现
这里我们参考了一些国外的科研项目,结合业内比较流行的设计,通过用有序无环图(DAG)来实现任务的串联依赖,与任务的平级分片。
5 总结
在深入学习作业调度框架的过程中,基本上所有框架实现 Cron 作业都是通过解析 CronExpression 语法实现的,之后通过解析出的触发点,完成触发。作业调度系统在整体项目中属于很底层的基础服务组件,很现实的问题是:很多业务项目其实对底层项目实现根本不关心,只要学习成本低,高可用,有专项小组维护,所以在做项目的设计实现的时候,要着重考虑用户的核心诉求。
作业调度系统在生产环境中每秒需要推送的任务很少,核心诉求并非高并发,而是高可用,所以在设计系统指标定义是秒级任务量的触发延时指标,在功能层面,作为底层系统一定要大而全,且接入成本低,提供一套默认的推荐配置,也要有高级功能满足各个业务线自身的自定义配置。
作者介绍:
作者奇佐(企业代号名),目前负责贝壳找房 java 后端开发方向相关工作。
本文转载自公众号贝壳产品技术(ID:gh_9afeb423f390)。
原文链接:
https://mp.weixin.qq.com/s/3hXyFCgclsuoznNQ2ulC4g
评论 1 条评论