[從原始碼學設計]螞蟻金服SOFARegistry之訊息匯流排

羅西的思考發表於2020-12-01

[從原始碼學設計]螞蟻金服SOFARegistry之訊息匯流排

0x00 摘要

SOFARegistry 是螞蟻金服開源的一個生產級、高時效、高可用的服務註冊中心。

本系列文章重點在於分析設計和架構,即利用多篇文章,從多個角度反推總結 DataServer 或者 SOFARegistry 的實現機制和架構思路,讓大家藉以學習阿里如何設計。

本文為第四篇,介紹SOFARegistry之訊息匯流排。

0x01 相關概念

1.1 事件驅動模型

事件驅動模型,也即是我們通常說的觀察者。基於釋出-訂閱模式的程式設計模型。

1.1.1 概念

定義物件間的一種一對多的依賴關係,當一個物件的狀態發生變化時,所有依賴它的物件都得到通知並自動更新。

從程式設計的角度來看,事件驅動模型的核心構件通常包含以下幾個:

  • 事件源:負責產生事件的物件。比如我們常見的按鈕,按鈕就是一個事件源,能夠產生“點選”這個事件
  • 事件監聽器(事件處理器):負責處理事件的物件
  • 事件:或者稱為事件物件,是事件源和事件監聽器之間的資訊橋樑。是整個事件模型驅動的核心

1.1.2 應用環境

當我們面對如下的環境時,事件驅動模型通常是一個好的選擇:

  • 程式中有許多工;
  • 任務之間高度獨立(因此它們不需要互相通訊,或者等待彼此);
  • 在等待事件到來時,某些任務會阻塞;

1.2 訊息匯流排

匯流排(Bus)一般指計算機各種功能部件之間傳送資訊的公共通訊幹線,而EventBus則是事件源(publisher)向訂閱方(subscriber)傳送訂閱事件的匯流排,它解耦了觀察者模式中訂閱方和事件源之間的強依賴關係

訊息匯流排扮演著一種訊息路由的角色,擁有一套完備的路由機制來決定訊息傳輸方向。傳送端只需要向訊息匯流排發出訊息而不用管訊息被如何轉發,為了避免訊息丟失,部分訊息匯流排提供了一定的持久化儲存和災備的機制

訊息匯流排簡單理解就是一個訊息中心,眾多微服務例項可以連線到匯流排上,例項可以往訊息中心傳送或接收資訊(通過監聽)。

一般的應用的場景就是在用觀察者模式的地方就可以用EventBus進行替代。

img

0x02 業務領域

2.1 業務範疇

DataServer 本質上是一個網路應用程式,所以有如下特點:

  • 需要處理各個方面傳送來的訊息;
  • 程式中任務繁多,任務之間獨立,大多數任務不存在互斥通訊等操作;在等待事件到來時,某些任務會阻塞;
  • 某一個訊息往往有多個投遞源;

因此天然適合用事件驅動機制來實現。

2.2 問題點

能夠想到的問題點如下:

  • 因為一個事件往往會有多個投遞源,如何解耦事件投遞和事件處理之間的邏輯?
  • 怎樣實現Listener一次註冊,就能夠知道Listener對那些事件感興趣的,進而在有某類事件發生時通知到Listener的呢?
  • 如何使得一個Listener可以處理多個事件?
  • 如何使得一個事件被多個Listener處理?
  • 可否簡化註冊流程?
  • 是否需要維護訊息順序?
  • 處理訊息方式是非同步還是同步?
  • 多個同樣訊息是否要歸併?

具體我們在後文會詳述阿里的思路。

2.3 解決方案

DataServer 內部邏輯主要是通過事件驅動機制來實現的,下圖列舉了部分事件在事件中心的互動流程,從圖中可以看到,一個事件往往會有多個投遞源,非常適合用 EventCenter 來解耦事件投遞和事件處理之間的邏輯;

0x03 EventCenter

業界訊息匯流排有很多,比如 Android EventBus是一個釋出/訂閱事件匯流排框架,基於觀察者模式,將事件的接收者和傳送者分開,簡化了元件之間的通訊。

而SOFARegistry EventCenter 的作用也類似:從邏輯上解耦,將事件的接收者和傳送者分開,簡化元件之間通訊。阿里的實現有自己的特點,開發者可以借鑑這裡的使用技巧和思路。

3.1 目錄結構

├── event
│   ├── AfterWorkingProcess.java
│   ├── DataServerChangeEvent.java
│   ├── Event.java
│   ├── EventCenter.java
│   ├── LocalDataServerChangeEvent.java
│   ├── MetaServerChangeEvent.java
│   ├── RemoteDataServerChangeEvent.java
│   ├── StartTaskEvent.java
│   ├── StartTaskTypeEnum.java
│   └── handler
│       ├── AbstractEventHandler.java
│       ├── AfterWorkingProcessHandler.java
│       ├── DataServerChangeEventHandler.java
│       ├── LocalDataServerChangeEventHandler.java
│       ├── MetaServerChangeEventHandler.java
│       └── StartTaskEventHandler.java

