ElasticJob的冪等機制,是指作業分片執行的冪等,他需要做到以下兩點:
-
同一個分片在當前作業例項上不會被重複執行
-
一個作業分片不能同時在多個作業例項上執行
如何實現冪等
場景模擬:存在任務A執行週期為10s一次。正常情況下任務處理耗時3-5s。但是某一時刻因為資料量突然增大或者因為資料庫壓力,導致任務耗時超過了10s。在該過程中,任務每10s排程一次,如果沒有冪等,那麼會存在一個任務同時多個排程的情況,處理相同的資料。
ElasticJob任務執行:com.dangdang.ddframe.job.executor.AbstractElasticJobExecutor#execute()
public final void execute() {
...
// 獲取當前作業伺服器的分片上下文
ShardingContexts shardingContexts = jobFacade.getShardingContexts();
//是否允許可以傳送作業事件
if (shardingContexts.isAllowSendJobEvent()) {
// 釋出作業狀態追蹤事件
jobFacade.postJobStatusTraceEvent(shardingContexts.getTaskId(), State.TASK_STAGING, String.format("Job '%s' execute begin.", jobName));
}
// 在一個排程任務觸發後如果上一次任務還未執行,則需要設定該分片狀態為mirefire,表示錯失了一次任務執行
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;
}
...
}
接下來主要看一下jobFacade.misfireIfRunning
的實現邏輯
public boolean misfireIfHasRunningItems(Collection<Integer> items) {
// 沒有分片正在執行,返回false,此次任務排程正常進行,否則設定mirefire
if (!this.hasRunningItems(items)) {
return false;
} else {
this.setMisfire(items);
return true;
}
}
如果存在未完成排程的分片,則呼叫setMisfire(items)
方法。如何判斷是否有未完成排程的分片呢,看看hasRunningItems(items)
的實現邏輯。
public boolean hasRunningItems(Collection<Integer> items) {
LiteJobConfiguration jobConfig = this.configService.load(true);
if (null != jobConfig && jobConfig.isMonitorExecution()) {
Iterator i$ = items.iterator();
int each;
do {
if (!i$.hasNext()) {
return false;
}
each = (Integer)i$.next();
} while(!this.jobNodeStorage.isJobNodeExisted(ShardingNode.getRunningNode(each)));
// ShardingNode.getRunningNode(each)
/*
public static String getRunningNode(int item) {
return String.format("sharding/%s/running", item);
}*/
return true;
} else {
return false;
}
}
在Elasticjob開啟monitorExecution
的機制下,分片任務開始時會建立sharding/分片/running
節點,任務完成後刪除該節點。所以上述程式碼中,可以看出來,可以透過是否存在該分片的節點來判斷是否有分片正在執行。
同時,呼叫setMisfire(items)
方法的時候,根據程式碼判斷,setMisfire(items)
方法為分配給該例項下的所有分片建立持久節點/shading/{item}/misfire節點,只要分配給該例項的任何一分片未執行完畢,則在該例項下的所有分片都增加misfire節點,然後忽略本次任務觸發執行,等待任務結束後再執行。
public void setMisfire(Collection<Integer> items) {
Iterator i$ = items.iterator();
while(i$.hasNext()) {
int each = (Integer)i$.next();
this.jobNodeStorage.createJobNodeIfNeeded(ShardingNode.getMisfireNode(each));
}
// ShardingNode.getMisfireNode(each)
/**
static String getMisfireNode(int item) {
return String.format("sharding/%s/misfire", item);
}
*/
}
在該執行方法中(com.dangdang.ddframe.job.executor.AbstractElasticJobExecutor#execute()
)
//執行job
execute(shardingContexts, JobExecutionEvent.ExecutionSource.NORMAL_TRIGGER);
// 如果存在Misfire節點,則清除該節點
while (jobFacade.isExecuteMisfired(shardingContexts.getShardingItemParameters().keySet())) {
// 清除Misfire節點
jobFacade.clearMisfire(shardingContexts.getShardingItemParameters().keySet());
execute(shardingContexts, JobExecutionEvent.ExecutionSource.MISFIRE);
}
總結:在下一個排程週期到達之後,只要發現這個分片的任何一個分片正在執行,則為該例項分片的所有分片都設定為misfire,等任務執行完畢後,再統一執行下一次任務排程。