Event Bus是Vert.x的“神經系統”,是最爲關鍵的幾個部分之一。今天我們就來探索一下Event Bus的實現原理。本篇分析的是Local模式的Event Bus,對應的Vert.x版本爲3.3.2。
本文假定讀者有一定的併發編程基礎以及Vert.x使用基礎,並且對Vert.x的線程模型以及back-pressure有所瞭解。
Local Event Bus的創建
一般情況下,我們通過Vertx
的eventBus
方法來創建或獲取一個EventBus
實例:
|
|
eventBus
方法定義於Vertx
接口中,我們來看一下其位於VertxImpl
類中的實現:
|
|
可以看到此方法返回VertxImpl
實例中的eventBus
成員,同時需要注意併發可見性問題。那麼eventBus
成員是何時初始化的呢?答案在VertxImpl
類的構造函數中。這裏截取相關邏輯:
|
|
可以看到VertxImpl
內部是通過createAndStartEventBus
方法來初始化eventBus
的。我們來看一下其邏輯:
|
|
可以看到此方法通過eventBus
= new EventBusImpl(this)
將eventBus
進行了初始化(Local模式爲EventBusImpl
),並且調用eventBus
的start
方法對其進行一些額外的初始化工作。我們來看一下EventBusImpl
類的start
方法:
|
|
首先初始化過程需要防止race condition,因此方法爲synchronized
的。該方法僅僅將EventBusImpl
類中的一個started
標誌位設爲true
來代表Event
Bus已啓動。注意started
標誌位爲volatile
的,這樣可以保證其可見性,確保其它線程通過checkStarted
方法讀到的started
結果總是最新的。設置完started
標誌位後,Vert.x會接着調用傳入的completionHandler
處理函數,也就是上面我們在createAndStartEventBus
方法中看到的
—— 調用metrics
成員的eventBusInitialized
方法以便Metrics類可以在Event
Bus初始化完畢後使用它(不過默認情況下此方法的邏輯爲空)。
可以看到初始化過程還是比較簡單的,我們接下來先來看看訂閱消息 —— consumer
方法的邏輯。
consume
我們來看一下consumer
方法的邏輯,其原型位於EventBus
接口中:
|
|
其實現位於EventBusImpl
類中:
|
|
首先要確保傳入的handler
不爲空,然後Vert.x會調用只接受一個address
參數的consumer
方法獲取對應的MessageConsumer
,最後給獲取到的MessageConsumer
綁定上傳入的handler
。我們首先來看一下另一個consumer
方法的實現:
|
|
首先Vert.x會檢查Event Bus是否已經啓動,並且確保傳入的地址不爲空。然後Vert.x會傳入一大堆參數創建一個新的HandlerRegistration
類型的實例,並返回。可以推測HandlerRegistration
是MessageConsumer
接口的具體實現,它一定非常重要。所以我們來看一看HandlerRegistration
類是個啥玩意。首先看一下HandlerRegistration
的類體系結構:
可以看到HandlerRegistration
類同時繼承了MessageConsumer<T>
以及Handler<Message<T>>
接口,從其類名可以看出它相當於一個”Handler註冊記錄”,是非常重要的一個類。它有一堆的成員變量,構造函數對vertx
, metrics
, eventBus
, address
(發送地址), repliedAddress
(回覆地址), localOnly
(是否在集羣內傳播), asyncResultHandler
等幾個成員變量進行初始化,並且檢查超時時間timeout
,如果設定了超時時間那麼設定並保存超時計時器(僅用於reply
handler中),如果計時器時間到,代表回覆超時。因爲有一些函數還沒介紹,超時的邏輯我們後邊再講。
Note: 由於
MessageConsumer
接口繼承了ReadStream
接口,因此它支持back-pressure,其實現就在HandlerRegistration
類中。我們將稍後解析back-pressure的實現。
現在回到consumer
方法中來。創建了MessageConsumer
實例後,我們接着調用它的handler
方法綁定上對應的消息處理函數。handler
方法的實現位於HandlerRegistration
類中:
|
|
首先,handler
方法將此HandlerRegistration
中的handler
成員設置爲傳入的消息處理函數。HandlerRegistration
類中有一個registered
標誌位代表是否已綁定消息處理函數。handler
方法會檢查傳入的handler
是否爲空且是否已綁定消息處理函數。如果不爲空且未綁定,Vert.x就會將registered
標誌位設爲true
並且調用eventBus
的addRegistration
方法將此consumer註冊至Event
Bus上;如果handler
爲空且已綁定消息處理函數,我們就調用unregister
方法註銷當前的consumer。我們稍後會分析unregister
方法的實現。
前面提到過註冊consumer的邏輯位於Event Bus的addRegistration
方法中,因此我們來分析一下它的實現:
|
|
addRegistration
方法接受四個參數:發送地址address
、傳入的consumer registration
、代表是否爲reply
handler的標誌位replyHandler
以及代表是否在集羣範圍內傳播的標誌位localOnly
。首先確保傳入的HandlerRegistration
不爲空。然後Vert.x會調用addLocalRegistration
方法將此consumer註冊至Event
Bus上:
|
|
首先該方法要確保地址address
不爲空,接着它會獲取當前線程下對應的Vert.x
Context,如果獲取不到則表明當前不在Verticle
中(即Embedded),需要調用vertx.getOrCreateContext()
來獲取Context
;然後將獲取到的Context
賦值給registration
內部的handlerContext
(代表消息處理對應的Vert.x
Context)。
下面就要將給定的registration
註冊至Event
Bus上了。這裏Vert.x用一個HandlerHolder
類來包裝registration
和context
。接着Vert.x會從存儲消息處理Handler
的哈希表handlerMap
中獲取給定地址對應的Handlers
,哈希表的類型爲ConcurrentMap<String,
Handlers>
,key爲地址,value爲對應的HandlerHolder
集合。注意這裏的Handlers
類代表一些Handler
的集合,它內部維護着一個列表list
用於存儲每個HandlerHolder
。Handlers
類中只有一個choose
函數,此函數根據輪詢算法從HandlerHolder
集合中選定一個HandlerHolder
,這即是Event
Bus發送消息時實現load-balancing的方法:
|
|
獲取到對應的handlers
以後,Vert.x首先需要檢查其是否爲空,如果爲空代表此地址還沒有註冊消息處理Handler
,Vert.x就會創建一個Handlers
並且將其置入handlerMap
中,將newAddress
標誌位設爲true
代表這是一個新註冊的地址,然後將其賦值給handlers
。接着我們向handlers
中的HandlerHolder
列表list
中添加剛剛創建的HandlerHolder
實例,這樣就將registration
註冊至Event
Bus中了。
前面判斷當前線程是否在Vert.x Context的標誌位hasContext
還有一個用途:如果當前線程在Vert.x
Context下(比如在Verticle中),Vert.x會通過addCloseHook
方法給當前的context
添加一個鉤子函數用於註銷當前綁定的registration
。當對應的Verticle
被undeploy的時候,此Verticle綁定的所有消息處理Handler
都會被unregister。Hook的類型爲HandlerEntry<T>
,它繼承了Closeable
接口,對應的邏輯在close
函數中實現:
|
|
可以看到close
函數會將綁定的registration
從Event
Bus的handlerMap
中移除並執行completionHandler
中的邏輯,completionHandler
可由用戶指定。
那麼在哪裏調用這些綁定的hook呢?答案是在DeploymentManager
類中的doUndeploy
方法中,通過context
的runCloseHooks
方法執行綁定的hook函數。相關代碼如下(只截取相關邏輯):
|
|
再回到addRegistration
方法中。剛纔addLocalRegistration
方法的返回值newAddress
代表對應的地址是否爲新註冊的。接着我們調用另一個版本的addRegistration
方法,傳入了一大堆參數:
|
|
好吧,傳入的前幾個參數沒用到。。。最後一個參數completionHandler
傳入的是registration::setResult
方法引用,也就是說這個方法調用了對應registration
的setResult
方法。其實現位於HandlerRegistration
類中:
|
|
首先先設置registration
內部的result
成員(正常情況下爲Future.succeededFuture()
)。接着Vert.x會判斷registration
是否綁定了completionHandler
(與之前的completionHandler
不同,這裏的completionHandler
是MessageConsumer
註冊成功時調用的Handler
),若綁定則記錄Metrics信息(handlerRegistered
)並在Vert.x
Context內調用completionHandler
的邏輯;若未綁定completionHandler
則僅記錄Metrics信息。
到此爲止,consumer
方法的邏輯就分析完了。在分析send
和publish
方法的邏輯之前,我們先來看一下如何註銷綁定的MessageConsumer
。
unregister
我們通過調用MessageConsumer
的unregister
方法實現註銷操作。Vert.x提供了兩個版本的unregister
方法:
|
|
其中第二個版本的unregister
方法會在註銷操作完成時調用傳入的completionHandler
。比如在cluster範圍內註銷consumer需要消耗一定的時間在集羣內傳播,因此第二個版本的方法就會派上用場。我們來看一下其實現,它們最後都是調用了HandlerRegistration
類的doUnregister
方法:
|
|
如果設定了超時定時器(timeoutID
合法),那麼Vert.x會首先將定時器關閉。接着Vert.x會判斷是否需要調用endHandler
。那麼endHandler
又是什麼呢?前面我們提到過MessageConsumer
接口繼承了ReadStream
接口,而ReadStream
接口定義了一個endHandler
方法用於綁定一個endHandler
,當stream中的數據讀取完畢時會調用。而在Event
Bus中,消息源源不斷地從一處發送至另一處,因此只有在某個consumer
被unregister的時候,其對應的stream纔可以叫“讀取完畢”,因此Vert.x選擇在doUnregister
方法中調用endHandler
。
接着Vert.x會判斷此consumer是否已註冊消息處理函數Handler
(通過檢查registered
標誌位),若已註冊則將對應的Handler
從Event
Bus中的handlerMap
中移除並將registered
設爲false
;若還未註冊Handler
且提供了註銷結束時的回調completionHandler
(注意不是HandlerRegistration
類的成員變量completionHandler
,而是之前第二個版本的unregister
中傳入的Handler
,用同樣的名字很容易混。。。),則通過callCompletionHandlerAsync
方法調用回調函數。
從Event Bus中移除Handler
的邏輯位於EventBusImpl
類的removeRegistration
方法中:
|
|
其真正的unregister
邏輯位於removeLocalRegistration
方法中。首先需要從handlerMap
中獲取地址對應的Handlers
實例handlers
,如果handlers
不爲空,爲了防止併發問題,Vert.x需要對其加鎖後再進行操作。Vert.x需要遍歷handlers
中的列表,遇到與傳入的HandlerRegistration
相匹配的HandlerHolder
就將其從列表中移除,然後調用對應holder
的setRemoved
方法標記其爲已註銷並記錄Metrics數據(handlerUnregistered
)。如果移除此HandlerHolder
後handlers
沒有任何註冊的Handler
了,就將該地址對應的Handlers
實例從handlerMap
中移除並保存剛剛移除的HandlerHolder
。另外,由於已經將此consumer註銷,在undeploy
verticle的時候不需要再進行unregister,因此這裏還要將之前註冊到context的hook移除。
調用完removeLocalRegistration
方法以後,Vert.x會調用另一個版本的removeRegistration
方法,調用completionHandler
(用戶在第二個版本的unregister
方法中傳入的處理函數)對應的邏輯,其它的參數都沒什麼用。。。
這就是MessageConsumer
註銷的邏輯實現。下面就到了本文的另一重頭戲了
—— 發送消息相關的函數send
和publish
。
send & publish
send
和publish
的邏輯相近,只不過一個是發送至目標地址的某一消費者,一個是發佈至目標地址的所有消費者。Vert.x使用一個標誌位send
來代表是否爲點對點發送模式。
幾個版本的send
和publish
最終都歸結於生成消息對象然後調用sendOrPubInternal
方法執行邏輯,只不過send
標誌位不同:
|
|
兩個方法中都是通過createMessage
方法來生成對應的消息對象的:
|
|
createMessage
方法接受5個參數:send
即上面提到的標誌位,address
爲發送目標地址,headers
爲設置的header,body
代表發送的對象,codecName
代表對應的Codec(消息編碼解碼器)名稱。createMessage
方法首先會確保地址不爲空,然後通過codecManager
來獲取對應的MessageCodec
。如果沒有提供Codec(即codecName
爲空),那麼codecManager
會根據發送對象body
的類型來提供內置的Codec實現(具體邏輯請見此處)。準備好MessageCodec
後,createMessage
方法就會創建一個MessageImpl
實例並且返回。
這裏我們還需要了解一下MessageImpl
的構造函數:
|
|
createMessage
方法並沒有設置回覆地址replyAddress
。如果用戶指定了replyHandler
的話,後邊sendOrPubInternal
方法會對此消息實體進行加工,設置replyAddress
並生成回覆邏輯對應的HandlerRegistration
。
我們看一下sendOrPubInternal
方法的源碼:
|
|
它接受三個參數:要發送的消息message
,發送配置選項options
以及回覆處理函數replyHandler
。首先sendOrPubInternal
方法要檢查Event
Bus是否已啓動,接着如果綁定了回覆處理函數,Vert.x就會調用createReplyHandlerRegistration
方法給消息實體message
包裝上回復地址,並且生成對應的reply
consumer。接着Vert.x創建了一個包裝消息的SendContextImpl
實例並調用了其next
方法。
我們一步一步來解釋。首先是createReplyHandlerRegistration
方法:
|
|
createReplyHandlerRegistration
方法首先檢查傳入的replyHandler
是否爲空(是否綁定了replyHandler
,回覆處理函數),如果爲空則代表不需要處理回覆,直接返回null
;若replyHandler
不爲空,createReplyHandlerRegistration
方法就會從配置中獲取reply的最大超時時長(默認30s),然後調用generateReplyAddress
方法生成對應的回覆地址replyAddress
:
|
|
生成回覆地址的邏輯有點簡單。。。。EventBusImpl
實例中維護着一個AtomicLong
類型的replySequence
成員變量代表對應的回覆地址。每次生成的時候都會使其自增,然後轉化爲String。也就是說生成的replyAddress
都類似於”1”、”5”這樣,而不是我們想象中的直接回復至sender的地址。。。
生成完畢以後,createReplyHandlerRegistration
方法會將生成的replyAddress
設定到消息對象message
中。接着Vert.x會通過convertHandler
方法對replyHandler
進行包裝處理並生成類型簡化爲Handler<Message<T>>
的simpleReplyHandler
,它用於綁定至後面創建的reply
consumer上。接着Vert.x會創建對應的reply consumer。關於reply
操作的實現,我們後邊會詳細講述。下面Vert.x就通過handler
方法將生成的回覆處理函數simpleReplyHandler
綁定至創建好的reply
consumer中,其底層實現我們之前已經分析過了,這裏就不再贅述。最後此方法返回生成的registration
,即對應的reply
consumer。注意這個reply consumer是一次性的,也就是說Vert.x會在其接收到回覆或超時的時候自動對其進行註銷。
OK,現在回到sendOrPubInternal
方法中來。下面Vert.x會創建一個SendContextImpl
實例並調用其next
方法。SendContextImpl
類實現了SendContext
接口,它相當於一個消息的封裝體,並且可以與Event
Bus中的interceptors
(攔截器)結合使用。
SendContext
接口定義了三個方法:
message
: 獲取當前SendContext
包裝的消息實體next
: 調用下一個消息攔截器send
: 代表消息的發送模式是否爲點對點模式
在Event Bus中,消息攔截器本質上是一個Handler<SendContext>
類型的處理函數。Event
Bus內部存儲着一個interceptors
列表用於存儲綁定的消息攔截器。我們可以通過addInterceptor
和removeInterceptor
方法進行消息攔截器的添加和刪除操作。如果要進行鏈式攔截,則在每個攔截器中都應該調用對應SendContext
的next
方法,比如:
|
|
我們來看一下SendContextImpl
類中next
方法的實現:
|
|
我們可以看到,SendContextImpl
類中維護了一個攔截器列表對應的迭代器。每次調用next
方法時,如果迭代器中存在攔截器,就將下個攔截器取出並進行相關調用。如果迭代器爲空,則代表攔截器都已經調用完畢,Vert.x就會調用EventBusImpl
類下的sendOrPub
方法進行消息的發送操作。
sendOrPub
方法僅僅在metrics模塊中記錄相關數據(messageSent
),最後調用deliverMessageLocally(SendContextImpl<T>)
方法執行消息的發送邏輯:
|
|
這裏面又套了一層。。。它最後其實是調用了deliverMessageLocally(MessageImpl)
方法。此方法返回值代表發送消息的目標地址是否註冊有MessageConsumer
,如果沒有(false
)則記錄錯誤並調用sendContext
中保存的回覆處理函數處理錯誤(如果綁定了replyHandler
的話)。
deliverMessageLocally(MessageImpl)
方法是真正區分send
和publish
的地方,我們來看一下其實現:
|
|
首先Vert.x需要從handlerMap
中獲取目標地址對應的處理函數集合handlers
。接着,如果handlers
不爲空的話,Vert.x就會判斷消息實體的send
標誌位。如果send
標誌位爲true
則代表以點對點模式發送,Vert.x就會通過handlers
的choose
方法(之前提到過),按照輪詢算法來獲取其中的某一個HandlerHolder
。獲取到HandlerHolder
之後,Vert.x會通過deliverToHandler
方法將消息分發至HandlerHolder
中進行處理;如果send
標誌位爲false
則代表向所有消費者發佈消息,Vert.x就會對handlers
中的每一個HandlerHolder
依次調用deliverToHandler
方法,以便將消息分發至所有註冊到此地址的Handler
中進行處理。
消息處理的真正邏輯就在deliverToHandler
方法中。我們來看一下它的源碼:
|
|
首先deliverToHandler
方法會複製一份要發送的消息,然後deliverToHandler
方法會調用metrics
的scheduleMessage
方法記錄對應的Metrics信息(計劃對消息進行處理。此函數修復了Issue
1480)。接着deliverToHandler
方法會從傳入的HandlerHolder
中獲取對應的Vert.x
Context,然後調用runOnContext
方法以便可以讓消息處理邏輯在Vert.x
Context中執行。爲防止對應的handler在處理之前被移除,這裏還需要檢查一下holder
的isRemoved
屬性。如果沒有移除,那麼就從holder
中獲取對應的handler
並調用其handle
方法執行消息的處理邏輯。注意這裏獲取的handler
實際上是一個HandlerRegistration
。前面提到過HandlerRegistration
類同時實現了MessageConsumer
接口和Handler
接口,因此它兼具這兩個接口所期望的功能。另外,之前我們提到過Vert.x會自動註銷接收過回覆的reply
consumer,其邏輯就在這個finally塊中。Vert.x會檢查holder
中的handler
是否爲reply
handler(reply consumer),如果是的話就調用其unregister
方法將其註銷,來確保reply
consumer爲一次性的。
之前我們提到過MessageConsumer
繼承了ReadStream
接口,因此HandlerRegistration
需要實現flow
control(back-pressure)的相關邏輯。那麼如何實現呢?我們看到,HandlerRegistration
類中有一個paused
標誌位代表是否還繼續處理消息。ReadStream
接口中定義了兩個函數用於控制stream的通斷:當處理速度小於讀取速度(發生擁塞)的時候我們可以通過pause
方法暫停消息的傳遞,將積壓的消息暫存於內部的消息隊列(緩衝區)pending
中;當相對速度正常的時候,我們可以通過resume
方法恢復消息的傳遞和處理。
我們看一下HandlerRegistration
中handle
方法的實現:
|
|
果然。。。handle
方法在處理消息的基礎上實現了擁塞控制的功能。爲了防止資源爭用,需要對自身進行加鎖;首先handle
方法會判斷當前的consumer
是否爲paused
狀態,如果爲paused
狀態,handle
方法會檢查當前緩衝區大小是否已經超過給定的最大緩衝區大小maxBufferedMessages
,如果沒超過,就將收到的消息push到緩衝區中;如果大於或等於閾值,Vert.x就需要丟棄超出的那部分消息。如果當前的consumer
爲正常狀態,則如果緩衝區不爲空,就將收到的消息push到緩衝區中並從緩衝區中pull隊列首端的消息,然後調用deliver
方法執行真正的消息處理邏輯。注意這裏是在鎖之外執行deliver
方法的,這是爲了保證在multithreaded
worker context下可以併發傳遞消息(見Bug
473714 )。由於multithreaded worker context允許在不同線程併發執行邏輯(見官方文檔),如果將deliver
方法置於synchronized
塊之內,其他線程必須等待當前鎖被釋放才能進行消息的傳遞邏輯,因而不能做到“delivery
concurrently”。
deliver
方法是真正執行“消息處理”邏輯的地方:
|
|
首先Vert.x會調用checkNextTick
方法來檢查消息隊列(緩衝區)中是否存在更多的消息等待被處理,如果有的話就取出隊列首端的消息並調用deliver
方法將其傳遞給handler
進行處理。這裏仍需要注意併發問題,相關實現:
|
|
檢查完消息隊列以後,Vert.x會接着根據message
判斷消息是否僅在本地進行處理並給local
標誌位賦值,local
標誌位將在記錄Metrics數據時用到。
接下來我們看到Vert.x從消息的headers
中獲取了一個地址creditsAddress
,如果creditsAddress
存在就向此地址發送一條消息,body爲1
。那麼這個creditsAddress
又是啥呢?其實,它與flow
control有關,我們會在下面詳細分析。發送完credit
消息以後,接下來就到了調用handler
處理消息的時刻了。在處理消息之前需要調用metrics
的beginHandleMessage
方法記錄消息開始處理的metrics數據,在處理完消息以後需要調用endHandleMessage
方法記錄消息處理結束的metrics數據。
嗯。。。到此爲止,消息的發送和處理過程就已經一目瞭然了。下面我們講一講之前代碼中出現的creditsAddress
到底是啥玩意~
MessageProducer
之前我們提到過,Vert.x定義了兩個接口作爲 flow control aware object 的規範:WriteStream
以及ReadStream
。對於ReadStream
我們已經不再陌生了,MessageConsumer
就繼承了它;那麼大家應該可以想象到,有MessageConsumer
就必有MessageProducer
。不錯,Vert.x中的MessageProducer
接口對應某個address
上的消息生產者,同時它繼承了WriteStream
接口,因此MessageProducer
的實現類MessageProducerImpl
同樣具有flow
control的能力。我們可以把MessageProducer
看做是一個具有flow
control功能的增強版的EventBus
。我們可以通過EventBus
接口的publisher
方法創建一個MessageProducer
。
對MessageProducer
有了初步瞭解之後,我們就可以解釋前面deliver
方法中的creditsAddress
了。MessageProducer
接口的實現類
—— MessageProducerImpl
類的流量控制功能是基於credit
的,其內部會維護一個credit
值代表“發送消息的能力”,其默認值等於DEFAULT_WRITE_QUEUE_MAX_SIZE
:
|
|
在採用點對點模式發送消息的時候,MessageProducer
底層會調用doSend
方法進行消息的發送。發送依然利用Event
Bus的send
方法,只不過doSend
方法中添加了flow
control的相關邏輯:
|
|
與MessageConsumer
類似,MessageProducer
內部同樣保存着一個消息隊列(緩衝區)用於暫存堆積的消息。當credits
大於0的時候代表可以發送消息(沒有出現擁塞),Vert.x就會調用Event
Bus的send
方法進行消息的發送,同時credits
要減1;如果credits
小於等於0,則代表此時消息發送的速度太快,出現了擁塞,需要暫緩發送,因此將要發送的對象暫存於緩衝區中。大家可能會問,credits
值不斷減小,那麼恢復消息發送能力(增大credits
)的邏輯在哪呢?這就要提到creditsAddress
了。我們看一下MessageProducerImpl
類的構造函數:
|
|
MessageProducerImpl
的構造函數中生成了一個creditAddress
,然後給該地址綁定了一個Handler
,當收到消息時調用doReceiveCredit
方法執行解除擁塞,恢復消息發送的邏輯。MessageProducerImpl
會將此MessageConsumer
保存,以便在關閉消息生產者流的時候將其註銷。接着構造函數會往options
的headers
中添加一條記錄,保存對應的creditAddress
,這也就是上面我們在deliver
函數中獲取的creditAddress
:
|
|
這樣,發送消息到creditsAddress
的邏輯也就好理解了。由於deliver
函數的邏輯就是處理消息,因此這裏向creditsAddress
發送一個 1 其實就是將對應的credits
值加1。恢復消息發送的邏輯位於MessageProducerImpl
類的doReceiveCredit
方法中:
|
|
邏輯一目瞭然。首先給credits
加上發送過來的值(正常情況下爲1),然後恢復發送能力,將緩衝區的數據依次取出、發送然後減小credits
。同時如果MessageProducer
綁定了drainHandler
(消息流不擁塞的時候調用的邏輯,詳見官方文檔),並且MessageProducer
發送的消息不再擁塞(credits
>= maxSize / 2
),那麼就在Vert.x Context中執行drainHandler
中的邏輯。
怎麼樣,體會到Vert.x中flow control的強大之處了吧!官方文檔中MessageProducer
的篇幅幾乎沒有,只在介紹WriteStream
的時候提了提,因此這部分也可以作爲MessageProducer
的參考。
reply
最後就是消息的回覆邏輯 —— reply
方法了。reply
方法的實現位於MessageImpl
類中,最終調用的是reply(Object,
DeliveryOptions, Handler<AsyncResult<Message<R>>>)
這個版本:
|
|
這裏reply
方法同樣調用EventBus
的createMessage
方法創建要回復的消息實體,傳入的replyAddress
即爲之前講過的生成的非常簡單的回覆地址。然後再將消息實體、配置以及對應的replyHandler
(如果有的話)傳入sendReply
方法進行消息的回覆。最後其實是調用了Event
Bus中的四個參數的sendReply
方法,它的邏輯與之前講過的sendOrPubInternal
非常相似:
|
|
參數中replyMessage
代表回覆消息實體,replierMessage
則代表回覆者自身的消息實體(sender)。
如果地址爲空則拋出異常;如果地址不爲空,則先調用createReplyHandlerRegistration
方法創建對應的replyHandlerRegistration
。createReplyHandlerRegistration
方法的實現之前已經講過了。注意這裏的createReplyHandlerRegistration
其實對應的是此replier的回覆,因爲Vert.x中的 Request-Response 消息模型不限制相互回覆(通信)的次數。當然如果沒有指定此replier的回覆的replyHandler
,那麼此處的replyHandlerRegistration
就爲空。最後sendReply
方法會創建一個ReplySendContextImpl
並調用其next
方法。
ReplySendContextImpl
類同樣是SendContext
接口的一個實現(繼承了SendContextImpl
類)。ReplySendContextImpl
比起其父類就多保存了一個replierMessage
。next
方法的邏輯與父類邏輯非常相似,只不過將回復的邏輯替換成了另一個版本的sendReply
方法:
|
|
然而。。。sendReply
方法並沒有用到傳入的replierMessage
,所以這裏最終還是調用了sendOrPub
方法(尼瑪,封裝的ReplySendContextImpl
貌似並沒有什麼卵用,可能爲以後的擴展考慮?)。。。之後的邏輯我們都已經分析過了。
這裏再強調一點。當我們發送消息同時指定replyHandler
的時候,其內部爲reply創建的reply
consumer(類型爲HandlerRegistration
)指定了timeout
。這個定時器從HandlerRegistration
創建的時候就開始計時了。我們回顧一下:
|
|
計時器會在超時的時候記錄錯誤並強制註銷當前consumer。由於reply consumer是一次性的,當收到reply的時候,Vert.x會自動對reply consumer調用unregister
方法對其進行註銷(實現位於EventBusImpl#deliverToHandler
方法中),而在註銷邏輯中會關閉定時器(參見前面對doUnregister
方法的解析);如果超時,那麼計時器就會觸發,Vert.x會調用sendAsyncResultFailure
方法註銷當前reply
consumer並處理錯誤。
synchronized的性能問題
大家可能看到爲了防止race condition,Vert.x底層大量使用了synchronized
關鍵字(重量級鎖)。這會不會影響性能呢?其實,如果開發者遵循Vert.x的線程模型和開發規範(使用Verticle)的話,有些地方的synchronized
對應的鎖會被優化爲 偏向鎖 或 輕量級鎖(因爲通常都是同一個Event
Loop線程獲取對應的鎖),這樣性能總體開銷不會太大。當然如果使用Multi-threaded worker verticles就要格外關注性能問題了。。。
總結
我們來簡略地總結一下Event Bus的工作原理。當我們調用consumer
綁定一個MessageConsumer
時,Vert.x會將它保存至Event
Bus實例內部的Map中;當我們通過send
或publish
向對應的地址發送消息的時候,Vert.x會遍歷Event
Bus中存儲consumer的Map,獲取與地址相對應的consumer集合,然後根據相應的策略傳遞並處理消息(send
通過輪詢策略獲取任意一個consumer並將消息傳遞至consumer中,publish
則會將消息傳遞至所有註冊到對應地址的consumer中)。同時,MessageConsumer
和MessageProducer
這兩個接口的實現都具有flow
control功能,因此它們也可以用在Pump
中。
Event Bus是Vert.x中最爲重要的一部分之一,探索Event Bus的源碼可以加深我們對Event Bus工作原理的理解。作爲開發者,只會使用框架是不夠的,能夠理解內部的實現原理和精華,並對其進行改進纔是更爲重要的。本篇文章分析的是Local模式下的Event Bus,下篇文章我們將來探索一下生產環境中更常用的 Clustered Event Bus 的實現原理,敬請期待!
本文標題:Vert.x 技術內幕 | Event Bus 源碼分析 (Local模式)
文章作者:sczyh30
發佈時間:2016年09月03日
原始鏈接:http://www.sczyh30.com/posts/Vert-x/vertx-advanced-local-event-bus-internal/
許可協議: "知識共享-保持署名-非商用-相同方式共享 4.0" 轉載請保留原文鏈接及作者。