elastic-job主要的设计理念是无中心化的分布式定时调度框架,思路来源于Quartz的基于数据库的高可用方案。但数据库没有分布式协调功能,所以在高可用方案的基础上增加了弹性扩容和数据分片的思路,以便于更大限度的利用分布式服务器的资源。

1. 主要功能

a) 分布式:重写Quartz基于数据库的分布式功能,改用Zookeeper实现注册中心。

b) 并行调度:采用任务分片方式实现。将一个任务拆分为n个独立的任务项,由分布式的服务器并行执行各自分配到的分片项。

c) 弹性扩容缩容:将任务拆分为n个任务项后,各个服务器分别执行各自分配到的任务项。一旦有新的服务器加入集群,或现有服务器下线,elastic-job将在保留本次任务执行不变的情况下,下次任务开始前触发任务重分片。

d) 集中管理:采用基于Zookeeper的注册中心,集中管理和协调分布式作业的状态,分配和监听。外部系统可直接根据Zookeeper的数据管理和监控elastic-job。

e) 定制化流程型任务:作业可分为简单和数据流处理两种模式,数据流又分为高吞吐处理模式和顺序性处理模式,其中高吞吐处理模式可以开启足够多的线程快速的处理数据,而顺序性处理模式将每个分片项分配到一个独立线程,用于保证同一分片的顺序性,这点类似于kafka的分区顺序性。

2. 其他功能

a) 失效转移:弹性扩容缩容在下次作业运行前重分片,但本次作业执行的过程中,下线的服务器所分配的作业将不会重新被分配。失效转移功能可以在本次作业运行中用空闲服务器抓取孤儿作业分片执行。同样失效转移功能也会牺牲部分性能。

b) Spring命名空间支持:elastic-job可以不依赖于spring直接运行,但是也提供了自定义的命名空间方便与spring集成。

c) 运维平台:提供web控制台用于管理作业。

3. 非功能需求

a) 稳定性:在服务器无波动的情况下,并不会重新分片;即使服务器有波动,下次分片的结果也会根据服务器IP和作业名称哈希值算出稳定的分片顺序,尽量不做大的变动。

b) 高性能:同一服务器的批量数据处理采用自动切割并多线程并行处理。

c) 灵活性:所有在功能和性能之间的权衡,都可通过配置开启/关闭。如:elastic-job会将作业运行状态的必要信息更新到注册中心。如果作业执行频度很高,会造成大量Zookeeper写操作,而分布式Zookeeper同步数据可能引起网络风暴。因此为了考虑性能问题,可以牺牲一些功能,而换取性能的提升。

d) 幂等性:elastic-job可牺牲部分性能用以保证同一分片项不会同时在两个服务器上运行。

e) 容错性:作业服务器和Zookeeper断开连接则立即停止作业运行,用于防止分片已经重新分配,而脑裂的服务器仍在继续执行,导致重复执行。

实现方案及开发理念

1. elastic-job的具体模块的底层及如何实现

elastic-job采用去中心化设计,主要分为注册中心,数据分片,分布式协调,定时任务处理和定制化流程型任务等模块。

a) 去中心化

去中心化指elastic-job并无调度中心这一概念,每个运行在集群中的作业服务器都是对等的,节点之间通过注册中心进行分布式协调。但elastic-job有主节点的概念,主节点用于处理一些集中式任务,如分片,清理运行时信息等,并无调度功能,定时调度都是由作业服务器自行触发。

b) 注册中心

注册中心模块目前直接使用zookeeper,用于记录作业的配置,服务器信息以及作业运行状态。Zookeeper虽然很成熟,但原理复杂,使用较难,在海量数据支持的情况下也会有性能和网络问题。目前elastic-job已经抽象出注册中心的接口,下一步将会考虑支持多注册中心,如etcd,或由用户自行实现注册中心。无临时节点和监听机制的注册中心需要自行实现定时心跳监测等功能。

c) 数据分片

数据分片是elastic-job中实现分布式的重要概念,将真实数据和逻辑分片对应,用于解耦作业框架和数据的关系。作业框架只负责将分片合理的分配给相关的作业服务器,而作业服务器需要根据所分配的分片匹配数据进行处理。服务器分片目前都存储在注册中心中,各个服务器根据自己的IP地址拉取分片。

d) 分布式协调

分布式协调模块用于处理作业服务器的动态扩容缩容。一旦集群中有服务器发生变化,分布式协调将自动监测并将变化结果通知仍存活的作业服务器。协调时将会涉及主节点选举,重分片等操作。目前使用的Zookeeper的临时节点和监听器实现主动检查和通知功能。

e) 定时任务处理

定时任务处理根据cron表达式定时触发任务,目前有防止任务同时触发,错过任务重出发等功能。主要还是使用Quartz本身的定时调度功能,为了便于控制,每个任务都使用独立的线程池。

f) 定制化流程型任务

定制化流程型任务将定时任务分为多种流程,有不经任何修饰的简单任务;有用于处理数据的fetchData/processData的数据流任务;以后还将增加消息流任务,文件任务,工作流任务等。用户能以插件的形式扩展并贡献代码。

 

