複雜任務中,流程的解耦設計

知了一笑發表於2022-05-08

做事不能急,得一步非同步的來;

一、業務場景

在系統開發的過程中,必然存在耗時極高的動作,是基於請求響應模式無法解決的問題,通常會採用解耦的思維,並基於非同步或者事件驅動的方式去排程整個流程的完整執行;

檔案任務:在系統解析大檔案資料時,在獲取任務之後,會非同步處理後續檔案讀寫流程;

中間表:執行復雜場景的資料分析時,收集完待分析的物件之後,會併發執行各個維度的採集動作,並依次將資料寫入臨時的中間表中,方便資料查詢動作;

在上述場景中,基於單次請求響應無法執行整個過程,必須對流程分段分步和非同步推進,在流程中根據場景去判斷,是非同步有序驅動,還是非同步併發處理,並基於各個節點的執行狀態判斷動作是否成功。

二、任務管理

複雜任務的執行週期相對偏長,要確保穩定的執行則需要對任務做精細的設計和管理,通常會基於如下幾個因素去描述任務:

  • 場景:定義任務的主題場景,便於將多種任務做統一管理和排程,例如:檔案、資料、報表等;
  • 計劃:對任務做好步驟的拆分,並制定和推進相應的執行計劃,例如:有序排程、併發執行等;
  • 狀態:針對任務和節點的執行計劃,都要提供細節的狀態定義,例如:開始/結束,進行中/已完成,成功/失敗等;

設計合理的任務結構,以便更高效的管理流程,根據主題場景做任務分類,新增相應的執行計劃,根據狀態跟蹤任務執行過程,並對失敗動作進行捕捉和重試;

三、設計思路

1、同步請求響應

服務之間的通訊模式一般分為:同步和非同步兩種;同步是指在請求端發出動作之後,會一直等待響應端完成,或者響應超時導致熔斷,即在一次請求呼叫中耦合所有的處理流程;

服務中大部分的請求都是同步響應模式,可以提高系統的響應速度;但是在分散式中,首先要控制超時熔斷的時間,避免在流量高峰期請求堆積,拖垮整個服務;另外對於被大量呼叫的公共服務,要提高併發的支撐能力,降低對請求鏈路的效能影響。

2、非同步解耦模式

非同步模式的最大優點就是實現請求和響應的完全解耦,任務只需要觸發一次開始動作,後續的流程就會逐步的推進直到結束;各個服務節點處理邏輯不會受到整個請求鏈路的耗時限制;

實現非同步有多種方式,例如:請求回撥、釋出訂閱、Broker代理等;在之前非同步章節中有詳細描述,這裡不再贅述;非同步消除了服務節點之間的依賴關係,但是也同樣提高了流程的複雜性;

3、事件驅動設計

事件驅動是一個抽象的概念,即通過事件的方式實現多個服務間的協同,驅動整個流程的處理邏輯;在業務層面是一種設計思想,在技術層面通常採用釋出訂閱的方式,同樣也可以消除服務間的強依賴關係;

事件和非同步在模式上很類似,事件驅動在設計上更加精細,例如在訂單場景中:將訂單的狀態變化作為一個事件,服務間通過訊息傳遞的方式,依次處理庫存服務、物流服務等;由於事件攜帶了一定的業務資訊和狀態,流程解耦更加徹底的同時複雜度也會更高。

四、實踐總結

1、結構設計

在結構設計中圍繞任務、節點、資料三個核心要素,以確保對任務的執行過程有完整的跟蹤和管理,要實現對任務的節點及相關的操作,具備執行重試或者直接取消撤回的控制;

狀態管理是一項很複雜的工作,要衡量任務中各個狀態標識是否合理,就要實時監控狀態的變化,並且基於各種極端情況去驗證流程,例如:重試設計、任務取消、任務暫停。

2、高併發管理

任務型的場景加上覆雜的管理流程,執行時間自然也很長,如果場景中涉及到大檔案的解析、或者資料排程,自然會引入任務分割與併發執行的機制;

比較常用的思路:根據任務排程的叢集數,對資料核心編號進行雜湊計算,可以採用取模和分段兩種演算法,然後基於多執行緒的方式併發處理各自服務內的分管任務。

3、管理模型

不管是觀察者模式,或者釋出訂閱模型,又或者說事件驅動設計,都可以理解為生產/消費的關係模型,圍繞生產、儲存、消費三個節點做管理;

  • 生產端:負責建立具體的訊息主體,在匯流排模式中,通常將訊息進行入庫儲存,然後再執行佇列推送,並跟蹤該過程的狀態變化,保證庫和佇列的一致性;
  • 訊息體:描述動作的釋出方和消費方,關鍵的狀態資訊變化,唯一標識和建立時間及版本,其餘則根據場景需要定義即可;
  • 消費端:在消費時要關注的核心問題即失敗重試,要避免重試機制引起資料不一致的問題,可以對消費進行加鎖或者訊息狀態校驗,以實現冪等的效果;
  • 儲存端:通常採用資料庫和訊息中介軟體雙儲存的模式,並且需要保證二者動作的同時成功或者失敗,順序為先入庫再執行佇列推送;

整個模型在設計思路上比較合理,但是架構的複雜性也變的很高,比如資料一致性問題、狀態機制、事務、冪等性、流程中斷等;整個鏈路需要詳細的追蹤記錄並且視覺化管理,開發補償動作的介面,用來及時解決可能出現的突發問題。

4、元件案例

Spring框架本身就極具複雜度,這裡單看事件模型的設計,包含三個核心角色:事件、釋出、監聽;與觀察者設計模式在理念上相同;

事件:ApplicationEvent基礎抽象類繼承自JDK中EventObject類,具體事件要繼承該類;source事件源,timestamp發生的系統時間;

public class OrderState {
    // 基礎要素
    private Integer eventId ;
    private String version ;
    private Long createTime ;

    // 訊息定位
    private String source ;
    private String target ;

    // 狀態變化
    private Integer orderId ;
    private Integer stateFrom ;
    private Integer stateTo ;
}
public class OrderStateEvent extends ApplicationEvent {
    public OrderStateEvent (OrderState orderState){
        super(orderState);
    }
}

釋出者:Spring定義的頂級介面ApplicationEventPublisher,提供事件釋出的能力;

@Service
public class EventService implements ApplicationContextAware, ApplicationEventPublisherAware {
    private ApplicationEventPublisher applicationEventPublisher;
    
    public void changeState (Integer orderId,Integer stateFrom,Integer stateTo){
        OrderState orderState = new OrderState() ;
        OrderStateEvent orderStateEvent = new OrderStateEvent(orderState) ;
        logger.info(Thread.currentThread().getName()+";"+orderStateEvent);
        applicationEventPublisher.publishEvent(orderStateEvent);
    }
}

監聽者:實現JDK中頂級介面EventListener,Spring擴充套件了多種事件監聽器,以實現各種場景的需求,例如:有序無序、同步非同步等;

@Component
public class OrderStateListener implements ApplicationListener<OrderStateEvent> {
    private static final Logger logger = LoggerFactory.getLogger(OrderStateListener.class) ;

    @Async
    @Override
    public void onApplicationEvent(OrderStateEvent orderStateEvent) {
        logger.info(Thread.currentThread().getName()+";"+orderStateEvent);
    }
}

五、參考原始碼

應用倉庫:
https://gitee.com/cicadasmile/middle-ware-parent

程式設計文件:
https://gitee.com/cicadasmile/butte-java-note

相關文章