基於任務排程的企業級分散式批處理方案

阿里巴巴雲原生發表於2022-06-08

作者:姚輝(千習)

背景

先來談下什麼是分散式批處理,從字面來理解就是有大批量的業務資料需要應用程式去批量計算處理,而通過單機模式去執行會耗費很長的處理時間,也不能充分發揮業務叢集中每個應用節點處理能力。通過一些常見的分散式批處理方案,可以有效地讓業務叢集中所有業務應用節點協同完成一個大批量資料處理的任務,從而提升整體的處理效率和處理可靠性。

 title=

批處理模型

在簡單單機場景下可以開啟多執行緒來同時處理一個大任務,在多個機器下可以由多臺機器同時並行處理同一個任務。因此,分散式批處理方案需要為開發者在程式碼開發層面遮蔽上述任務切分後分發、並行執行、結果匯聚、失敗容錯、動態擴容等業務應用叢集間的分散式協調邏輯,讓使用者僅聚焦於上述紅框描述的業務邏輯分片規則和業務邏輯處理即可。

大資料批處理比較

在大資料處理場景中我們也會用到 MapReduce 模型,其處理邏輯本質與我們要討論的業務批處理邏輯是一致的。在大資料場景下的批處理主要是面向資料本身的處理,並需要部署相應大資料平臺叢集來支援資料儲存和資料批處理程式處理,因此該場景下主要目的是用於構建一個完整的資料平臺。與大資料批處理場景相比較,本次更主要聚焦討論分散式業務批處理場景,基於現有業務應用服務叢集構建分散式批處理邏輯。通過分散式批處理方案可以解決以下需求

  • 對耗時業務邏輯解耦,保障核心鏈路業務處理快速響應
  • 充分排程業務叢集所有應用節點合作批量完成業務處理
  • 有別於大資料處理,子任務處理過程中還會有呼叫其他線上業務服務參與批處理過程

開源批處理方案

ElasticJob

ElasticJob 是一款分散式任務排程框架,其主要特點是在 Quartz 基礎上實現定時排程並提供在業務叢集中對任務進行分片協調處理能力。在整個架構上基於 Zookeeper 來實現任務分片執行、應用叢集動態彈性排程、子任務執行高可用。分片排程模型可支援大批量業務資料處理均衡的分發至業務叢集中的每一個節點進行處理,有效地提高了任務處理效率。

 title=

  • SimpleJob

Spring Boot 工程可通過 YAML 配置任務定義,指定以下內容:任務實現類、定時排程週期、分片資訊。

elasticjob:
  regCenter:
    serverLists: 127.0.0.1:2181
    namespace: elasticjob-lite-springboot
  jobs:
    simpleJob:
      elasticJobClass: org.example.job.SpringBootSimpleJob
      cron: 0/5 * * * * ?
      overwrite: true
      shardingTotalCount: 3
      shardingItemParameters: 0=Beijing,1=Shanghai,2=Guangzhou

配置的 org.example.job.SpringBootSimpleJob 類需要實現 SimpleJob 介面的 execute 方法,並且通過 ShardingContext 引數獲取對應業務分片資料進行業務邏輯處理。

@Component
public class SpringBootSimpleJob implements SimpleJob {
    @Override
    public void execute(final ShardingContext shardingContext) {
        String value = shardingContext.getShardingParameter();
        System.out.println("simple.process->"+value);
    }
}

我們部署 3 個應用服務作為排程處理叢集處理上述任務,當任務觸發執行時,ElasticJob 就會將對應 3 個分片任務分別給 3 個應用服務進行處理來完成整個任務資料處理。

 title=

  • DataflowJob

DataflowJob 目前來看本質上跟 SimpleJob 在整體的結構上並無本質區別。參考如下介面,相比 SimpleJob 其增加了 fetchData 方法供業務方自行實現載入要處理的資料,實際就是將 SimpleJob 的 execute 方法在邏輯定義上拆解成兩個步驟。唯一區別在於 DataflowJob 提供一種常駐的資料處理任務(可稱為:streaming process),支援任務常駐執行直至 fetchData 為空。

public interface DataflowJob<T> extends ElasticJob {

    /**
     * Fetch to be processed data.
     *
     * @param shardingContext sharding context
     * @return to be processed data
     */
    List<T> fetchData(ShardingContext shardingContext);

    /**
     * Process data.
     *
     * @param shardingContext sharding context
     * @param data to be processed data
     */
    void processData(ShardingContext shardingContext, List<T> data);
}

