ElasticJob详解

来源:互联网 发布:张大奕淘宝店叫啥 编辑:程序博客网 时间:2024/06/06 03:48

console

console没有和elastic-job-lite发生直接的联系,而是借助ZK和 Elastic-job-lite联系了在一起 

但是大家注意到Elastic-Job-Lite和zk怎么联系在一起的么?是的Listener 就是它。当Console增加或修改了zk上的节点时(这里指的是jobName节点下的节点和数据变动,感谢严欢欢同学指出),就会触发Listener,进而触发Job的相关的操作,如触发、暂停、恢复等等,有了这些。明白了原理,我们来看看代码, 
目前提供3种作业类型,分别是SimpleDataFlowScript

方法参数shardingContext包含作业配置,分片和运行时信息。可通过getShardingTotalCount()getShardingItems()等方法分别获取分片总数,运行在本作业服务器的分片序列号集合等。

开发指南

代码开发

作业类型

目前提供3种作业类型,分别是SimpleDataFlowScript

DataFlow类型用于处理数据流,它又提供2种作业类型,分别是ThroughputDataFlowSequenceDataFlow。需要继承相应的抽象类。

Script类型用于处理脚本,可直接使用,无需编码。

方法参数shardingContext包含作业配置,分片和运行时信息。可通过getShardingTotalCount()getShardingItems()等方法分别获取分片总数,运行在本作业服务器的分片序列号集合等。

Simple类型作业

Simple类型作业意为简单实现,未经任何封装的类型。需要继承AbstractSimpleElasticJob,该类只提供了一个方法用于覆盖,此方法将被定时执行。用于执行普通的定时任务,与Quartz原生接口相似,只是增加了弹性扩缩容和分片等功能。

[java] view plain copy
  1. public class MyElasticJob extends AbstractSimpleElasticJob {  
  2.   
  3.     @Override  
  4.     public void process(JobExecutionMultipleShardingContext context) {  
  5.         // do something by sharding items  
  6.     }  
  7. }  

ThroughputDataFlow类型作业

ThroughputDataFlow类型作业意为高吞吐的数据流作业。需要继承AbstractIndividualThroughputDataFlowElasticJob并可以指定返回值泛型,该类提供3个方法可覆盖,分别用于抓取数据,处理数据和指定是否流式处理数据。可以获取数据处理成功失败次数等辅助监控信息。如果流式处理数据,fetchData方法的返回值只有为null或长度为空时,作业才会停止执行,否则作业会一直运行下去;非流式处理数据则只会在每次作业执行过程中执行一次fetchData方法和processData方法,即完成本次作业。流式数据处理参照TbSchedule设计,适用于不间歇的数据处理。

作业执行时会将fetchData的数据传递给processData处理,其中processData得到的数据是通过多线程(线程池大小可配)拆分的。如果采用流式作业处理方式,建议processData处理数据后更新其状态,避免fetchData再次抓取到,从而使得作业永远不会停止。processData的返回值用于表示数据是否处理成功,抛出异常或者返回false将会在统计信息中归入失败次数,返回true则归入成功次数。

[java] view plain copy
  1. public class MyElasticJob extends AbstractIndividualThroughputDataFlowElasticJob<Foo> {  
  2.   
  3.     @Override  
  4.     public List<Foo> fetchData(JobExecutionMultipleShardingContext context) {  
  5.         Map<Integer, String> offset = context.getOffsets();  
  6.         List<Foo> result = // get data from database by sharding items and by offset  
  7.         return result;  
  8.     }  
  9.   
  10.     @Override  
  11.     public boolean processData(JobExecutionMultipleShardingContext context, Foo data) {  
  12.         // process data  
  13.         // ...  
  14.   
  15.         // store offset  
  16.         for (int each : context.getShardingItems()) {  
  17.             updateOffset(each, "your offset, maybe id");  
  18.         }  
  19.         return true;  
  20.     }  
  21. }  

SequenceDataFlow类型作业

