Golang事件系統(tǒng)Event Bus
本文介紹了事件總線實現(xiàn)。
最近在學(xué)習(xí)開源項目Grafana
的代碼,發(fā)現(xiàn)作者實現(xiàn)了一個事件總線的機(jī)制,在項目里面大量應(yīng)用,效果也非常好,代碼也比較簡單,介紹給大家看看。
源碼文件地址:grafana/bus.go at main · grafana/grafana · GitHub
1.注冊和調(diào)用
在這個項目里面隨處可見這種寫法:

關(guān)鍵是bus.Dispatch(&query)
這段代碼,它的參數(shù)是一個結(jié)構(gòu)體GetAlertByIdQuery
,內(nèi)容如下:

根據(jù)名字可以看出這個方法就是通過Id去查詢Alert,其中Alert
結(jié)構(gòu)體就是結(jié)果對象,這里就不貼出來了。
通過查看源碼可以得知,Dispatch背后是調(diào)用了GetAlertById
這個方法,然后把結(jié)果賦值到query參數(shù)的Result中返回。

問題來了,這是怎么實現(xiàn)的呢?Dispatch到底做了哪些操作?這樣做有什么好處?
下面我來一一解答:
首先,在Dispatch之前,你需要先注冊這個方法,也就是調(diào)用AddHandler
,在這個項目里面可以看到init函數(shù)里面有大量這樣的代碼:

其實這個方法的邏輯也很簡單,所謂注冊也就是把通過一個map把函數(shù)名和對應(yīng)的函數(shù)做一個映射關(guān)系保存起來,當(dāng)我們Dispatch的時候其實就是通過參數(shù)名查找之前注冊過的函數(shù),然后通過反射調(diào)用該函數(shù)。
Bus結(jié)構(gòu)體里面有幾個map成員,在這個項目里面作者定義了3種不同類型的handler,一種是普通的handler,也就是剛才展示的那種,第二種是帶上下文的handler,還有一種則是事件訂閱用到的handler,我們給一個事件注冊多個監(jiān)聽者,當(dāng)事件觸發(fā)的時候會依次調(diào)用多個監(jiān)聽函數(shù),其實就是一個觀察者模式。

下面就看看具體的源碼,AddHandler
方法內(nèi)容如下:

Dispatch方法的源碼如下:
對于AddHandlerCtx
和DispatchCtx
這個2個方法基本上是一樣的,只不過多了一個上下文參數(shù),可以拿來做超時控制或者其它用途。
2.訂閱和發(fā)布
除此之外,還有2個方法AddEventListener
和Publish
,即事件的訂閱和發(fā)布。

查看源碼可以得知,可以給一個事件注冊多個handler函數(shù),而Publish的時候則是依次調(diào)用注冊的函數(shù),邏輯也不復(fù)雜。

這里面有一點不好,所有訂閱函數(shù)的調(diào)用是順序的,并沒有使用協(xié)程,所以如果注冊了很多個函數(shù),這樣效率也不高啊。
3.好處
可能有人會好奇,為什么明明可以直接調(diào)用函數(shù)就行,為啥非得繞個彎子,整這么復(fù)雜?
況且,每次調(diào)用都得使用反射機(jī)制,性能也不行。
我覺得主要有以下幾點:
1.這種寫法邏輯清晰,解耦
2.方便單元測試
3.性能不是最大考量,雖然說反射會降低性能
轉(zhuǎn)自:wangbjun.site/2021/coding/golang/event-bus.html