在 DataflowJob 任務的 yaml 配置上新增 props: streaming.process=true,即可實現該任務 streaming process 的效果。當任務被觸發執行後,每個分片任務將按對應流程:fetchData->processData->fetchData 迴圈執行直到 fetchData 為空。該模式場景分析:

  • 單個分片任務待資料量大,fetchData 時讀取該分片部分分頁資料進行處理直至所有資料處理完畢
  • 分片待資料持續產生,使任務通過 fetchData 一直獲取資料,實現長期駐留持續地進行業務資料處理
elasticjob:
  regCenter:
    serverLists: 127.0.0.1:2181
    namespace: elasticjob-lite-springboot
  jobs:
    dataflowJob:
      elasticJobClass: org.example.job.SpringBootDataflowJob
      cron: 0/5 * * * * ?
      overwrite: true
      shardingTotalCount: 3
      shardingItemParameters: 0=Beijing,1=Shanghai,2=Guangzhou
      props:
        # 開啟streaming process
        streaming.process: true
  • 特性分析

ElasticJob 的分散式分片排程模型,對常見簡單的批處理場景提供了很大的便利支援,解決了一個大批量業務資料處理分散式切分執行的整個協調過程。另外在以下一些方面可能還存在些不足:

  • 整個架構的核心取決於 ZK 穩定性
    • 需要額外運維部署並且要保證其高可用
    • 大量任務儲存觸發執行過程都依賴 ZK,當任務量大時 ZK 叢集容易成為排程效能瓶頸
  • 分片配置數量固定,不支援動態分片
    • 如每個分片待處理資料量差異大時,容易打破叢集處理能力平衡
    • 如分片定義不合理,當叢集規模遠大於分片數量時叢集彈性失去效果
    • 分片定義與業務邏輯較為割裂,人為維持兩者之間聯絡比較麻煩
  • 管控臺能力弱

Spring Batch 批處理框架

Spring Batch 批處理框架,其提供輕量且完善批處理能力。Spring Batch 任務批處理框主要提供:單程式多執行緒處理、分散式多程式處理兩種方式。在單程式多執行緒處理模式下,使用者可自行定一個 Job 作為一個批處理任務單元,Job 是由一個或多個 Step 步驟進行串聯或並行組成,每一個 Step 又分別由 reader、process、writer 構成來完成每一步任務的讀取、處理、輸出。後續主要討論一個 Job 只包含一個 Step 的場景進行分析。

 title=

Spring Batch 框架個人覺得單程式下多執行緒實踐意義並不是太大,主要是在較小批量資料任務處理採用該框架來實現有點費功夫,完全可以自行開執行緒池來解決問題。本次討論主要聚焦於一定規模的業務叢集下分散式協同完成業務資料批處理任務的場景。在 Spring Batch 中提供了遠端分片/分割槽處理能力,在 Job 的 Step 中可根據特定規則將任務拆分成多個子任務並分發給叢集中其他的 worker 來處理,以實現分散式並行批處理處理能力。其遠端互動能力常見是藉助第三方訊息中介軟體來實現子任務的分發和執行結果匯聚。

  • 遠端分塊(Remote Chunking)

遠端分塊是 Spring Batch 在處理大批量資料任務時提供的一種分散式批處理解決方案,它可以做到在一個 Step 步驟中通過 ItemReader 載入資料構建成多個 Chunk 塊,並由 ItemWriter 將這多個分塊通過訊息中介軟體或其他形式分發至叢集節點,由叢集應用節點對每一個 Chunk 塊進行業務處理。

 title=

Remote Chunking 示例

在上述主節點 ItemReader 和 ItemWriter 可以對映為本次討論的批處理模型中的“任務拆分-split”階段,主節點對 ItemWriter 可採用 Spring Batch Integration 提供的 ChunkMessageChannelItemWriter,該元件通過整合 Spring Integration 提供的其他通道(如:AMQP、JMS)完成批處理任務資料載入和分塊分發。


    @Bean
    public Job remoteChunkingJob() {
         return jobBuilderFactory.get("remoteChunkingJob")
             .start(stepBuilderFactory.get("step2")
                     .<Integer, Integer>chunk(2) // 每Chunk塊包含reader載入的記錄數
                     .reader(itemReader())
                     // 採用ChunkMessageChannelItemWriter分發Chunk塊
                     .writer(itemWriter())
                     .build())
             .build();
     }

    @Bean
    public ItemReader<Integer> itemReader() {
        return new ListItemReader<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
    }

    @Bean
    public ItemWriter<Integer> itemWriter() {
        MessagingTemplate messagingTemplate = new MessagingTemplate();
        messagingTemplate.setDefaultChannel(requests());
        ChunkMessageChannelItemWriter<Integer> chunkMessageChannelItemWriter = new ChunkMessageChannelItemWriter<>();
        chunkMessageChannelItemWriter.setMessagingOperations(messagingTemplate);
        chunkMessageChannelItemWriter.setReplyChannel(replies());
        return chunkMessageChannelItemWriter;
    }
    // 省略了相關訊息中介軟體對接通道配置