SequenceDataFlow类型作业和ThroughputDataFlow作业类型极为相似,所不同的是ThroughputDataFlow作业类型可以将获取到的数据多线程处理,但不会保证多线程处理数据的顺序。如:从2个分片共获取到100条数据,第1个分片40条,第2个分片60条,配置为两个线程处理,则第1个线程处理前50条数据,第2个线程处理后50条数据,无视分片项;SequenceDataFlow类型作业则根据当前服务器所分配的分片项数量进行多线程处理,每个分片项使用同一线程处理,防止了同一分片的数据被多线程处理,从而导致的顺序问题。如:从2个分片共获取到100条数据,第1个分片40条,第2个分片60条,则系统自动分配两个线程处理,第1个线程处理第1个分片的40条数据,第2个线程处理第2个分片的60条数据。由于ThroughputDataFlow作业可以使用多于分片项的任意线程数处理,所以性能调优的可能会优于SequenceDataFlow作业。

[java] view plain copy
  1. public class MyElasticJob extends AbstractIndividualSequenceDataFlowElasticJob<Foo> {  
  2.   
  3.     @Override  
  4.     public List<Foo> fetchData(JobExecutionSingleShardingContext context) {  
  5.         int offset = context.getOffset();  
  6.         List<Foo> result = // get data from database by sharding items and by offset  
  7.         return result;  
  8.     }  
  9.   
  10.     @Override  
  11.     public boolean processData(JobExecutionSingleShardingContext context, Foo data) {  
  12.         // process data  
  13.         // ...  
  14.   
  15.         // store offset  
  16.         updateOffset(context.getShardingItem(), "your offset, maybe id");  
  17.         return true;  
  18.     }  
  19. }  

Script类型作业

Script类型作业意为脚本类型作业,支持shellPythonperl等所有类型脚本。只需通过控制台/代码配置scriptCommandLine即可。执行脚本路径可以包含参数,最后一个参数为作业运行时信息.

[java] view plain copy
  1. #!/bin/bash  
  2. echo sharding execution context is $*  

作业运行时输出

sharding execution context is {"shardingItems":[0,1,2,3,4,5,6,7,8,9],"shardingItemParameters":{},"offsets":{},"jobName":"scriptElasticDemoJob","shardingTotalCount":10,"jobParameter":"","monitorExecution":true,"fetchDataCount":1}

批量处理

为了提高数据处理效率,数据流类型作业提供了批量处理数据的功能。之前逐条处理数据的两个抽象类分别是AbstractIndividualThroughputDataFlowElasticJobAbstractIndividualSequenceDataFlowElasticJob,批量处理则使用另外两个接口AbstractBatchThroughputDataFlowElasticJobAbstractBatchSequenceDataFlowElasticJob。不同之处在于processData方法的返回值从boolean类型变为int类型,用于表示一批数据处理的成功数量,第二个入参则转变为List数据集合。

异常处理

elastic-job在最上层接口提供了handleJobExecutionException方法,使用作业时可以覆盖此方法,并使用quartz提供的JobExecutionException控制异常后作业的声明周期。默认实现是直接将异常抛出。示例:

任务监听配置

可以通过配置多个任务监听器,在任务执行前和执行后执行监听的方法。监听器分为每台作业节点均执行和分布式场景中仅单一节点执行两种。

每台作业节点均执行的监听

若作业处理作业服务器的文件,处理完成后删除文件,可考虑使用每个节点均执行清理任务。此类型任务实现简单,且无需考虑全局分布式任务是否完成,请尽量使用此类型监听器。

步骤:

  • 定义监听器
[java] view plain copy
  1. import com.dangdang.ddframe.job.api.JobExecutionMultipleShardingContext;  
  2. import com.dangdang.ddframe.job.api.listener.ElasticJobListener;  
  3.   
  4. public class MyElasticJobListener implements ElasticJobListener {  
  5.   
  6.     @Override  
  7.     public void beforeJobExecuted(final JobExecutionMultipleShardingContext shardingContext) {  
  8.         // do something ...  
  9.     }  
  10.   
  11.     @Override  
  12.     public void afterJobExecuted(final JobExecutionMultipleShardingContext shardingContext) {  
  13.         // do something ...  
  14.     }  
  15. }  
  • 将监听器作为参数传入JobScheduler

