領域事件 disruptor 使用場景之實現Spring事件驅動模型 ApplicationEvent

前言

Disruptor 是一個開源的併發框架。由英國外匯交易公司LMAX開發的一個高性能隊列,並且大大的簡化了併發程序開發的難度,獲得2011Duke’s程序框架創新獎。

假設場景

假設有這麼一種業務場景,業務爲【用戶註冊】處理完後,同時觸發【郵件通知】業務、【贈送積分】業務的執行,在不利用MQ的情況下,會有什麼樣的解決思路?可能的解決思路有如下

  1. 業務【用戶註冊】處理後,開啓線程處理【郵件通知】、【贈送積分】的業務
  2. 使用disruptor進行處理
  3. 生產消費者模式
  4. 觀察者模式 ... 解決的思路有很多。

​ 本文就介紹的是基於disruptor實現的,類似Spring事件驅動模型 ApplicationEvent,這裏稱爲領域事件。事件驅動模式與觀察者模式在某些方面極爲相似;當一個主體發生改變時,所有依屬體都得到通知。不過,觀察者模式與單個事件源關聯,而事件驅動模式則可以與多個事件源關聯。下面先看一下對於領域事件的介紹。

簡介 Event Source 領域事件

  1. 領域驅動設計,基於LMAX架構。
  2. 單一職責原則,可以給系統的可擴展、高伸縮、低耦合達到極致。
  3. 異步高併發、線程安全的、使用disruptor環形數組來消費業務。可併發執行,性能超高,執行1000W次事件只需要1.1秒左右(這個得看你的電腦配置)。
  4. 使用事件消費的方式編寫代碼,使得業務在複雜也不會使得代碼混亂,維護代碼成本更低
  5. 可靈活的定製業務線程模型
  6. 插件形式提供事件領域,做到了可插拔,就像玩樂高積木般有趣。

領域事件的本質是對disruptor的封裝使用。

使用場景

計算密集型的業務推薦使用,比如鬥地主中的出牌,所有牌桌可使用同一個領域事件來處理,但牌局結算時如果涉及IO (類似 DB 的入庫時 ),就可切換到其他的領域事件,這樣就可以不阻塞出牌業務線程。

​ 一個領域事件可以看成是一個線程,那麼也就是說,我們可以用一個出牌的領域事件,做計算密集型的出牌業務,用N個結算的領域事件來做結算業務。可以舉例不太恰當,但大概就是這個意思。

​ 下面的示例中也介紹了其他的使用場景,類似Spring事件驅動模型 ApplicationEvent。

圖解

領域事件消費圖

在使用的角度,程序員只需要關注兩件事

  1. 定義業務數據載體(領域消息實體
  2. 處理該業務數據載體的事件 (領域事件)

示例

自定義領域消息實體

  1. 定義領域實體 - 並實現 Eo 接口
  2. Eo 接口是框架提供, 領域實體類用戶隨意編寫, 建議以Eo結尾.
  3. 領域消息實體的對象字段隨意定製 (根據你的業務)
/**
 * 領域消息 - 學生
 * <pre>
 *     推薦定義領域事件實體類的時候都使用final
 *     避免某個領域事件對該實體進行數據修改
 * </pre>
 */
public record StudentEo(int id) implements Eo {

}

​ record 是值類型,好像是 java14 的出的(具體忘記的),轉爲java類的大概意思就是類中聲明瞭一個屬性 id,並自動提供 getter 方法。

定義領域事件

用於處理 StudentEo 領域消息實體

// 事件處理類,事件消費, 實現領域事件消費接口。一個事件消費類只處理一件事件(單一職責原則)
public final class StudentEmailEventHandler1 implements DomainEventHandler<StudentEo> {
    @Override
    public void onEvent(StudentEo studentEo, boolean endOfBatch) {
        log.debug("給這個學生髮送一個email消息: {}", studentEo);
    }
}

測試用例

public class StudentDomainEventTest {

    DomainEventContext domainEventContext;

    @After
    public void tearDown() throws Exception {
        // 事件消費完後 - 事件停止
        domainEventContext.stop();
    }

    @Before
    public void setUp() {
        // ======項目啓動時配置一次(初始化)======

        // 領域事件上下文參數
        DomainEventContextParam contextParam = new DomainEventContextParam();
        // 配置一個學生的領域事件消費 - 給學生髮生一封郵件
        contextParam.addEventHandler(new StudentEmailEventHandler1());
        // 配置一個學生的領域事件消費 - 回家
        // contextParam.addEventHandler(new StudentGoHomeEventHandler2());
        // 配置一個學生的領域事件消費 - 讓學生睡覺
        // contextParam.addEventHandler(new StudentSleepEventHandler3());

        // 啓動事件驅動
        domainEventContext = new DomainEventContext(contextParam);
        domainEventContext.startup();
    }

    @Test
    public void testEventSend() {
        // 這裏開始就是你的業務代碼
        StudentEo studentEo = new StudentEo(1);
        /*
         * 發送事件、上面只配置了一個事件。
         * 如果將來還需要給學生髮送一封email,那麼直接配置。(可擴展)
         * 如果將來還需要記錄學生今天上了什麼課程,那麼也是直接配置 (可擴展) 這裏的業務代碼無需任何改動(松耦合)
         * 如果將來又不需要給學生髮送email的事件了,直接刪除配置即可,這裏還是無需改動代碼。(高伸縮)
         */
        studentEo.send();
    }
}

​ 當使用上領域事件後,可以在不改變原有業務代碼,就可以添加將來新增的業務邏輯。如上面示例代碼中的

  • 如果將來還需要記錄學生今天上了什麼課程,那麼也是直接配置 (可擴展) 這裏的業務代碼無需任何改動(松耦合)
  • 如果將來又不需要給學生髮送email的事件了,直接刪除配置即可,這裏還是無需改動代碼。(高伸縮)

在回到開頭的【用戶註冊】處理完後的業務。我們只需要預留一個【用戶註冊】的Eo,將來如果有新增業務如:

  • 【郵件通知】業務
  • 【贈送積分】業務

各種 XXX 等新業務,我們也不需要改動【用戶註冊】這塊的代碼。只要單一職責原則,我們的代碼維護性會非常的高。

總結

​ 使用基於 disruptor 的領域驅動設計的領域事件,可以使得我們的代碼有如下好處:

  1. 單一職責原則,可以給系統的可擴展、高伸縮、低耦合達到極致。
  2. 異步高併發
  3. 業務在複雜也不會使得代碼混亂,維護代碼成本更低
  4. 插件形式提供事件領域,做到了可插拔,就像玩樂高積木般有趣。

源碼參考地址:light-domain-event 模塊

java 網絡遊戲服務器框架-文檔

java 網絡遊戲服務器框架-源碼

最後

本文可以轉載,但必須保留所有內容。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章