Slave 節點主要是對分發過來的 Chunk 塊資料(可理解為子任務)進行對應業務邏輯處理和資料結果輸出。因此,在子任務處理端需要通過配置 Spring Batch Integration 提供的 ChunkProcessorChunkHandler 來完成子任務接收、實際業務處理、反饋處理結果等相關動作。

    // 省略了相關訊息中介軟體對接通道配置

    // 接收分塊任務升級及反饋執行結果
    @Bean
    @ServiceActivator(inputChannel = "slaveRequests", outputChannel = "slaveReplies")
    public ChunkProcessorChunkHandler<Integer> chunkProcessorChunkHandler() {
        ChunkProcessor<Integer> chunkProcessor = new SimpleChunkProcessor(slaveItemProcessor(), slaveItemWriter());
        ChunkProcessorChunkHandler<Integer> chunkProcessorChunkHandler = new ChunkProcessorChunkHandler<>();
        chunkProcessorChunkHandler.setChunkProcessor(chunkProcessor);
        return chunkProcessorChunkHandler;
    }

    // 實際業務需要開發的任務處理邏輯processor
    @Bean
    public SlaveItemProcessor slaveItemProcessor(){ return new SlaveItemProcessor();}

    // 實際業務需要開發的任務處理邏輯writer
    @Bean
    public SlaveItemWriter slaveItemWriter(){ return new SlaveItemWriter();}
  • 遠端分割槽(Remote Partitioning)

遠端分割槽與遠端分塊主要區別在於 master 節點不負責資料載入,可理解為將當前 Step 通過 Partitioner 拆分出多個子 Step(也可以理解為子任務),然後通過 PartitionHandler 將對應的子任務分發給各個 Slave 節點處理,為此,Spring Batch Integration 提供了 MessageChannelPartitionHandler 來實現對應的子任務分發,其底層也是需要依賴訊息中介軟體等進行適配對接。在每個 Slave 節點需要讀取子任務 Step 的上下文資訊,根據該資訊進行完整的 ItemReader、ItemProcess、ItemWrite 處理。

 title=

  • 特性分析

Spring Batch 框架,綜合特性分析:

  • 具有完備批處理能力:支援單機多執行緒、分散式多程式協同批處理處理,支援自定義的分片模型。
  • 缺定時排程支援:原生無定時排程能力需整合三方定時框架(如:Spring Task 需自行解決叢集重複觸發)。
  • 視覺化管控能力弱:Spring Batch 常見採用程式或檔案配置任務,管控臺需額外搭建且管控能力較弱。
  • 整合難度高:其分散式批處理能力需額外第三方中介軟體整合搭建,或基於其介面自行擴充套件開發;基於官方提供的方式完成企業級使用需要相對複雜規劃整合。

企業級批處理方案-SchedulerX 視覺化 MapReduce 任務

SchedulerX 任務排程平臺針對企業級批處理需求提供了完善的整體解決方案,使用者可直接採用公有云平臺的服務即可輕鬆實現業務應用叢集的分散式批處理能力(使用者非阿里雲業務應用部署也可支援對接),無需額外部署其他中介軟體整合維護。

原理剖析

在整個解決方案中,任務排程平臺為使用者註冊的任務提供全方位的視覺化管控、高可靠定時排程以及視覺化查詢能力。另外,在使用者業務應用側通過整合 SchedulerX SDK,即可實現分散式批處理能力的快速接入。此時使用者僅需關心批處理模型中子任務業務切分規則、每個子任務處理邏輯即可。這個分散式批處理過程中具備以下特性:

  • 子任務高可用:當叢集執行節點當機時,支援自動 failover 將掉線機器上對子任務重新分發給其他節點
  • 自動彈性擴容:當叢集中有新對應用節點部署上來後,能自動參與到後續任務的執行過程中
  • 視覺化能力:為任務和子任務的執行過程提供各類監控運維及業務日誌查詢能力

 title=

