Elasticjob執行job冪等

月落长空發表於2024-07-25

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,等任務執行完畢後,再統一執行下一次任務排程。

相關文章