開發指南
代碼開發
作業類型
目前提供3
種作業類型,分別是Simple
, DataFlow
和Script
。
DataFlow
類型用於處理數據流,它又提供2
種作業類型,分別是ThroughputDataFlow
和SequenceDataFlow
。需要繼承相應的抽象類。
Script
類型用於處理腳本,可直接使用,無需編碼。
方法參數shardingContext
包含作業配置,分片和運行時信息。可通過getShardingTotalCount()
,getShardingItems()
等方法分別獲取分片總數,運行在本作業服務器的分片序列號集合等。
Simple類型作業
Simple
類型作業意爲簡單實現,未經任何封裝的類型。需要繼承AbstractSimpleElasticJob
,該類只提供了一個方法用於覆蓋,此方法將被定時執行。用於執行普通的定時任務,與Quartz
原生接口相似,只是增加了彈性擴縮容和分片等功能。
public class MyElasticJob extends AbstractSimpleElasticJob {
@Override
public void process(JobExecutionMultipleShardingContext context) {
// do something by sharding items
}
}
ThroughputDataFlow類型作業
ThroughputDataFlow
類型作業意爲高吞吐的數據流作業。需要繼承AbstractIndividualThroughputDataFlowElasticJob
並可以指定返回值泛型,該類提供3
個方法可覆蓋,分別用於抓取數據,處理數據和指定是否流式處理數據。可以獲取數據處理成功失敗次數等輔助監控信息。如果流式處理數據,fetchData
方法的返回值只有爲null
或長度爲空時,作業纔會停止執行,否則作業會一直運行下去;非流式處理數據則只會在每次作業執行過程中執行一次fetchData
方法和processData
方法,即完成本次作業。流式數據處理參照TbSchedule
設計,適用於不間歇的數據處理。
作業執行時會將fetchData
的數據傳遞給processData
處理,其中processData
得到的數據是通過多線程(線程池大小可配)拆分的。如果採用流式作業處理方式,建議processData
處理數據後更新其狀態,避免fetchData
再次抓取到,從而使得作業永遠不會停止。processData
的返回值用於表示數據是否處理成功,拋出異常或者返回false
將會在統計信息中歸入失敗次數,返回true
則歸入成功次數。
public class MyElasticJob extends AbstractIndividualThroughputDataFlowElasticJob<Foo> {
@Override
public List<Foo> fetchData(JobExecutionMultipleShardingContext context) {
Map<Integer, String> offset = context.getOffsets();
List<Foo> result = // get data from database by sharding items and by offset
return result;
}
@Override
public boolean processData(JobExecutionMultipleShardingContext context, Foo data) {
// process data
// ...
//TODO 多線程處理,高併發的情況,存儲的值可能不一致,謹慎使用
// store offset
for (int each : context.getShardingItems()) {
updateOffset(each, "your offset, maybe id");
}
return true;
}
}
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
作業。
public class MyElasticJob extends AbstractIndividualSequenceDataFlowElasticJob<Foo> {
@Override
public List<Foo> fetchData(JobExecutionSingleShardingContext context) {
int offset = context.getOffset();
List<Foo> result = // get data from database by sharding items and by offset
return result;
}
@Override
public boolean processData(JobExecutionSingleShardingContext context, Foo data) {
// process data
// ...
// store offset
updateOffset(context.getShardingItem(), "your offset, maybe id");
return true;
}
}
Script類型作業
Script
類型作業意爲腳本類型作業,支持shell
,Python
,perl
等所有類型腳本。只需通過控制檯/代碼配置scriptCommandLine即可。執行腳本路徑可以包含參數,最後一個參數爲作業運行時信息.
#!/bin/bash
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}
批量處理
爲了提高數據處理效率,數據流類型作業提供了批量處理數據的功能。之前逐條處理數據的兩個抽象類分別是AbstractIndividualThroughputDataFlowElasticJob
和AbstractIndividualSequenceDataFlowElasticJob
,批量處理則使用另外兩個接口AbstractBatchThroughputDataFlowElasticJob
和AbstractBatchSequenceDataFlowElasticJob
。不同之處在於processData
方法的返回值從boolean
類型變爲int
類型,用於表示一批數據處理的成功數量,第二個入參則轉變爲List
數據集合。
異常處理
elastic-job
在最上層接口提供了handleJobExecutionException
方法,使用作業時可以覆蓋此方法,並使用quartz
提供的JobExecutionException
控制異常後作業的聲明週期。默認實現是直接將異常拋出。示例:
任務監聽配置
可以通過配置多個任務監聽器,在任務執行前和執行後執行監聽的方法。監聽器分爲每臺作業節點均執行和分佈式場景中僅單一節點執行兩種。
每臺作業節點均執行的監聽
若作業處理作業服務器的文件,處理完成後刪除文件,可考慮使用每個節點均執行清理任務。此類型任務實現簡單,且無需考慮全局分佈式任務是否完成,請儘量使用此類型監聽器。
步驟:
- 定義監聽器
import com.dangdang.ddframe.job.api.JobExecutionMultipleShardingContext;
import com.dangdang.ddframe.job.api.listener.ElasticJobListener;
public class MyElasticJobListener implements ElasticJobListener {
@Override
public void beforeJobExecuted(final JobExecutionMultipleShardingContext shardingContext) {
// do something ...
}
@Override
public void afterJobExecuted(final JobExecutionMultipleShardingContext shardingContext) {
// do something ...
}
}
將監聽器作爲參數傳入
JobScheduler
public class JobMain {
public static void main(final String[] args) {
new JobScheduler(regCenter, jobConfig, new MyElasticJobListener()).init();
}
}
分佈式場景中僅單一節點執行的監聽
若作業處理數據庫數據,處理完成後只需一個節點完成數據清理任務即可。此類型任務處理複雜,需同步分佈式環境下作業的狀態同步,提供了超時設置來避免作業不同步導致的死鎖,請謹慎使用。
步驟:
- 定義監聽器
import com.dangdang.ddframe.job.api.JobExecutionMultipleShardingContext;
import com.dangdang.ddframe.job.api.listener.AbstractDistributeOnceElasticJobListener;
public final class TestDistributeOnceElasticJobListener extends AbstractDistributeOnceElasticJobListener {
public TestDistributeOnceElasticJobListener(final long startTimeoutMills, final long completeTimeoutMills) {
super(startTimeoutMills, completeTimeoutMills);
}
@Override
public void doBeforeJobExecutedAtLastStarted(final JobExecutionMultipleShardingContext shardingContext) {
// do something ...
}
@Override
public void doAfterJobExecutedAtLastCompleted(final JobExecutionMultipleShardingContext shardingContext) {
// do something ...
}
}
將監聽器作爲參數傳入
JobScheduler
public class JobMain {
public static void main(final String[] args) {
long startTimeoutMills = 5000L;
long completeTimeoutMills = 10000L;
new JobScheduler(regCenter, jobConfig, new MyDistributeOnceElasticJobListener(startTimeoutMills, completeTimeoutMills)).init();
}
}
作業配置
與spring
容器配合使用作業,可以將作業Bean
配置爲Spring
Bean
,可在作業中通過依賴注入使用Spring
容器管理的數據源等對象。可用placeholder
佔位符從屬性文件中取值。
Spring命名空間配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:reg="http://www.dangdang.com/schema/ddframe/reg"
xmlns:job="http://www.dangdang.com/schema/ddframe/job"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.dangdang.com/schema/ddframe/reg
http://www.dangdang.com/schema/ddframe/reg/reg.xsd
http://www.dangdang.com/schema/ddframe/job
http://www.dangdang.com/schema/ddframe/job/job.xsd
">
<!--配置作業註冊中心 -->
<reg:zookeeper id="regCenter" server-lists=" yourhost:2181" namespace="dd-job" base-sleep-time-milliseconds="1000" max-sleep-time-milliseconds="3000" max-retries="3" />
<!-- 配置簡單作業-->
<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" />
<!-- 配置數據流作業-->
<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" />
<!-- 配置腳本作業-->
<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" />
<!-- 配置帶監聽的簡單作業-->
<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">
<job:listener class="xx.MySimpleJobListener"/>
<job:listener class="xx.MyOnceSimpleJobListener" started-timeout-milliseconds="1000" completed-timeout-milliseconds="2000" />
</job:simple>
</beans>
job:simple命名空間屬性詳細說明
屬性名類型 | 是否必填 | 缺省值 | 描述 | |
---|---|---|---|---|
id | String | 是 |
作業名稱 | |
class | String | 否 | 作業實現類,需實現ElasticJob 接口,腳本型作業不需要配置 |
|
registry-center-ref | String | 是 |
註冊中心Bean 的引用,需引用reg:zookeeper 的聲明 |
|
cron | String | 是 |
cron 表達式,用於配置作業觸發時間 |
|
sharding-total-count | int | 是 |
作業分片總數 | |
sharding-item-parameters | String | 否 | 分片序列號和參數用等號分隔,多個鍵值對用逗號分隔 分片序列號從 0 開始,不可大於或等於作業分片總數如: 0=a,1=b,2=c |
|
job-parameter | String | 否 | 作業自定義參數 可以配置多個相同的作業,但是用不同的參數作爲不同的調度實例 |
|
monitor-execution | boolean | 否 | true | 監控作業運行時狀態 每次作業執行時間和間隔時間均非常短的情況,建議不監控作業運行時狀態以提升效率。因爲是瞬時狀態,所以無必要監控。請用戶自行增加數據堆積監控。並且不能保證數據重複選取,應在作業中實現冪等性。 每次作業執行時間和間隔時間均較長的情況,建議監控作業運行時狀態,可保證數據不會重複選取。 |
monitor-port | int | 否 | -1 | 作業監控端口 建議配置作業監控端口, 方便開發者dump作業信息。 使用方法: echo “dump” | nc 127.0.0.1 9888 |
max-time-diff-seconds | int | 否 | -1 | 最大允許的本機與註冊中心的時間誤差秒數 如果時間誤差超過配置秒數則作業啓動時將拋異常 配置爲 -1 表示不校驗時間誤差 |
failover | boolean | 否 | false | 是否開啓失效轉移 僅 monitorExecution 開啓,失效轉移纔有效 |
misfire | boolean | 否 | true | 是否開啓錯過任務重新執行 |
job-sharding-strategy-class | String | 否 | true | 作業分片策略實現類全路徑 默認使用平均分配策略 詳情參見:作業分片策略 |
description | String | 否 | 作業描述信息 | |
disabled | boolean | 否 | false | 作業是否禁止啓動 可用於部署作業時,先禁止啓動,部署結束後統一啓動 |
overwrite | boolean | 否 | false | 本地配置是否可覆蓋註冊中心配置 如果可覆蓋,每次啓動作業都以本地配置爲準 |
job:dataflow命名空間屬性詳細說明
job:dataflow命名空間擁有job:simple命名空間的全部屬性,以下僅列出特有屬性
屬性名 | 類型 | 是否必填 | 缺省值 | 描述 |
---|---|---|---|---|
process-count-interval-seconds | int | 否 | 300 | 統計作業處理數據數量的間隔時間 單位:秒 |
concurrent-data-process-thread-count | int | 否 | CPU核數*2 | 同時處理數據的併發線程數 不能小於1 僅 ThroughputDataFlow 作業有效 |
fetch-data-count | int | 否 | 1 | 每次抓取的數據量 |
streaming-process | boolean | 否 | false | 是否流式處理數據 如果流式處理數據, 則 fetchData 不返回空結果將持續執行作業如果非流式處理數據, 則處理數據完成後作業結束 |
job:script命名空間屬性詳細說明,基本屬性參照job:simple命名空間屬性詳細說明
job:script命名空間擁有job:simple命名空間的全部屬性,以下僅列出特有屬性
屬性名 | 類型 | 是否必填 | 缺省值 | 描述 |
---|---|---|---|---|
script-command-line | String | 否 | 腳本型作業執行命令行 |
job:listener命名空間屬性詳細說明
job:listener
必須配置爲job:bean
的子元素
屬性名 | 類型 | 是否必填 | 缺省值 | 描述 |
---|---|---|---|---|
class | String | 是 |
前置後置任務監聽實現類,需實現ElasticJobListener 接口 |
|
started-timeout-milliseconds | long | 否 |
Long.MAX_VALUE | AbstractDistributeOnceElasticJobListener型監聽器,最後一個作業執行前的執行方法的超時時間 單位:毫秒 |
completed-timeout-milliseconds | long | 否 |
Long.MAX_VALUE | AbstractDistributeOnceElasticJobListener型監聽器,最後一個作業執行後的執行方法的超時時間 單位:毫秒 |
reg:bean命名空間屬性詳細說明
屬性名 | 類型 | 是否必填 | 缺省值 | 描述 |
---|---|---|---|---|
id | String | 是 |
註冊中心在Spring 容器中的主鍵 |
|
server-lists | String | 是 |
連接Zookeeper 服務器的列表包括IP地址和端口號 多個地址用逗號分隔 如: host1:2181,host2:2181 |
|
namespace | String | 是 |
Zookeeper 的命名空間 |
|
base-sleep-time-milliseconds | int | 否 | 1000 | 等待重試的間隔時間的初始值 單位:毫秒 |
max-sleep-time-milliseconds | int | 否 | 3000 | 等待重試的間隔時間的最大值 單位:毫秒 |
max-retries | int | 否 | 3 | 最大重試次數 |
session-timeout-milliseconds | int | 否 | 60000 | 會話超時時間 單位:毫秒 |
connection-timeout-milliseconds | int | 否 | 15000 | 連接超時時間 單位:毫秒 |
digest | String | 否 | 無驗證 | 連接Zookeeper 的權限令牌缺省爲不需要權限驗證 |
nested-port | int | 否 | -1 | 內嵌Zookeeper 的端口號-1表示不開啓內嵌 Zookeeper |
nested-data-dir | String | 否 | 內嵌Zookeeper 的數據存儲路徑爲空表示不開啓內嵌 Zookeeper |
不使用Spring配置
如果不使用Spring框架,可以用如下方式啓動作業。
import com.dangdang.ddframe.job.api.config.JobConfiguration;
import com.dangdang.ddframe.job.api.JobScheduler;
import com.dangdang.ddframe.reg.base.CoordinatorRegistryCenter;
import com.dangdang.ddframe.reg.zookeeper.ZookeeperConfiguration;
import com.dangdang.ddframe.reg.zookeeper.ZookeeperRegistryCenter;
import com.dangdang.example.elasticjob.core.job.SimpleJobDemo;
import com.dangdang.example.elasticjob.core.job.ThroughputDataFlowJobDemo;
import com.dangdang.example.elasticjob.core.job.SequenceDataFlowJobDemo;
import com.dangdang.ddframe.job.plugin.job.type.integrated.ScriptElasticJob;
public class JobDemo {
// 定義Zookeeper註冊中心配置對象
private ZookeeperConfiguration zkConfig = new ZookeeperConfiguration("localhost:2181", "elastic-job-example", 1000, 3000, 3);
// 定義Zookeeper註冊中心
private CoordinatorRegistryCenter regCenter = new ZookeeperRegistryCenter(zkConfig);
// 定義簡單作業配置對象
private final SimpleJobConfiguration simpleJobConfig = JobConfigurationFactory.createSimpleJobConfigurationBuilder("simpleElasticDemoJob",
SimpleJobDemo.class, 10, "0/30 * * * * ?").build();
// 定義高吞吐流式處理的數據流作業配置對象
private final DataFlowJobConfiguration throughputJobConfig = JobConfigurationFactory.createDataFlowJobConfigurationBuilder("throughputDataFlowElasticDemoJob",
ThroughputDataFlowJobDemo.class, 10, "0/5 * * * * ?").streamingProcess(true).build();
// 定義順序的數據流作業配置對象
private final DataFlowJobConfiguration sequenceJobConfig = JobConfigurationFactory.createDataFlowJobConfigurationBuilder("sequenceDataFlowElasticDemoJob",
SequenceDataFlowJobDemo.class, 10, "0/5 * * * * ?").build();
// 定義腳本作業配置對象
private final ScriptJobConfiguration scriptJobConfig = JobConfigurationFactory.createScriptJobConfigurationBuilder("scriptElasticDemoJob",
10, "0/5 * * * * ?", "test.sh").build();
public static void main(final String[] args) {
new JobDemo().init();
}
private void init() {
// 連接註冊中心
regCenter.init();
// 啓動簡單作業
new JobScheduler(regCenter, simpleJobConfig).init();
// 啓動高吞吐流式處理的數據流作業
new JobScheduler(regCenter, throughputJobConfig).init();
// 啓動順序的數據流作業
new JobScheduler(regCenter, sequenceJobConfig).init();
// 啓動腳本作業
new JobScheduler(regCenter, scriptJobConfig).init();
}
}