[java] view plain copy
  1. public class JobMain {  
  2.   
  3.     public static void main(final String[] args) {  
  4.         new JobScheduler(regCenter, jobConfig, new MyElasticJobListener()).init();  
  5.     }  
  6. }  

分布式场景中仅单一节点执行的监听

若作业处理数据库数据,处理完成后只需一个节点完成数据清理任务即可。此类型任务处理复杂,需同步分布式环境下作业的状态同步,提供了超时设置来避免作业不同步导致的死锁,请谨慎使用。

步骤:

  • 定义监听器
[java] view plain copy
  1. import com.dangdang.ddframe.job.api.JobExecutionMultipleShardingContext;  
  2. import com.dangdang.ddframe.job.api.listener.AbstractDistributeOnceElasticJobListener;  
  3.   
  4. public final class TestDistributeOnceElasticJobListener extends AbstractDistributeOnceElasticJobListener {  
  5.   
  6.     public TestDistributeOnceElasticJobListener(final long startTimeoutMills, final long completeTimeoutMills) {  
  7.         super(startTimeoutMills, completeTimeoutMills);  
  8.     }  
  9.   
  10.     @Override  
  11.     public void doBeforeJobExecutedAtLastStarted(final JobExecutionMultipleShardingContext shardingContext) {  
  12.         // do something ...  
  13.     }  
  14.   
  15.     @Override  
  16.     public void doAfterJobExecutedAtLastCompleted(final JobExecutionMultipleShardingContext shardingContext) {  
  17.         // do something ...  
  18.     }  
  19. }  
  • 将监听器作为参数传入JobScheduler
[java] view plain copy
  1. public class JobMain {  
  2.   
  3.     public static void main(final String[] args) {  
  4.         long startTimeoutMills = 5000L;  
  5.         long completeTimeoutMills = 10000L;  
  6.         new JobScheduler(regCenter, jobConfig, new MyDistributeOnceElasticJobListener(startTimeoutMills, completeTimeoutMills)).init();  
  7.     }  
  8. }  

作业配置

Spring容器配合使用作业,可以将作业Bean配置为Spring Bean,可在作业中通过依赖注入使用Spring容器管理的数据源等对象。可用placeholder占位符从属性文件中取值。

Spring命名空间配置

[html] view plain copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.     xmlns:reg="http://www.dangdang.com/schema/ddframe/reg"  
  5.     xmlns:job="http://www.dangdang.com/schema/ddframe/job"  
  6.     xsi:schemaLocation="http://www.springframework.org/schema/beans  
  7.                         http://www.springframework.org/schema/beans/spring-beans.xsd  
  8.                         http://www.dangdang.com/schema/ddframe/reg  
  9.                         http://www.dangdang.com/schema/ddframe/reg/reg.xsd  
  10.                         http://www.dangdang.com/schema/ddframe/job  
  11.                         http://www.dangdang.com/schema/ddframe/job/job.xsd  
  12.                         ">  
  13.     <!--配置作业注册中心 -->  
  14.     <reg:zookeeper id="regCenter" server-lists=" yourhost:2181" namespace="dd-job" base-sleep-time-milliseconds="1000" max-sleep-time-milliseconds="3000" max-retries="3" />  
  15.   
  16.     <!-- 配置简单作业-->  
  17.     <job:simple id="simpleElasticJob" class="xxx.MySimpleElasticJob" registry-center-ref="regCenter" cron="0/10 * * * * ?"   sharding-total-count="3" sharding-item-parameters="0=A,1=B,2=C" />  
  18.   
  19.     <!-- 配置数据流作业-->  
  20.     <job:dataflow id="throughputDataFlow" class="xxx.MyThroughputDataFlowElasticJob" registry-center-ref="regCenter" cron="0/10 * * * * ?" sharding-total-count="3" sharding-item-parameters="0=A,1=B,2=C" process-count-interval-seconds="10" concurrent-data-process-thread-count="10" />  
  21.   
  22.     <!-- 配置脚本作业-->  
  23.     <job:script id="scriptElasticJob" registry-center-ref="regCenter" cron="0/10 * * * * ?" sharding-total-count="3" sharding-item-parameters="0=A,1=B,2=C" script-command-line="/your/file/path/demo.sh" />  
  24.   
  25.     <!-- 配置带监听的简单作业-->  
  26.     <job:simple id="listenerElasticJob" class="xxx.MySimpleListenerElasticJob" registry-center-ref="regCenter" cron="0/10 * * * * ?"   sharding-total-count="3" sharding-item-parameters="0=A,1=B,2=C">  
  27.         <job:listener class="xx.MySimpleJobListener"/>  
  28.         <job:listener class="xx.MyOnceSimpleJobListener" started-timeout-milliseconds="1000" completed-timeout-milliseconds="2000" />  
  29.     </job:simple>  
  30. </beans>  


