0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Golang事件總線機(jī)制的實(shí)現(xiàn)

Linux愛(ài)好者 ? 來(lái)源:wangbjun.site ? 作者:wangbjun.site ? 2022-07-01 16:02 ? 次閱讀

【導(dǎo)讀】本文介紹了事件總線實(shí)現(xiàn)。

最近在學(xué)習(xí)開(kāi)源項(xiàng)目Grafana的代碼,發(fā)現(xiàn)作者實(shí)現(xiàn)了一個(gè)事件總線的機(jī)制,在項(xiàng)目里面大量應(yīng)用,效果也非常好,代碼也比較簡(jiǎn)單,介紹給大家看看。

源碼文件地址:grafana/bus.go at main · grafana/grafana · GitHub

1.注冊(cè)和調(diào)用

在這個(gè)項(xiàng)目里面隨處可見(jiàn)這種寫法:

funcValidateOrgAlert(c*models.ReqContext){
id:=c.ParamsInt64(":alertId")

query:=models.GetAlertByIdQuery{Id:id}

iferr:=bus.Dispatch(&query);err!=nil{
c.JsonApiErr(404,"Alertnotfound",nil)
return
}

ifc.OrgId!=query.Result.OrgId{
c.JsonApiErr(403,"Youarenotallowedtoedit/viewalert",nil)
return
}
}

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

typeGetAlertByIdQuerystruct{
Idint64
Result*Alert
}

根據(jù)名字可以看出這個(gè)方法就是通過(guò)Id去查詢Alert,其中Alert結(jié)構(gòu)體就是結(jié)果對(duì)象,這里就不貼出來(lái)了。

通過(guò)查看源碼可以得知,Dispatch背后是調(diào)用了GetAlertById這個(gè)方法,然后把結(jié)果賦值到query參數(shù)的Result中返回。

funcGetAlertById(query*models.GetAlertByIdQuery)error{
alert:=models.Alert{}
has,err:=x.ID(query.Id).Get(&alert)
if!has{
returnfmt.Errorf("couldnotfindalert")
}
iferr!=nil{
returnerr
}
query.Result=&alert
returnnil
}

問(wèn)題來(lái)了,這是怎么實(shí)現(xiàn)的呢?Dispatch到底做了哪些操作?這樣做有什么好處?

下面我來(lái)一一解答:

首先,在Dispatch之前,你需要先注冊(cè)這個(gè)方法,也就是調(diào)用AddHandler,在這個(gè)項(xiàng)目里面可以看到init函數(shù)里面有大量這樣的代碼:

funcinit(){
bus.AddHandler("sql",SaveAlerts)
bus.AddHandler("sql",HandleAlertsQuery)
bus.AddHandler("sql",GetAlertById)
...
}

其實(shí)這個(gè)方法的邏輯也很簡(jiǎn)單,所謂注冊(cè)也就是把通過(guò)一個(gè)map把函數(shù)名和對(duì)應(yīng)的函數(shù)做一個(gè)映射關(guān)系保存起來(lái),當(dāng)我們Dispatch的時(shí)候其實(shí)就是通過(guò)參數(shù)名查找之前注冊(cè)過(guò)的函數(shù),然后通過(guò)反射調(diào)用該函數(shù)。

Bus結(jié)構(gòu)體里面有幾個(gè)map成員,在這個(gè)項(xiàng)目里面作者定義了3種不同類型的handler,一種是普通的handler,也就是剛才展示的那種,第二種是帶上下文的handler,還有一種則是事件訂閱用到的handler,我們給一個(gè)事件注冊(cè)多個(gè)監(jiān)聽(tīng)者,當(dāng)事件觸發(fā)的時(shí)候會(huì)依次調(diào)用多個(gè)監(jiān)聽(tīng)函數(shù),其實(shí)就是一個(gè)觀察者模式。

//InProcBusdefinesthebusstructure
typeInProcBusstruct{
handlersmap[string]HandlerFunc
handlersWithCtxmap[string]HandlerFunc
listenersmap[string][]HandlerFunc
txMngTransactionManager
}

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