下面描述下大致的原理過程:

  • 在平臺建立 MapReduce 任務後,定時排程服務會為它開啟高可靠的定時觸發執行
  • 當 MapReduce 任務觸發執行時,排程服務會在接入上來的業務 Worker 節點中選擇一個節點作為本次任務執行的主節點
  • 主節點執行執行使用者自定義開發的子任務切分載入邏輯,並通過 map 方法呼叫給叢集中其他 worker 節點均衡地分發子任務處理請求
  • 主節點會監控整個分散式批處理任務的處理過程,以及每個 Worker 節點健康監控,保障整體執行高可用
  • 其他各個 worker 節點在接收子任務處理請求後,開始回撥執行使用者自定義的業務邏輯,最終完成每個子任務的處理需求;並且可以配置單個應用節點同時處理子任務的並行執行緒數。
  • 所有子任務完成後,主節點將匯聚所有子任務執行結果回撥 reduce 方法,並反饋排程平臺記錄本次執行結果

開發者只需在業務應用中實現一個 MapReduceJobProcessor 抽象類,在 isRootTask 中載入本次需要處理的業務子任務資料物件列表;在非 root 請求中通過 jobContext.getTask()獲取單個子任務物件資訊,根據該資訊執行業務處理邏輯。在業務應用部署釋出至叢集節點後,當任務觸發執行時叢集所有節點會參與協調整個分散式批處理任務執行直至完成。

public class MapReduceHelloProcessor extends MapReduceJobProcessor {

    @Override
    public ProcessResult reduce(JobContext jobContext) throws Exception {
        // 所有子任務完成的匯聚邏輯處理回撥,可選實現
        jobContext.getTaskResults();
        return new ProcessResult(true, "處理結果數量集:" + jobContext.getTaskResults().size());
    }

    @Override
    public ProcessResult process(JobContext jobContext) throws Exception {
        if (isRootTask(jobContext)) {
            List<String> list = // 載入業務待處理的子任務列表
            // 回撥sdk提供的map方法,自動實現子任務分發
            ProcessResult result = map(list, "SecondDataProcess");
            return result;
        } else {
            // 獲得單個子任務資料資訊,進行單個子任務業務處理
            String data = (String) jobContext.getTask();
            // ... 業務邏輯處理補充 ...
            return new ProcessResult(true, "資料處理成功!");
        }
    }
}

功能優勢

  • 子任務視覺化能力

使用者大盤:提供了所有任務的觸發執行視覺化記錄資訊。

 title=

視覺化子任務詳情:通過查詢任務執行記錄詳情,可獲得每一個子任務執行狀態及所在節點。

 title=

  • 子任務業務日誌

在子任務列表中點選“日誌”,可以獲得當前子任務處理過程中的日誌記錄資訊。

 title=

  • 執行堆疊檢視

執行堆疊檢視功能,可用於在子任務處理過程中出現卡住一直執行未結束的場景下,方便排查對應執行執行緒棧資訊。

 title=

 title=

  • 自定義業務標籤

子任務業務標籤能力,為使用者提供了快速視覺化的子任務業務資訊檢視和查詢能力。在下圖中“賬戶名稱”是本次子任務切分出來的業務標籤資訊,使用者可基於該資訊快速瞭解對應業務子任務的處理狀態,並支援查詢指定業務標籤資訊的子任務處理狀態。

 title=

如何為子任務配置自定義標籤,只需對本次 map 分發的子任務物件實現 BizSubTask 介面,並實現其 labelMap 方法即可為每個子任務新增其專屬的業務特徵標籤用於視覺化查詢。

public class AccountTransferProcessor extends MapReduceJobProcessor {

    private static final Logger logger = LoggerFactory.getLogger("schedulerxLog");

    @Override
    public ProcessResult reduce(JobContext context) throws Exception {
        return new ProcessResult(true);
    }

    @Override
    public ProcessResult process(JobContext context) throws Exception {
        if(isRootTask(context)){
            logger.info("split task list size:20");
            List<AccountInfo> list = new LinkedList();
            for(int i=0; i < 20; i++){
                list.add(new AccountInfo(i, "CUS"+StringUtils.leftPad(i+"", 4, "0"),
                        "AC"+StringUtils.leftPad(i+"", 12, "0")));
            }
            return map(list, "transfer");
        }else {
            logger.info("start biz process...");
            logger.info("task info:"+context.getTask().toString());
            TimeUnit.SECONDS.sleep(30L);
            logger.info("start biz process end.");
            return new ProcessResult(true);
        }
    }
}

public class AccountInfo implements BizSubTask {

        private long id;

        private String name;