job:simple命名空间属性详细说明

属性名类型是否必填缺省值描述idString 作业名称classString否 作业实现类,需实现ElasticJob接口,脚本型作业不需要配置registry-center-refString 注册中心Bean的引用,需引用reg:zookeeper的声明cronString cron表达式,用于配置作业触发时间sharding-total-countint 作业分片总数sharding-item-parametersString否 分片序列号和参数用等号分隔,多个键值对用逗号分隔
分片序列号从0开始,不可大于或等于作业分片总数
如:
0=a,1=b,2=cjob-parameterString否 作业自定义参数
可以配置多个相同的作业,但是用不同的参数作为不同的调度实例monitor-executionboolean否true监控作业运行时状态
每次作业执行时间和间隔时间均非常短的情况,建议不监控作业运行时状态以提升效率。因为是瞬时状态,所以无必要监控。请用户自行增加数据堆积监控。并且不能保证数据重复选取,应在作业中实现幂等性。
每次作业执行时间和间隔时间均较长的情况,建议监控作业运行时状态,可保证数据不会重复选取。monitor-portint否-1作业监控端口
建议配置作业监控端口, 方便开发者dump作业信息。
使用方法: echo “dump” | nc 127.0.0.1 9888max-time-diff-secondsint否-1最大允许的本机与注册中心的时间误差秒数
如果时间误差超过配置秒数则作业启动时将抛异常
配置为-1表示不校验时间误差failoverboolean否false是否开启失效转移
monitorExecution开启,失效转移才有效misfireboolean否true是否开启错过任务重新执行job-sharding-strategy-classString否true作业分片策略实现类全路径
默认使用平均分配策略
详情参见:作业分片策略descriptionString否 作业描述信息disabledboolean否false作业是否禁止启动
可用于部署作业时,先禁止启动,部署结束后统一启动overwriteboolean否false本地配置是否可覆盖注册中心配置
如果可覆盖,每次启动作业都以本地配置为准

job:dataflow命名空间属性详细说明

job:dataflow命名空间拥有job:simple命名空间的全部属性,以下仅列出特有属性

属性名类型是否必填缺省值描述process-count-interval-secondsint否300统计作业处理数据数量的间隔时间
单位:秒concurrent-data-process-thread-countint否CPU核数*2同时处理数据的并发线程数
不能小于1
ThroughputDataFlow作业有效fetch-data-countint否1每次抓取的数据量streaming-processboolean否false是否流式处理数据
如果流式处理数据, 则fetchData不返回空结果将持续执行作业
如果非流式处理数据, 则处理数据完成后作业结束

job:script命名空间属性详细说明,基本属性参照job:simple命名空间属性详细说明

job:script命名空间拥有job:simple命名空间的全部属性,以下仅列出特有属性

属性名类型是否必填缺省值描述script-command-lineString否 脚本型作业执行命令行

job:listener命名空间属性详细说明

job:listener必须配置为job:bean的子元素

属性名类型是否必填缺省值描述classString 前置后置任务监听实现类,需实现ElasticJobListener接口started-timeout-millisecondslongLong.MAX_VALUEAbstractDistributeOnceElasticJobListener型监听器,最后一个作业执行前的执行方法的超时时间
单位:毫秒completed-timeout-millisecondslongLong.MAX_VALUEAbstractDistributeOnceElasticJobListener型监听器,最后一个作业执行后的执行方法的超时时间
单位:毫秒

reg:bean命名空间属性详细说明