func(b*InProcBus)AddHandler(handlerHandlerFunc){
handlerType:=reflect.TypeOf(handler)
queryTypeName:=handlerType.In(0).Elem().Name()//獲取函數(shù)第一個(gè)參數(shù)的名稱,在上面例子里面就是GetAlertByIdQuery
b.handlers[queryTypeName]=handler
}

Dispatch方法的源碼如下:

func(b*InProcBus)Dispatch(msgMsg)error{
varmsgName=reflect.TypeOf(msg).Elem().Name()

withCtx:=true
handler:=b.handlersWithCtx[msgName]//根據(jù)參數(shù)名查找注冊(cè)過(guò)的函數(shù),先查找?guī)tx的handler
ifhandler==nil{
withCtx=false
handler=b.handlers[msgName]
ifhandler==nil{
returnErrHandlerNotFound
}
}
varparams=[]reflect.Value{}
ifwithCtx{
//如果查找到的handler是帶Ctx的就給個(gè)默認(rèn)的Background的Ctx
params=append(params,reflect.ValueOf(context.Background()))
}
params=append(params,reflect.ValueOf(msg))

ret:=reflect.ValueOf(handler).Call(params)//通過(guò)反射機(jī)制調(diào)用函數(shù)
err:=ret[0].Interface()
iferr==nil{
returnnil
}
returnerr.(error)
}

對(duì)于AddHandlerCtxDispatchCtx這個(gè)2個(gè)方法基本上是一樣的,只不過(guò)多了一個(gè)上下文參數(shù),可以拿來(lái)做超時(shí)控制或者其它用途。

2.訂閱和發(fā)布

除此之外,還有2個(gè)方法AddEventListenerPublish,即事件的訂閱和發(fā)布。

func(b*InProcBus)AddEventListener(handlerHandlerFunc){
handlerType:=reflect.TypeOf(handler)
eventName:=handlerType.In(0).Elem().Name()
_,exists:=b.listeners[eventName]
if!exists{
b.listeners[eventName]=make([]HandlerFunc,0)
}
b.listeners[eventName]=append(b.listeners[eventName],handler)
}

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

func(b*InProcBus)Publish(msgMsg)error{
varmsgName=reflect.TypeOf(msg).Elem().Name()
varlisteners=b.listeners[msgName]

varparams=make([]reflect.Value,1)
params[0]=reflect.ValueOf(msg)

for_,listenerHandler:=rangelisteners{
ret:=reflect.ValueOf(listenerHandler).Call(params)
e:=ret[0].Interface()
ife!=nil{
err,ok:=e.(error)
ifok{
returnerr
}
returnfmt.Errorf("expectedlistenertoreturnanerror,got'%T'",e)
}
}
returnnil
}

這里面有一點(diǎn)不好,所有訂閱函數(shù)的調(diào)用是順序的,并沒(méi)有使用協(xié)程,所以如果注冊(cè)了很多個(gè)函數(shù),這樣效率也不高啊。

3.好處

可能有人會(huì)好奇,為什么明明可以直接調(diào)用函數(shù)就行,為啥非得繞個(gè)彎子,整這么復(fù)雜?

況且,每次調(diào)用都得使用反射機(jī)制,性能也不行。

我覺(jué)得主要有以下幾點(diǎn):

1.這種寫法邏輯清晰,解耦

2.方便單元測(cè)試

3.性能不是最大考量,雖然說(shuō)反射會(huì)降低性能

原文標(biāo)題:Golang 事件系統(tǒng) Event Bus

文章出處:【微信公眾號(hào):Linux愛(ài)好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

審核編輯:湯梓紅
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 總線
    +關(guān)注

    關(guān)注

    10

    文章

    2896

    瀏覽量

    88228
  • 開(kāi)源
    +關(guān)注

    關(guān)注

    3

    文章

    3383

    瀏覽量

    42607
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4810

    瀏覽量

    68829

原文標(biāo)題:Golang 事件系統(tǒng) Event Bus

