[從原始碼學設計]螞蟻金服SOFARegistry之訊息匯流排
0x00 摘要
SOFARegistry 是螞蟻金服開源的一個生產級、高時效、高可用的服務註冊中心。
本系列文章重點在於分析設計和架構,即利用多篇文章,從多個角度反推總結 DataServer 或者 SOFARegistry 的實現機制和架構思路,讓大家藉以學習阿里如何設計。
本文為第四篇,介紹SOFARegistry之訊息匯流排。
0x01 相關概念
1.1 事件驅動模型
事件驅動模型,也即是我們通常說的觀察者。基於釋出-訂閱模式的程式設計模型。
1.1.1 概念
定義物件間的一種一對多的依賴關係,當一個物件的狀態發生變化時,所有依賴它的物件都得到通知並自動更新。
從程式設計的角度來看,事件驅動模型的核心構件通常包含以下幾個:
- 事件源:負責產生事件的物件。比如我們常見的按鈕,按鈕就是一個事件源,能夠產生“點選”這個事件
- 事件監聽器(事件處理器):負責處理事件的物件
- 事件:或者稱為事件物件,是事件源和事件監聽器之間的資訊橋樑。是整個事件模型驅動的核心
1.1.2 應用環境
當我們面對如下的環境時,事件驅動模型通常是一個好的選擇:
- 程式中有許多工;
- 任務之間高度獨立(因此它們不需要互相通訊,或者等待彼此);
- 在等待事件到來時,某些任務會阻塞;
1.2 訊息匯流排
匯流排(Bus)一般指計算機各種功能部件之間傳送資訊的公共通訊幹線,而EventBus則是事件源(publisher)向訂閱方(subscriber)傳送訂閱事件的匯流排,它解耦了觀察者模式中訂閱方和事件源之間的強依賴關係。
訊息匯流排扮演著一種訊息路由的角色,擁有一套完備的路由機制來決定訊息傳輸方向。傳送端只需要向訊息匯流排發出訊息而不用管訊息被如何轉發,為了避免訊息丟失,部分訊息匯流排提供了一定的持久化儲存和災備的機制。
訊息匯流排簡單理解就是一個訊息中心,眾多微服務例項可以連線到匯流排上,例項可以往訊息中心傳送或接收資訊(通過監聽)。
一般的應用的場景就是在用觀察者模式的地方就可以用EventBus進行替代。
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 參考
螞蟻金服服務註冊中心如何實現 DataServer 平滑擴縮容
螞蟻金服服務註冊中心 SOFARegistry 解析 | 服務發現優化之路