Elastic-Job原理分析(version:2.1.4)
当当的Elastic-Job开源出了两种分布式Job的解决方案:
1. elastic-job-lite,这是一个无中心节点的调度;
Elastic-Job-Lite定位为轻量级无中心化解决方案,使用jar包的形式提供分布式任务的协调服务。
2. elastic-job-cloud是一个有中心节点的分布式调度开源工具
Elastic-Job-Cloud使用Mesos + Docker(TBD)的解决方案,额外提供资源治理、应用分发以及进程隔离等服务。
Elastic-Job-Lite和Elastic-Job-Cloud提供同一套API开发作业,它们的核心都是elastic-job-core,开发者仅需一次开发,即可根据需要以Lite或Cloud的方式部署
Elastic-Job-Lite实现分布式job的原理:
要在分布式集群环境下去安全的执行一个调度任务,我们常规的想法就是保证在集群环境下,只有集群中的一台机器能够获取执行任务的权限。
但是Elastic-Job在实现分布式Job时没有按照这个思路去设计,而是将集群中所有的机器都利用起来,去多进程多线程的执行我们的作业任务。
那么它是如何做到分布式作业任务的不重不漏的呢?
Elastic-Job提出了一个数据分片(shardingItem)的概念。
举个例子:
假如我现在部署了3台机器的一个集群,并且按照Elastic-Job的要求设置了分片数量shardingCount=10,则按照Elastic-Job基于平均分配算法的分片策略得到的分片结果为:1=[0,1,2,9], 2=[3,4,5], 3=[6,7,8] (参见:com.dangdang.ddframe.job.lite.api.strategy.impl.AverageAllocationJobShardingStrategy.java)
那么在每一台机器上去执行时,我只要取到本机的数据分片对应的数据来处理,就能够实现数据的分片处理。并且集群中所有机器处理的数据总和即为全量数据。
这样就能把集群中所有的机器都调动起来去同时并行的处理任务,效率比较高。
更有甚者,如果本机的数据分片分到了多个分片(即一个JVM进程分到了多个分片),则Elastic-Job会为每一个分片去启动一个线程来执行分片任务。
这样,一台机器就会开启多个线程就并行的处理作业任务,效率进一步的提高。
(参见:com.dangdang.ddframe.job.executor.AbstractElasticJobExecutor#process(ShardingContexts shardingContexts,
JobExecutionEvent.ExecutionSource executionSource))
特殊场景:分片数为1时
public class MyElasticJob implements SimpleJob { public void execute(ShardingContext shardingContext) { // 1.当分片数为1时,在同一个zookepper和jobname情况下,多台机器部署了Elastic
// job时,只有拿到shardingContext.getShardingItem()为0的机器得以执行,其他的机器不执行 // 2.当分片数大于1时,假如有3台服务器,分成10片,则分片项分配结果为服务器A=0,1,2;服务器B=3,4,5;服务器C=6,7,8,9。此时每台服务器可根据拿到的shardingItem值进行相应的处理,
// 举例场景:
// 假如job处理数据库中的数据业务,方法为:A服务器处理数据库中Id以0,1,2结尾的数据,B处理数据库中Id以3,4,5结尾的数据,C处理器处理6,7,8,9结尾的数据,合计处理0-9为全部数据
// 如果服务器C崩溃,Elastic
// Job自动进行进行失效转移,将C服务器的分片转移到A和B服务器上,则分片项分配结果为服务器A=0,1,2,3,4;服务器B=5,6,7,8,9
// 此时,A服务器处理数据库中Id以0,1,2,3,4结尾的数据,B处理数据库中Id以5,6,7,8,9结尾的数据,合计处理0-9为全部数据. processByEndId(shardingContext.getShardingItem());
} private void processByEndId(int shardingContext) { // TODO: 处理数据Id结尾为 shardingContext的数据 }
}
主要功能
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断开连接则立即停止作业运行,用于防止分片已经重新分配,而脑裂的服务器仍在继续执行,导致重复执行。
以一个官方的例子(com.dangdang.ddframe.job.example.JavaMain.java)来分析Elastic-Job的执行过程:
准备:
由于Elastic-Job是基于Quratz来封装的,我们先看一下Quartz是如何来提交一个作业任务的:
public class MyJob implements Job { @Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
System.out.println(DateTime.now() + "-------执行MyJob");
} public static void main(String[] args) { // 通过schedulerFactory获取一个调度器
SchedulerFactory schedulerfactory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
// 通过schedulerFactory获取一个调度器
scheduler = schedulerfactory.getScheduler(); // 创建jobDetail实例,绑定Job实现类, 指明job的名称,所在组的名称,以及绑定job类
JobDetail job = JobBuilder.newJob(MyJob.class).withIdentity("job1", "jgroup1").build(); // 定义调度触发规则, 使用simpleTrigger规则(cornTrigger) :从第2s开始,每5s执行一次。(http://cron.qqe2.com/ 生成corn)
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("simpleTrigger", "triggerGroup")
.withSchedule(CronScheduleBuilder.cronSchedule("2/5 * * * * ? *")).startNow().build(); // 把作业和触发器注册到任务调度中
scheduler.scheduleJob(job, trigger); // 启动调度
System.out.println(DateTime.now() + "------job start----");
scheduler.start(); } catch (Exception e) {
e.printStackTrace();
} }
}
以此为基础,我们来看Elastic-Job-Lite是如何对它进行封装的:
0. 入口 (我们要执行的任务是JavaSimpleJob.java)
// 将要执行的作业任务放到 SimpleJobConfiguration 中
SimpleJobConfiguration simpleJobConfig = new SimpleJobConfiguration(coreConfig, JavaSimpleJob.class.getCanonicalName());
// 启动
new JobScheduler(regCenter, LiteJobConfiguration.newBuilder(simpleJobConfig).build(), jobEventConfig).init();
作业任务 job 是通过 LiteJobConfiguration 来定义的,里面会定义是哪一种类型的job(JobTypeConfiguration)。
现在支持3种类型的job:SimpleJobConfiguration、DataflowJobConfiguration、ScriptJobConfiguration
1. JobScheduler#init()
1.1 com.dangdang.ddframe.job.lite.api.JobScheduler 实例化时,会构造JobFacade。
jobFacade = new LiteJobFacade(regCenter, liteJobConfig.getJobName(), Arrays.asList(elasticJobListeners), jobEventBus);
JobFacade 是用来操作分片上下文的(后面会用到)
1.2 创建org.quartz.JobDetail
JobDetail result = JobBuilder.newJob(LiteJob.class).withIdentity(liteJobConfig.getJobName()).build();
result.getJobDataMap().put(JOB_FACADE_DATA_MAP_KEY, jobFacade);
.....
scheduler.start(); // 启动Quartz
2. Quartz执行作业任务
到了corn表达式设定的执行时间,Quartz会去执行LiteJob#execute()
2.1 LiteJob#execute(final JobExecutionContext context)
// 根据作业任务的类型,拿到作业任务对应的JobExecutor,然后去执行
JobExecutorFactory.getJobExecutor(elasticJob, jobFacade).execute();
2.2 AbstractElasticJobExecutor#execute()
/**
* 执行作业.
*/
public final void execute() {
try {
jobFacade.checkJobExecutionEnvironment();
} catch (final JobExecutionEnvironmentException cause) {
jobExceptionHandler.handleException(jobName, cause);
}
// 重要:获取当前作业服务器的分片上下文。如果发现没有leader,就选出一个leader,并进行分片
ShardingContexts shardingContexts = jobFacade.getShardingContexts();
if (shardingContexts.isAllowSendJobEvent()) {
jobFacade.postJobStatusTraceEvent(shardingContexts.getTaskId(), State.TASK_STAGING, String.format("Job '%s' execute begin.", jobName));
}
if (jobFacade.misfireIfRunning(shardingContexts.getShardingItemParameters().keySet())) {
if (shardingContexts.isAllowSendJobEvent()) {
jobFacade.postJobStatusTraceEvent(shardingContexts.getTaskId(), State.TASK_FINISHED,
String.format(
"Previous job '%s' - shardingItems '%s' is still running, misfired job will start after previous job completed.",
jobName, shardingContexts.getShardingItemParameters().keySet()));
}
return;
}
try {
jobFacade.beforeJobExecuted(shardingContexts);
// CHECKSTYLE:OFF
} catch (final Throwable cause) {
// CHECKSTYLE:ON
jobExceptionHandler.handleException(jobName, cause);
}
execute(shardingContexts, JobExecutionEvent.ExecutionSource.NORMAL_TRIGGER);
while (jobFacade.isExecuteMisfired(shardingContexts.getShardingItemParameters().keySet())) {
jobFacade.clearMisfire(shardingContexts.getShardingItemParameters().keySet());
execute(shardingContexts, JobExecutionEvent.ExecutionSource.MISFIRE);
}
jobFacade.failoverIfNecessary();
try {
jobFacade.afterJobExecuted(shardingContexts);
// CHECKSTYLE:OFF
} catch (final Throwable cause) {
// CHECKSTYLE:ON
jobExceptionHandler.handleException(jobName, cause);
}
}
附:
分片服务:
com.dangdang.ddframe.job.lite.internal.sharding.ShardingService
ShardingService#getShardingItems(String jobInstanceId) -- 获取本机的shardingItems
默认分片策略:
com.dangdang.ddframe.job.lite.api.strategy.impl.AverageAllocationJobShardingStrategy
主节点选举:
com.dangdang.ddframe.job.lite.internal.election.LeaderService
作业实例的唯一标记:
com.dangdang.ddframe.job.lite.api.strategy.JobInstance
jobInstanceId = IpUtils.getIp() + DELIMITER + ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
节点信息写入:
PersistShardingInfoTransactionExecutionCallback#execute()
作业节点数据:
com.dangdang.ddframe.job.lite.internal.storage.JobNodeStorage
注意 ShardingContexts 与 ShardingContext 的区别:
ShardingContexts是本机的分片上下文(里面保存了本机分配到的shardingItemParameters的集合)
/**
* 分配于本作业实例的分片项和参数的Map.
*/
private final Map<Integer, String> shardingItemParameters;
ShardingContext是当前线程的分片上下文(里面只保存有一个shardingItem)
/**
* 分配于本作业实例的分片项.
*/
private final int shardingItem;
Elastic-Job特性:
1. Elastic-Job-Lite并无作业调度中心节点,而是基于部署作业框架的程序在到达相应时间点时各自触发调度。
注册中心仅用于作业注册和监控信息存储。而主作业节点仅用于处理分片和清理等功能。
2. 为了保证作业的在分布式场景下的一致性,一旦作业与注册中心无法通信,运行中的作业会立刻停止执行,但作业的进程不会退出,这样做的目的是为了防止作业重分片时,将与注册中心失去联系的节点执行的分片分配给另外节点,导致同一分片在两个节点中同时执行。
3. Elastic-Job-Lite采用无中心化设计,若每个客户端的配置不一致,不做控制的话,最后一个启动的客户端配置将会成为注册中心的最终配置。
4. 弹性扩容缩容:将任务拆分为n个任务项后,各个服务器分别执行各自分配到的任务项。一旦有新的服务器加入集群,或现有服务器下线,elastic-job将在保留本次任务执行不变的情况下,下次任务开始前触发任务重分片
5. 定制化流程型任务:作业可分为简单和数据流处理两种模式,数据流又分为高吞吐处理模式和顺序性处理模式,其中高吞吐处理模式可以开启足够多的线程快速的处理数据,而顺序性处理模式将每个分片项分配到一个独立线程,用于保证同一分片的顺序性,这点类似于kafka的分区顺序性。
6. 失败转移:http://www.cnblogs.com/nevermorewang/p/5744324.html
LTS、xxl-job、Elastic-Job对比
Elastic-Job官方文档及相关文章:
http://dangdangdotcom.github.io/elastic-job/elastic-job-lite/00-overview/
https://my.oschina.net/u/719192/blog/506062?nocache=1497421466555
http://www.infoq.com/cn/articles/dangdang-distributed-work-framework-elastic-job/
开源方案对比:
http://www.songwie.com/articlelist/192
Elastic-Job原理分析(version:2.1.4)的更多相关文章
- Android中Input型输入设备驱动原理分析(一)
转自:http://blog.csdn.net/eilianlau/article/details/6969361 话说Android中Event输入设备驱动原理分析还不如说Linux输入子系统呢,反 ...
- 使用AsyncTask异步更新UI界面及原理分析
概述: AsyncTask是在Android SDK 1.5之后推出的一个方便编写后台线程与UI线程交互的辅助类.AsyncTask的内部实现是一个线程池,所有提交的异步任务都会在这个线程池中的工作线 ...
- 消息队列NetMQ 原理分析5-StreamEngine、Encord和Decord
消息队列NetMQ 原理分析5-StreamEngine,Encord和Decord 前言 介绍 目的 StreamEngine 发送数据 接收数据 流程分析 Encoder V2Encoder V1 ...
- Groovy实现原理分析——准备工作
欢迎和大家交流技术相关问题: 邮箱: jiangxinnju@163.com 博客园地址: http://www.cnblogs.com/jiangxinnju GitHub地址: https://g ...
- MyBatis:二级缓存原理分析
MyBatis从入门到放弃七:二级缓存原理分析 前言 说起mybatis的一级缓存和二级缓存我特意问了几个身边的朋友他们平时会不会用,结果没有一个人平时业务场景中用. 好吧,那我暂且用来学习源码吧.一 ...
- dubbo源码解析五 --- 集群容错架构设计与原理分析
欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 博客园 Dubbo 入门之二 --- 项目结构解析 博客园 Dubbo 源码分析系列之 ...
- SpringBoot入门(0) HelloWorld的实现与原理分析
SpringBoot(0) HelloWorld的实现与原理分析 一.环境准备 1.1 环境约束 –jdk1.8:Spring Boot 推荐jdk1.7及以上:java version “1.8.0 ...
- Heartbleed心脏出血漏洞原理分析
Heartbleed心脏出血漏洞原理分析 2017年01月14日 18:14:25 阅读数:2718 1. 概述 OpenSSL在实现TLS和DTLS的心跳处理逻辑时,存在编码缺陷.OpenSS ...
- Android插件化与热修复(六)-微信Tinker原理分析
Tinker热修复原理分析 热补丁技术是在用户不需要重新安装应用的情况下实现应用更新,可快速解决一些线上问题.热补丁省去了Android应用发布版本的成本,而且用户端的更新也是无感知的. Tinker ...
- MyBatis的深入原理分析之1-架构设计以及实例分析
MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简单.优雅.本文主要讲述MyBatis的架构设计思路,并且讨论MyBatis的几个核心部件,然后结合一个select查询实例, ...
随机推荐
- 【进阶修炼】——改善C#程序质量(7)
113,声明变量时考虑最大值. Ushort的最大值是65535,用于不同的用途这个变量可能发生溢出,所以设计时应充分了解每个变量的最大值. 114,MD5不再安全. MD5多用于信息完整性的校验.R ...
- mysql查找有某列但没有此列索引的表
select a.TABLE_SCHEMA,a.TABLE_NAME from information_schema.`COLUMNS` a left join (select 'etl_stamp' ...
- 你应该知道的CSS2.0中最常用的18条技巧
一.使用css缩写 使用缩写可以帮助减少你CSS文件的大小,更加容易阅读. 具体内容请浏览:CSS常用缩写语法 二.明确定义单位,除非值为0. 忘记定义尺寸的单位是CSS新手普遍的错误.在HTML中 ...
- 关于Unity中的刚体和碰撞器的相关用法(二)
在关于Unity中的刚体和碰撞器的相关用法(一)的基础上 有一个plane平面,一个ball球体,都挂了碰撞器,ball挂了刚体Rigidbody,写了一个脚本ball挂载在球体上,球体从空中落下装机 ...
- Ajax+Jsp+servlet+json技术的使用
Ajax+Jsp+servlet+json技术的使用 在使用json的时候,记得必须导入如下几个.jar包,最好是手动复制.jar包只lib路径下,否则可能出现异常. commons-beanutil ...
- OpenGL中的二维编程——从简单的矩形开始
一.OpenGL的组成 图元函数(primitive function)指定要生成屏幕图像的图元.包括两种类型:可以在二维.三维或者四维空间进行定义的几何图元,如多边形:离散实体:位图. 属性函数(a ...
- 第三百二十二节,web爬虫,requests请求
第三百二十二节,web爬虫,requests请求 requests请求,就是用yhthon的requests模块模拟浏览器请求,返回html源码 模拟浏览器请求有两种,一种是不需要用户登录或者验证的请 ...
- e810. 创建弹出菜单
final JPopupMenu menu = new JPopupMenu(); // Create and add a menu item JMenuItem item = new JMenuIt ...
- (原创)OpenStack服务如何使用Keystone(一)---Keystone端的操作
(一)Keystone端的操作 (二)如何在OpenStack服务上部署Keystone中间件 (三)详细配置keystonemiddleware OpenStack项目如果要使用Keystone作为 ...
- wapp HTTP Error 404. The requested resource is not found.
原因: 本地80端口被占用,需要修改WAMP的默认端口 修改设置: 找到 bin/apache/apache***/conf/httpd.conf文件 将文件中的80修改为8088 修改这两个地方端口 ...