文章出處:【微信號(hào):LinuxHub,微信公眾號(hào):Linux愛(ài)好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    如何選擇合適的總線協(xié)議

    傳輸?shù)姆绞?,包括?shù)據(jù)的編碼、傳輸速率、同步機(jī)制和錯(cuò)誤檢測(cè)等。一個(gè)好的總線協(xié)議應(yīng)該能夠滿足系統(tǒng)的性能需求,同時(shí)保持足夠的靈活性以適應(yīng)不同的應(yīng)用場(chǎng)景。 2. 確定系統(tǒng)需求 在選擇總線協(xié)議之前,首先需要明確系統(tǒng)的具體需求。這
    的頭像 發(fā)表于 12-31 09:41 ?89次閱讀

    RISC-V芯片中使用的各種常用總線釋義

    (Slave)之間的并發(fā)數(shù)據(jù)傳輸,具有分離的讀/寫通道,以及復(fù)雜的流量控制和錯(cuò)誤檢測(cè)機(jī)制。特點(diǎn) :高吞吐率、低延遲、支持復(fù)雜的數(shù)據(jù)傳輸模式。AHB總線釋義 :AHB(Advanced
    發(fā)表于 12-28 17:53

    如何使用Arduino實(shí)現(xiàn)CAN總線通信呢

    的硬件模塊實(shí)現(xiàn)CAN總線通信。 硬件需求 Arduino板 :任何支持Arduino IDE的板子都可以,例如Arduino Uno、Mega等。 CAN總線模塊 :例如MCP2515或MCP2562
    的頭像 發(fā)表于 12-23 09:06 ?358次閱讀

    CAN總線與LIN總線的區(qū)別

    不同的數(shù)據(jù)傳輸速率,從最低的10 kbps到最高的1 Mbps。 拓?fù)浣Y(jié)構(gòu): 通常采用雙絞線結(jié)構(gòu),支持多點(diǎn)通信。 錯(cuò)誤檢測(cè): 具有強(qiáng)大的錯(cuò)誤檢測(cè)機(jī)制,包括位錯(cuò)誤、幀錯(cuò)誤等。 仲裁機(jī)制: 使用基于優(yōu)先級(jí)的非破壞性總線仲裁
    的頭像 發(fā)表于 11-12 10:13 ?2235次閱讀

    如何使用Arduino實(shí)現(xiàn)CAN總線通信

    開(kāi)源硬件平臺(tái),通過(guò)添加CAN總線模塊,也可以實(shí)現(xiàn)CAN通信。 硬件準(zhǔn)備 Arduino開(kāi)發(fā)板 :可以選擇Arduino Uno、Mega等型號(hào)。 CAN總線模塊 :如MCP2515或MCP2562,這些模塊
    的頭像 發(fā)表于 11-12 10:09 ?1194次閱讀

    Golang配置代理方法

    由于一些客觀原因的存在,我們開(kāi)發(fā) Golang 項(xiàng)目的過(guò)程總會(huì)碰到無(wú)法下載某些依賴包的問(wèn)題。這不是一個(gè)小問(wèn)題,因?yàn)槟愕墓ぷ鲿?huì)被打斷,即便你使用各種神通解決了問(wèn)題,很可能這時(shí)你的線程已經(jīng)切換到其他的事情上了(痛恨思路被打斷!)。所以最好是一開(kāi)始我們就重視這個(gè)問(wèn)題,并一勞永逸的解決它。
    的頭像 發(fā)表于 11-11 11:17 ?335次閱讀
    <b class='flag-5'>Golang</b>配置代理方法

    CAN總線控制器的工作原理

    CAN(Controller Area Network,控制器局域網(wǎng))總線控制器的工作原理涉及多個(gè)方面,包括消息傳輸、沖突檢測(cè)與解決、總線仲裁等關(guān)鍵機(jī)制。以下是對(duì)CAN總線控制器工作原
    的頭像 發(fā)表于 09-30 11:33 ?1051次閱讀

    EN?Power?Bus二總線接口轉(zhuǎn)接485方案芯片-485接口芯片

    EN20F18 是采用低壓直流供電總線通訊技術(shù)設(shè)計(jì)的一款通訊接口芯片,是英銳恩EN Power Bus二總線接口轉(zhuǎn)接485方案芯片,用于兩總線終端產(chǎn)品,利用總線電壓識(shí)別和電流回饋
    發(fā)表于 09-29 16:04

    干貨分享 | TSMaster—LIN 喚醒與休眠機(jī)制

    在汽車總線中常見(jiàn)的喚醒方式有硬線喚醒、網(wǎng)絡(luò)喚醒和特定信號(hào)喚醒,而LIN總線則是通過(guò)休眠幀與喚醒電平來(lái)實(shí)現(xiàn)的,本文將介紹LIN的喚醒與休眠機(jī)制。本文關(guān)鍵詞:LIN網(wǎng)絡(luò)管理,休眠,喚醒
    的頭像 發(fā)表于 09-25 08:03 ?1960次閱讀
    干貨分享 | TSMaster—LIN 喚醒與休眠<b class='flag-5'>機(jī)制</b>

    【米爾NXP i.MX 93開(kāi)發(fā)板試用評(píng)測(cè)】4、使用golang搭建Modbus 服務(wù)器

    負(fù)責(zé)處理來(lái)自客戶端(通常稱為Modbus客戶端或從站)的請(qǐng)求,并根據(jù)請(qǐng)求提供相應(yīng)的數(shù)據(jù)或執(zhí)行操作。 快速開(kāi)發(fā)modbus服務(wù)器 可以使用golang快速部署一個(gè)modbus服務(wù)器。我們先在開(kāi)發(fā)板上安裝
    發(fā)表于 09-21 22:51

    異步總線中傳送操作的控制機(jī)制

    據(jù)傳輸過(guò)程中存在一定的延遲,因此需要一種有效的控制機(jī)制來(lái)保證數(shù)據(jù)傳輸?shù)臏?zhǔn)確性和可靠性。 異步總線概述 1.1 異步總線的定義 異步總線是一種在計(jì)算機(jī)系統(tǒng)中用于數(shù)據(jù)傳輸?shù)耐ㄐ欧绞?,其特點(diǎn)
    的頭像 發(fā)表于 07-23 09:17 ?727次閱讀

    求助,是否有自帶timeout機(jī)制的EEPROM?

    請(qǐng)問(wèn)下各位大佬們是否有自帶timeout機(jī)制的EEPROM? 如果由于主設(shè)備異常復(fù)位導(dǎo)致總線死鎖,是否有能檢測(cè)到SDA低于一段時(shí)間后,會(huì)將自己reset的EEPROM;(主設(shè)備沒(méi)有解決總線死鎖的手段) 我找了一圈沒(méi)有找到,請(qǐng)問(wèn)下
    發(fā)表于 07-05 06:14

    如何快速實(shí)現(xiàn)CAN總線故障定位?

    快速實(shí)現(xiàn)CAN總線故障定位是汽車電子和工業(yè)自動(dòng)化領(lǐng)域中的一個(gè)重要課題。CAN總線作為一種重要的通信網(wǎng)絡(luò),其穩(wěn)定性和可靠性對(duì)于整個(gè)系統(tǒng)的運(yùn)行至關(guān)重要。
    的頭像 發(fā)表于 04-09 15:46 ?898次閱讀

    Golang為何舍棄三元運(yùn)算符

    golang中不存在?:運(yùn)算符的原因是因?yàn)檎Z(yǔ)言設(shè)計(jì)者已經(jīng)預(yù)見(jiàn)到三元運(yùn)算符經(jīng)常被用來(lái)構(gòu)建一些極其復(fù)雜的表達(dá)式。雖然使用if進(jìn)行替代會(huì)讓代碼顯得更長(zhǎng),但這毫無(wú)疑問(wèn)可讀性更強(qiáng)。
    的頭像 發(fā)表于 04-03 15:13 ?729次閱讀

    CAN總線的可靠通信是依靠什么機(jī)制來(lái)實(shí)現(xiàn)的?

    CAN總線采取多種技術(shù)措施來(lái)消除外界干擾,確保可靠通信。
    的頭像 發(fā)表于 01-30 09:50 ?1835次閱讀