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查询实例, ...
随机推荐
- android开发(39) 使用android系统的账户中心管理账
在android的系统设置页,有个“账户”分组,里面有很多的账户,很多app都使用了这个账户系统,比如“谷歌”,“淘宝”,“微信”,“华为”等.这些都是大公司呢,有没有可能我们也使用这个,让我们的软件 ...
- mysql innodb_buffer_pool_size mysql占用内存大小和主从复制并行线程数量
innodb_buffer_pool_size set global slave_parallel_workers=4;
- R语言学习笔记 (入门知识)
R免费使用:统计工具:# 注释,行注释块注释:anything="这是注释的内容"常用R语言编辑器:Rsutdio,Tinn-R,Eclipse+StatET:中文会有乱码帮助:? ...
- Java 内部线程
InsttoolCacheScheduler_ Worker-2 Quartz InsttoolCacheScheduler_Worker-2线程就是ThreadPool线程的一个简单实现,它主要负责 ...
- snapshots On Vmware
快照使用 编辑 如果你创建了多于一个的虚拟机快照,那么,你将有多个还原点可以用于恢复.当你创建了一个快照,那快照些现在可写的在那个点上就变成了只读的.使用in-file delta技术就能创建新文件记 ...
- nano 命令 linux
用途说明 nano是一个字符终端的文本编辑器,有点像DOS下的editor程序.它比vi/vim要简单得多,比较适合Linux初学者使用.某些Linux发行版的默认编辑器就是nano.(nano - ...
- VMware快照的工作原理
VMware中的快照是对VMDK在某个时间点的“拷贝”,这个“拷贝”并不是对VMDK文件的复制,而是保持磁盘文件和系统内存在该时间点的状态,以便在出现故障后虚拟机能够恢复到该时间点.如果对某个虚拟机创 ...
- 《FPGA全程进阶---实战演练》第四章之实验平台软硬件使用简介
本章主要是讲解读者在进行FPGA逻辑设计之前的准备工作,需要下载Quartus II软件和 Modelsim 软件,一个是用来进行FPGA逻辑设计,一个是用来对逻辑进行理论分析与验证. 1.1 qua ...
- 关于Unity中RectTransform和Transform
以前一直以为在Inspector面板上的是Transform,后来才发现原来2D是RectTransform,3D是Transform 3D面板上显示的是位置坐标组件Transform,2D面板上显示 ...
- android读取大图片并缓存
最近开发电视版的云存储应用,要求”我的相册“模块有全屏预览图片的功能,全屏分辨率是1920*1080超清.UI组件方面采用Gallery+ImageSwitcher组合,这里略过,详情参见google ...