官网地址:

https://github.com/elasticjob

 

源码解析-主要功能

1、原理

首先贴一张官网的架构设计图片

Elastic-Job-Lite Architecture

elastic-job有lite版和cloud版,最大的区别是有无调度中心,一般采用的是lite版本,无中心化。

功能列表有(官网)

  • 分布式调度协调
  • 弹性扩容缩容
  • 失效转移
  • 错过执行作业重触发
  • 作业分片一致性,保证同一分片在分布式环境中仅一个执行实例
  • 支持并行调度
  • 支持作业声明周期操作
  • 丰富的作业类型
  • Spring整合以及命名空间提供
  • 运维平台

个人使用和结合网上整理的:

定时任务: 基于成熟的定时任务作业框架Quartz cron表达式执行定时任务。

作业注册中心: 基于Zookeeper和其客户端Curator实现的全局作业注册控制中心。用于注册,控制和协调分布式作业执行。

作业分片: 将一个任务分片成为多个小任务项在多服务器上同时执行。

弹性扩容缩容: 运行中的作业服务器崩溃,或新增加n台作业服务器,作业框架将在下次作业执行前重新分片,不影响当前作业执行。

支持多种作业执行模式: 支持OneOff,Perpetual和SequencePerpetual三种作业模式。

失效转移: 运行中的作业服务器崩溃不会导致重新分片,只会在下次作业启动时分片。启用失效转移功能可以在本次作业执行过程中,监测其他作业服务器空闲,抓取未完成的孤儿分片项执行。

运行时状态收集: 监控作业运行时状态,统计最近一段时间处理的数据成功和失败数量,记录作业上次运行开始时间,结束时间和下次运行时间。

作业停止,恢复和禁用:用于操作作业启停,并可以禁止某作业运行(上线时常用)。

被错过执行的作业重触发:自动记录错过执行的作业,并在上次作业完成后自动触发。可参考Quartz的misfire。

多线程快速处理数据:使用多线程处理抓取到的数据,提升吞吐量。

幂等性:重复作业任务项判定,不重复执行已运行的作业任务项。由于开启幂等性需要监听作业运行状态,对瞬时反复运行的作业对性能有较大影响。

容错处理:作业服务器与Zookeeper服务器通信失败则立即停止作业运行,防止作业注册中心将失效的分片分项配给其他作业服务器,而当前作业服务器仍在执行任务,导致重复执行。

Spring支持:支持spring容器,自定义命名空间,支持占位符。

运维平台:提供运维界面,可以管理作业和注册中心。

结合使用过程中的理解和网上的结论:

a)第一台服务器上线触发主服务器选举。主服务器一旦下线,则重新触发选举,选举过程中阻塞,只有主服务器选举完成,才会执行其他任务。

 b)某作业服务器上线时会自动将服务器信息注册到注册中心,下线时会自动更新服务器状态。

 c)主节点选举,服务器上下线,分片总数变更均更新重新分片标记。

 d)定时任务触发时,如需重新分片,则通过主服务器分片,分片过程中阻塞,分片结束后才可执行任务。如分片过程中主服务器下线,则先选举主服务器,再分片。

 e)通过上一项说明可知,为了维持作业运行时的稳定性,运行过程中只会标记分片状态,不会重新分片。分片仅可能发生在下次任务触发前。

 f)每次分片都会按服务器IP排序,保证分片结果不会产生较大波动。

 g)实现失效转移功能,在某台服务器执行完毕后主动抓取未分配的分片,并且在某台服务器下线后主动寻找可用的服务器执行任务。

 

源码分析-主要类图

一、大概的类图

二、程序的入口

elastic-job-example\elastic-job-example-lite-java\src\main\java\com\dangdang\ddframe\job\example\JavaMain.java

我们看到这里启动了三种Job,分别是SimpleJob、DataflowJob、ScriptJob,这三种job本质上是不同的,其内部实现采用了3中不同的执行器(稍后说)。

我们先看下setUpSimpleJob

最重要的jobschedule的主要方法如下图:

 

 

服务创建好了,就需要启动它,就是JobScheduler.init 方法了

 

 

job启动和执行过程

 

作业启动流程

作业执行流程

 

 

elastic-job应用实例

一、引入依赖

elastic-job需要一个注册中心

二、配置文件

properties文件

job定义的xml文件:

三、实例代码

simplejob:

dataflowjob:

elastic-job问题答疑

Q1:请问失效转移中如何判断失效?对任务本身实现有什么限制?
失效转移目前通过Zookeeper监听分片项临时节点判断。elastic-job会经过注册中心会话过期时间才能感知任务挂掉。失效转移有两种形式:1、任务挂掉,elastic-job会找空闲的作业服务器(可能是未分配任务的,也可能是完成执行本次任务执行的)执行。2、如果当时没有空闲服务器,则将在某服务器完成分配的任务时抓取未分配的分片项。
 
Q2:Zookeeper的作用是保存任务信息吗,如果Zookeeper挂了会影响任务执行吗?
Zookeeper目前的znode分四类,config,servers,execution,leader。config用于保存分布式作业的全局控制,如,分多少片,要不要执行misfire,cron表达式。servers用于注册作业服务器状态和分片信息。execution以分片的维度存储作业运行时状态。leader用于存储主节点。elastic-job作业执行是无中心化的,但主节点起到协调的作用,如:重分片、清理上次运行时信息等。
 