属性名类型是否必填缺省值描述idString 注册中心在Spring容器中的主键server-listsString 连接Zookeeper服务器的列表
包括IP地址和端口号
多个地址用逗号分隔
如: host1:2181,host2:2181namespaceString Zookeeper的命名空间base-sleep-time-millisecondsint否1000等待重试的间隔时间的初始值
单位:毫秒max-sleep-time-millisecondsint否3000等待重试的间隔时间的最大值
单位:毫秒max-retriesint否3最大重试次数session-timeout-millisecondsint否60000会话超时时间
单位:毫秒connection-timeout-millisecondsint否15000连接超时时间
单位:毫秒digestString否无验证连接Zookeeper的权限令牌
缺省为不需要权限验证

不使用Spring配置

如果不使用Spring框架,可以用如下方式启动作业。

[java] view plain copy
  1. import com.dangdang.ddframe.job.api.config.JobConfiguration;  
  2. import com.dangdang.ddframe.job.api.JobScheduler;  
  3. import com.dangdang.ddframe.reg.base.CoordinatorRegistryCenter;  
  4. import com.dangdang.ddframe.reg.zookeeper.ZookeeperConfiguration;  
  5. import com.dangdang.ddframe.reg.zookeeper.ZookeeperRegistryCenter;  
  6. import com.dangdang.example.elasticjob.core.job.SimpleJobDemo;  
  7. import com.dangdang.example.elasticjob.core.job.ThroughputDataFlowJobDemo;  
  8. import com.dangdang.example.elasticjob.core.job.SequenceDataFlowJobDemo;  
  9. import com.dangdang.ddframe.job.plugin.job.type.integrated.ScriptElasticJob;  
  10.   
  11. public class JobDemo {  
  12.   
  13.     // 定义Zookeeper注册中心配置对象  
  14.     private ZookeeperConfiguration zkConfig = new ZookeeperConfiguration("localhost:2181""elastic-job-example"100030003);  
  15.   
  16.     // 定义Zookeeper注册中心  
  17.     private CoordinatorRegistryCenter regCenter = new ZookeeperRegistryCenter(zkConfig);  
  18.   
  19.     // 定义简单作业配置对象  
  20.     private final SimpleJobConfiguration simpleJobConfig = JobConfigurationFactory.createSimpleJobConfigurationBuilder("simpleElasticDemoJob",  
  21.                     SimpleJobDemo.class10"0/30 * * * * ?").build();  
  22.   
  23.     // 定义高吞吐流式处理的数据流作业配置对象  
  24.     private final DataFlowJobConfiguration throughputJobConfig = JobConfigurationFactory.createDataFlowJobConfigurationBuilder("throughputDataFlowElasticDemoJob",  
  25.                     ThroughputDataFlowJobDemo.class10"0/5 * * * * ?").streamingProcess(true).build();  
  26.   
  27.     // 定义顺序的数据流作业配置对象  
  28.     private final DataFlowJobConfiguration sequenceJobConfig = JobConfigurationFactory.createDataFlowJobConfigurationBuilder("sequenceDataFlowElasticDemoJob",  
  29.                     SequenceDataFlowJobDemo.class10"0/5 * * * * ?").build();  
  30.   
  31.     // 定义脚本作业配置对象  
  32.     private final ScriptJobConfiguration scriptJobConfig = JobConfigurationFactory.createScriptJobConfigurationBuilder("scriptElasticDemoJob",  
  33.                     10"0/5 * * * * ?""test.sh").build();  
  34.   
  35.     public static void main(final String[] args) {  
  36.         new JobDemo().init();  
  37.     }  
  38.   
  39.     private void init() {  
  40.         // 连接注册中心  
  41.         regCenter.init();  
  42.         // 启动简单作业  
  43.         new JobScheduler(regCenter, simpleJobConfig).init();  
  44.         // 启动高吞吐流式处理的数据流作业  
  45.         new JobScheduler(regCenter, throughputJobConfig).init();  
  46.         // 启动顺序的数据流作业  
  47.         new JobScheduler(regCenter, sequenceJobConfig).init();  
  48.         // 启动脚本作业  
  49.         new JobScheduler(regCenter, scriptJobConfig).init();  
  50.     }  
  51. }  

0 0
原创粉丝点击