        private String accountId;

        public AccountInfo(long id, String name, String accountId) {
            this.id = id;
            this.name = name;
            this.accountId = accountId;
        }

        // 子任務標籤資訊設定
        @Override
        public Map<String, String> labelMap() {
            Map<String, String> labelMap = new HashMap();
            labelMap.put("賬戶名稱", name);
            return labelMap;
        }
    }
  • 相容開源

SchedulerX 支援基於常見開源框架編寫的執行器,包括:XXL-Job、ElasticJob,後續排程平臺還將計劃支援排程 Spring Batch 任務。

案例場景

分散式批處理模型(視覺化 MapReduce 模型),在實際企業級應用中是有大量的需求場景存在。一些常見的使用場景如:

  • 針對分庫分表資料批量並行處理,將分庫或分表資訊作為子任務物件在叢集節點間分發實現並行處理
  • 按城市區域的物流訂單資料處理,將城市和區域作為子任務物件在叢集節點間分發實現並行處理
  • 鑑於視覺化 MapReduce 子任務視覺化能力,可將重點客戶/訂單資訊作為子任務處理物件,來進行相應資料包表處理或資訊推送,以實現重要子任務的視覺化跟蹤處理
  • 基金銷售業務案例

以下提供一個基金銷售業務案例以供參考如果使用分散式批處理模型,以便使用者在自己的業務場景下自由發揮。案例說明:在基金公司與基金銷售公司(如:螞蟻財富)之間每天會有投資者的賬戶/交易申請資料同步處理,其往往採用的是檔案資料互動,一個基金公司對接著 N 多家銷售商(反之亦然),每家銷售商提供的資料檔案完全獨立;每一個銷售商的資料檔案都需要經過檔案校驗、介面檔案解析、資料校驗、資料匯入這麼幾個固定步驟。基金公司在處理上述固定步驟就非常適合採用分散式批處理方式以加快資料檔案處理,以每個銷售商為子任務物件分發至叢集中,所有應用節點參與解析各自分配到的不同銷售商資料檔案處理。

@Component
public class FileImportJob extends MapReduceJobProcessor {

    private static final Logger logger = LoggerFactory.getLogger("schedulerx");

    @Override
    public ProcessResult reduce(JobContext context) throws Exception {
        return new ProcessResult(true);
    }

    @Override
    public ProcessResult process(JobContext context) throws Exception {
        if(isRootTask(context)){
            // ---------------------------------------------------------
            // Step1. 讀取對接的銷售商列表Code
            // ---------------------------------------------------------
            logger.info("以銷售商為維度構建子任務列表...");

            // 虛擬碼從資料庫讀取銷售商列表,Agency類需要實現BizSubTask介面並可將
            // 銷售商名稱/編碼作為子任務標籤,以便控制檯視覺化跟蹤
            List<Agency> agencylist = getAgencyListFromDb();
            return map(agencylist, "fileImport");
        }else {
            // ---------------------------------------------------------
            // Step2. 針對單個銷售商進行對應檔案資料的處理
            // ---------------------------------------------------------
            Agency agency = (Agency)context.getTask();
            File file = loadFile(agency);
            logger.info("檔案載入完成.");
            validFile(file);
            logger.info("檔案校驗通過.");
            List<Request> request = resolveRequest(file);
            logger.info("檔案資料解析完成.");
            List<Request> request = checkRequest(request);
            logger.info("申請資料檢查通過.");
            importRequest(request);
            logger.info("申請資料匯入完成.");
            return new ProcessResult(true);
        }
    }
}

 title=

案例主要是將基金交易清算中的一個業務環節,採用並行批處理方式來進行處理,其後續每一個處理環節也可以採用類似方式處理。另外,每一個視覺化 MapReduce 任務節點通過 DAG 依賴編排可構建一個完整的自動業務清算流程。

總結

分散式任務排程平臺 SchedulerX 為企業級分散式批處理提供來完善的解決方案,為使用者提供了快速易用的接入模式,並支援定時排程、視覺化執行跟蹤、可管控簡運維、高可用的排程服務,同時配套企業級監控大盤、日誌服務、監控報警等能力。

參考文獻:

Spring Batch Integration:

https://docs.spring.io/spring...

ElasticJob:

https://shardingsphere.apache...

分散式任務排程 SchedulerX 使用手冊:

https://help.aliyun.com/docum...

SchedulerX 如何幫助使用者解決分散式任務排程:

https://mp.weixin.qq.com/s/EgyfS1Vuv4itnuxbiT7KwA

相關文章