3.2 類定義

類定義如下:

public class EventCenter {

    private Multimap<Class<? extends Event>, AbstractEventHandler> MAP = ArrayListMultimap.create();

    /**
     * eventHandler register
     * @param handler
     */
    public void register(AbstractEventHandler handler) {
        List<Class<? extends Event>> interests = handler.interest();
        for (Class<? extends Event> interest : interests) {
            MAP.put(interest, handler);
        }
    }

    /**
     * event handler handle process
     * @param event
     */
    public void post(Event event) {
        Class clazz = event.getClass();
        if (MAP.containsKey(clazz)) {
            Collection<AbstractEventHandler> handlers = MAP.get(clazz);
            if (handlers != null) {
                for (AbstractEventHandler handler : handlers) {
                    handler.handle(event);
                }
            }
        } else {
            throw new RuntimeException("no suitable handler was found:" + clazz);
        }
    }
}

3.2.1 操作

普通 EventBus 大多有三個操作:

  • 註冊 Listener--register (Object Listener);
  • 登出 Listener--unregister (Object Listener);
  • 釋出 Event--post (Object event);

但是阿里的EventCenter並沒有登出操作,因為業務上不需要,所以只有如下介面。

  • register(AbstractEventHandler handler) 的工作就是找出這個Listener對哪些事件感興趣,然後把這種事件型別和對應的Listener註冊到 EventCenter;
  • post一個event時候,會遍歷這個訊息的處理函式列表,逐一呼叫處理函式,其實就是同步執行了,當然也許 EventHandler 內部自己實現了非同步;因為是同步執行,所以不需要維持訊息的有序性,否則需要使用queue來實現每個執行緒post的Event是有序的;

具體使用舉例如下:在MetaServerChangeEventHandler中有如下程式碼投放訊息。

eventCenter.post(new StartTaskEvent(set));

eventCenter.post(new DataServerChangeEvent(result.getNodes(), versionMap,
        DataServerChangeEvent.FromType.REGISTER_META));

3.2.2 執行 & 解耦

handler中宣告瞭自己支援什麼種類的event,當register時候,會以event為key,把自己註冊到eventCenter的map中,在 post 函式中,根據event的class,取出了handler,從而執行,也做到了解耦。

3.2.3 Listener列表

在觀察者模式中,事件源中會維護一個Listener的列表,而且向這個事件源註冊的Listener一般只會收到一類事件的通知,如果Listener對多個不同類的事件感興趣,則需要向多個事件源註冊。

EventCenter 是怎樣實現Listener一次註冊,能夠知道Listener對那些事件感興趣的,進而在有某類事件發生時通知到Listener的呢

答案在ArrayListMultimap,其key是Event,其 Value 就是 AbstractEventHandler。這個 map 就是 Event 事件型別 和對其感興趣的處理函式的列表,一個 Event 可能有多個處理函式。

3.2.4 ArrayListMultimap

顧名思義,com.google.common.collect.ArrayListMultimap 可以在key對應的value中設定一個ArrayList。這樣就保證了一個事件可以有多個處理函式

具體可以見下例子。

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;

import java.util.Collection;

public class testArrayListMultimap {
     static void main() {
        Multimap<String, String> multimap = ArrayListMultimap.create();
        multimap.put("fruit", "banana");
        multimap.put("fruit", "apple");
        multimap.put("fruit", "apple");
        multimap.put("fruit", "peach");
        multimap.put("fish","crucian");
        multimap.put("fish","carp");

        System.err.println(multimap.size());//6
        Collection<String> fruits = multimap.get("fruit");
        System.err.println(fruits);//[bannana, apple, apple, peach]
    }
}

3.3 Listener

Listener 是由 AbstractEventHandler 的派生類實現的。

3.3.1 基類

EventHandler基類AbstractEventHandler定義具體如下:

public abstract class AbstractEventHandler<Event> implements InitializingBean {

    @Autowired
    private EventCenter         eventCenter;

    @Override
    public void afterPropertiesSet() throws Exception {
        eventCenter.register(this);
    }

    /**
     * event handle func
     * @param event
     */
    public void handle(Event event) {
            doHandle(event);
    }

    public abstract List<Class<? extends Event>> interest();

    public abstract void doHandle(Event event);
}

其主要作用為三點:

  • 派生類必須實現interest來宣告自己想處理什麼Event,而且Event是配置在一個陣列中,這樣就使得一個函式可以處理多個事件
@Override
public List<Class<? extends LocalDataServerChangeEvent>> interest() {
    return Lists.newArrayList(LocalDataServerChangeEvent.class);
}
  • 派生類實現doHandle來處理訊息;

  • 因為afterPropertiesSet中做了設定,所以每一個繼承此類的Handler都會自動註冊到EventCenter之中

3.3.2 派生類

以MetaServerChangeEventHandler為例,只要在interest函式中宣告自己對哪些訊息感興趣,在doHandle函式中實現業務即可。