Q3:在任务处理上可以与spring batch集成吗?
spring batch之前关注过,但目前elastic-job还没有集成。elastic-job的spring支持是自定义了job的命名空间,更简化了基于spring的配置,并且可以使用spring注入的bean。spring batch也是很好的作业框架,包括spring-quartz也很不错,但分布式功能并不成熟。所以在这之上改动难度比较大,而且elastic-job更希望做一个不依赖于spring,而是能融入spring的绿色产品。
 
Q4:针对简单和数据流,能够说说具体分片是怎么处理的吗?
简单的作业就是未经过任何业务逻辑的封装,只是提供了一个execute方法,定时触发,但是增加了分布式分片功能。可以简单理解为quartz的分布式版本。quartz虽然可以支持基于数据库的分布式高可用,但不能分片。也就是说,两台服务器,只能一主一备,不能同时负载均衡的运行。数据流类型作业参照了阿里之前开源的TBSchedule,将数据处理分为fetchData和processData。先将数据从数据库,文件系统,或其他数据源取出来,然后processData集中处理,可以逐条处理,可以批量处理(这块未来将加上)。processData是多线程执行的,数据流类型作业可再细分为两种,一种是高吞吐,一种是顺序性。高吞吐可以开启任意多的线程并行执行数据处理,而顺序执行会根据每个分片项一个线程,保证分片项之中的数据有序,这点参照了kafka的实现。数据流类型作业有isStreaming这个参数,用于控制是否流式不停歇的处理数据,类似永动机,只要有数据,则一直处理。但这种作业不适合每次fetchData都对数据库造成压力很大的场景。
 
Q5:请问如何实现一个任务仅仅只在一个节点执行一次?
目前的幂等性,是在execution的znode中增加了对分片项状态的注册,如果状态是运行中,即使有别的服务器要运行这个分片项,elastic-job也会拒绝运行,而是等待这个状态变为非运行的状态。每个作业分片项启动时会更新状态。服务器没有波动的情况下,是不存在一个分片被分到两个服务器的情况。但一旦服务器波动,在分片的瞬间有可能出现这种情况。关于分片,其实是比较复杂的实现。目前分片是发现服务器波动,或修改分片总数,将记录一个状态,而非直接分片。分片将在下次作业触发时执行,只有主节点可以分片,分片中从节点都将阻塞。无调度中心式分布式作业最大的一个问题是,无法保证主节点作业一定先于其他从节点触发。所以很有可能从节点先触发执行,而使用旧分片;然后主节点才重新分片,将造成这次作业分片可能不一致。这就需要execution节点来保证幂等性。下次执行时,只要无服务器波动,之前错误的分片自然会修正。
 
Q6:如果Zookeeper挂了,是否全部的任务都挂了不能运行包括已经运行过一次的,如果又恢复了,任务能正常运行吗,还是业务应用服务也要重新启动?
其实Zookeeper是不太容易挂的。毕竟Zookeeper是分布式高可用,一般不会是单台。目前elastic-job做到的容错是,连不上Zookeeper的作业服务器将立刻停止执行作业,防止主节点已重新分片,而脑裂的服务器还在执行。也就是说,Zookeeper挂掉,所有作业都将停止。而作业服务器一旦与Zookeeper恢复连接,作业也将恢复运行。所以Zookeeper挂掉不会影响数据,而Zookeeper恢复,作业会继续跑,不用重启。
 
Q7:可以具体到业务层面吗?比如有个任务,是一样发送100w的用户邮件,这时候应该怎么分片?针对分布式数据库的分页在咱们这里又是怎么处理的?
100W用户的邮件,个人认为可以按照用户id取模,比如分成100个分片,将整个userid % 100,然后每个分片发送userid结尾是取摸结果的邮件。详细来说:分片1发送以01结尾的userid的邮件,…,分片99发送以99结尾的userid的邮件。分布式数据库的分页,理论上来说,不是作业框架处理的范畴,应由数据中间层处理。顺便说下,ddframe的数据中间层部分,sharding-JDBC将于明年初开源。通过修改JDBC驱动实现分库分表。非MyCat或cobar这种中间件方式;也非基于hibernate或mybatis这种ORM方式。sharding-JDBC相对轻量级,也更加容易适配各种数据库和ORM
 

elastic-job页面监控

前面说了那么多,需要一个web工程来直观的展示各个job,elastic-job-console应运而生

一、功能简介

1、添加注册中心

这个web工程是支持多注册中心的,可以对注册中心进行添加、删除操作。

二、任务查看

点击详情,可以看到这个服务器上对应的任务列表等信息

三、切换注册中心

多个注册中心,需要切换的时候

 

 

代码如下,console依赖common所以代码有两部分

1、common:elastic-job-common.rar

2、console:elastic-job-lite.rar

posted on 2021-07-12 09:43  流年公子  阅读(920)  评论(0编辑  收藏  举报