elastic-job-lite 是一个分布式任务调度系统,尝试从以下几个方面分析它的架构设计:
- 作业执行流程模板化,用户只需要关注任务的实现
- 任务调度(定时,一次性)
- 多样化的作业类型
- 面向开发者的扩展点
- 作业间依赖关系
- 作业监听器
- DAG(暂无)
- 分布式
- 注册中心,配置中心
- 高可用,弹性伸缩
- 作业治理
- 故障转移
- 错过再执行
- 自诊断修复
- 对主流框架的适配
- 面向用户的API是否易用
其他:作业生命周期与运维平台
Elastic-Job-lite 功能列表
功能 | 子功能 |
---|---|
弹性调度 | 支持任务在分布式场景下的分片和高可用 |
能够水平扩展任务的吞吐量和执行效率 | |
任务处理能力随资源配备弹性伸缩 | |
作业治理 | 失效转移 |
错过作业重新执行 | |
自诊断修复 | |
作业开放生态 | 可扩展的作业类型统一接口 |
丰富的作业类型库,如数据流、脚本、HTTP、文件、大数据等 | |
易于对接业务作业,能够与 Spring 依赖注入无缝整合 | |
可视化管控端 | 作业管控端 |
作业执行历史数据追踪 | |
注册中心管理 |
实现原理,详见 官网概念 & 功能
本文重点介绍弹性调度和作业调度相关的功能
功能源码分析 – elasticjob-lite-core
https://github.com/apache/shardingsphere-elasticjob
概述 elasticjob-lite-core 的包结构
api 暴露给用户调用的类
- bootstrap 作业启动类
- - OneOffJobBootstrap 一次性调度
- - ScheduleJobBootstrap 定时调度
- listener 作业监听器
- - AbstractDistributeOnceElasticJobListener 分布式作业监听器
- register // TODO 没看懂
- - JobInstanceRegistry
internal 内部核心功能,按功能分包
- config 配置
- election 选举
- instance 实例 | JobInstance
- server 服务器 | ip
- sharding 分片
- failover 故障转移
- reconcile 自诊断修复(重新分片)
- setup 启动设置
- schedule 调度器 | 作业调度器,作业类型,作业生命周期
- storage 存储 | 默认zk
- snapshot dump 任务快照
- trigger 手动触发
- listener 配置变更监听器
- guarantee 分布式作业监听器幂等保障
架构分层
--------- API ---------
JobBootstrap 启动类API
--------- 公共入口 ---------
JobScheduler 启动类实现,解耦了API和底层实现
--------- controller ---------
XXXController(任务调度控制器)
XXXFacade(整合底层特性,对外提供统一功能)
XXXManager(功能管理器,通过监听配置变更实现)
--------- server ---------
XXXServer 业务逻辑层
--------- 存储 ---------
JobNodeStorage 存储层
xxxNode 充血模型
数据结构
path | value | type | 功能 |
---|---|---|---|
/{jobName} | {originalJobClassName} | ||
/{jobName}/systemTime/current | zk的当前时间戳 | ||
/{jobName}/config | YAML(JobConfig) | 持久化 | 变更监听 | |
/{jobName}/servers/{ip} | enable | disable | ||
/{jobName}/instances/{jobInstanceId} | {JobInstance} | 临时 | 宕机 | zk断连 |
/{jobName}/leader/election/instance | {主节点 ip} | 临时 | 主节点 |
/{jobName}/leader/election/instance/latch | 选主 | 分片 | ||
{jobName}/leader/sharding/necessary | 再分片标记 | ||
{jobName}/leader/sharding/processing | 临时 | 再分片进行中 | |
{jobName}/sharding/{item}/running | 临时 | 幂等 | |
{jobName}/sharding/{item}/instance | {jobInstanceId} | 分片对应实例 | |
{jobName}/sharding/{item}/disabled | |||
{jobName}/sharding/{item}/misfire | 错过再执行标记 | ||
/{jobName}/sharding/{item}/failover | 临时 | 故障转移 | |
/{jobName}/trigger/{jobInstanceId} | 触发 |
API – 暴露给用户调用的类
api 类源码都比较简单,主要关注于他们的使用,详见官网使用手册 及 demo
internal – 功能特性
任务调度 schedule
启动设置 setup
基础功能 config election server instance sharding
作业治理 failover reconcile
存储统一放在 storage
其他 snapshot listener trigger
note:
{jobName} 对应配置里 job 的 id
{item} 对应分片号
config 配置
ConfigurationService
load(boolean fromCache)
核心方法,加载配置。缓存机制依赖于 CuratorCache
1. 若 fromCache == true 走缓存
2. 缓存不存在,从zk加载,节点/{jobName}/config
3. 通过yaml解析成 JobConfigurationPOJO
setUpJobConfiguration(String jobClassName, JobConfiguration jobConfig)
设置任务配置
1. 检查作业是否冲突。job节点 key:/{jobName} data:{originalJobClassName}
2. 设置任务配置。配置节点 key: /{jobName}/config value:YAML(jobConfig)
checkMaxTimeDiffSecondsTolerable()
检查注册中心的时间与实例的时间误差是否在容忍范围内
涉及配置属性 MaxTimeDiffSeconds 和节点 /{jobName}/config
MaxTimeDiffSeconds:注册中心的时间与实例的时间误差
1. 缓存加载 MaxTimeDiffSeconds,MaxTimeDiffSeconds < 0 表示不检查
2. 检查本机与注册中心的时间误差秒数是否在允许范围
1. 注册中心当前时间 /{jobName}/systemTime/current
2. 本机当前时间 System.getCurrentMillis()
RescheduleListenerManager
监听到配置变更重新安排作业
1. 监听zk节点 /{
jobName}/config 变化
2. 底层调用 quartz 的 Scheduler.rescheduleJob 方法刷新任务
election 选主
ElectionListenerManager
主节点上下线,选举
LeaderElectionJobListener 选举监听器
@Override
protected void dataChanged(final String path, final Type eventType, final String data) {
if (!JobRegistry.getInstance().isShutdown(jobName) && (isActiveElection(path, data) || isPassiveElection(path, eventType))) {
leaderService.electLeader();
}
}
节点参与选举需要满足以下两个条件之一:
1. 实例运行中&无主节点(/{
jobName}/leader/election/instance 不存在)&本机处于开启状态(/{
jobName}/servers/{
ip}.data == enable)
2. 主节点宕机(临时节点 /{
jobName}/leader/election/instance 已删除)&当前节点可用(/{
jobName}/instances/{
instance})
LeaderService
electLeader 选举
public void electLeader() {
// Curator.LeaderLatch选主,节点 /{jobName}/leader/election/instance/latch
jobNodeStorage.executeInLeader(LeaderNode.LATCH, ne