public class MetaServerChangeEventHandler extends AbstractEventHandler<MetaServerChangeEvent> {
  
    @Override
    public List<Class<? extends MetaServerChangeEvent>> interest() {
        return Lists.newArrayList(MetaServerChangeEvent.class);
    }
  
		@Override
    public void doHandle(MetaServerChangeEvent event) {
       ......
    }
}

3.3.2 自動註冊

這裡需要專門說一下自動註冊,因為初接觸者很容易疏漏從而感到奇怪。

自動註冊使用的是Spring的afterPropertiesSet方法完成

afterPropertiesSet方法可以針對某個具體的bean進行配置,其將在Bean所有的屬性被初始化後呼叫,但是會在init前呼叫。afterPropertiesSet 必須實現 InitializingBean介面。

package org.springframework.beans.factory;

public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

基類AbstractEventHandler實現InitializingBean介面。

public abstract class AbstractEventHandler<Event> implements InitializingBean

而每一個派生類就註冊了派生類本身到eventCenter。

@Override
public void afterPropertiesSet() throws Exception {
    eventCenter.register(this);
}

3.4 核心訊息

具體涉及到業務,EventCenter主要處理三種訊息:

  • DataServerChangeEvent,是其他Data Server的節點變化訊息;
  • MetaServerChangeEvent,是Meta Sever的變化訊息;
  • StartTaskEvent:;

分別對應三個訊息處理handler:

  • public class DataServerChangeEventHandler extends AbstractEventHandler

  • public class MetaServerChangeEventHandler extends AbstractEventHandler

  • public class StartTaskEventHandler extends AbstractEventHandler

我們用 StartTaskEvent 舉例,具體訊息內容根據具體業務設定。

public class StartTaskEvent implements Event {
    private final Set<StartTaskTypeEnum> suitableTypes;

    public StartTaskEvent(Set<StartTaskTypeEnum> suitableTypes) {
        this.suitableTypes = suitableTypes;
    }

    public Set<StartTaskTypeEnum> getSuitableTypes() {
        return suitableTypes;
    }
}

3.5 主要邏輯

EventCenter主要邏輯如下圖所示:

          +------------------------------+
          | MetaServerChangeEventHandler |
          +-----------+------------------+
                      |
                      |  post(new StartTaskEvent)
                      |
                      |
                      |                                      +------------------------+
                      v                                      |  StartTaskEventHandler |
+---------------------+-----------------------+              |                        |
|                EventCenter                  |              | +--------------------+ |
|                                             |              | |                    | |
| +-----------------------------------------+ +---------------------> doHandle      | |
| |Multimap< <Event>, AbstractEventHandler> | |              | |                    | |
| +-----------------------------------------+ | <--------------+ afterPropertiesSet | |
|                                             |  register    | |                    | |
+---------------------------------------------+              | |      interest      | |
                                                             | |                    | |
                                                             | +--------------------+ |
                                                             +------------------------+

手機如下圖:

0x04 總結

SOFARegistry EventCenter 的作用與業界大多匯流排類似:從邏輯上解耦,將事件的接收者和傳送者分開,簡化元件之間通訊。但是阿里的實現有自己的特點,開發者可以借鑑這裡的使用技巧和思路。

針對我們前面提出的問題,現在回答如下:

  • 因為一個事件往往會有多個投遞源,如何解耦事件投遞和事件處理之間的邏輯?
    • 答案:handler中宣告瞭自己支援什麼種類的event,當register時候會以event為key,把自己註冊到eventCenter的map中;在 post 函式中,根據event的class,取出了handler從而執行,也做到了解耦。
  • 怎樣實現Listener一次註冊,就能夠知道Listener對那些事件感興趣的,進而在有某類事件發生時通知到Listener的呢?
    • 答案:派生類必須實現interest來宣告自己想處理什麼Event;
  • 如何使得一個Listener可以處理多個事件?
    • 答案:接上問題,Event是配置在一個陣列中,這樣就使得一個函式可以處理多個事件
  • 如何使得一個事件被多個Listener處理?
    • 答案:採用ArrayListMultimap實現listener列表;
  • 可否簡化註冊流程?
    • 答案:自動註冊,派生類不需要操心。afterPropertiesSet中做了設定,所以每一個繼承此類的Handler都會自動註冊到EventCenter之中
  • 是否需要維護訊息順序?
    • 答案:不需要,因為是同步處理;
  • 處理訊息方式是非同步還是同步?
    • 答案:這裡是同步;
  • 多個同樣訊息是否要歸併?
    • 答案:這裡不需要歸併,沒有業務需求;

0xFF 參考

Guava中EventBus分析

螞蟻金服服務註冊中心如何實現 DataServer 平滑擴縮容

螞蟻金服服務註冊中心 SOFARegistry 解析 | 服務發現優化之路

服務註冊中心 Session 儲存策略 | SOFARegistry 解析

海量資料下的註冊中心 - SOFARegistry 架構介紹

相關文章