0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Golang事件总线机制的实现

Linux爱好者 来源:wangbjun.site 作者:wangbjun.site 2022-07-01 16:02 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

【导读】本文介绍了事件总线实现。

最近在学习开源项目Grafana的代码,发现作者实现了一个事件总线的机制,在项目里面大量应用,效果也非常好,代码也比较简单,介绍给大家看看。

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

1.注册和调用

在这个项目里面随处可见这种写法:

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
}
}

关键是bus.Dispatch(&query)这段代码,它的参数是一个结构体GetAlertByIdQuery,内容如下:

typeGetAlertByIdQuerystruct{
Idint64
Result*Alert
}

根据名字可以看出这个方法就是通过Id去查询Alert,其中Alert结构体就是结果对象,这里就不贴出来了。

通过查看源码可以得知,Dispatch背后是调用了GetAlertById这个方法,然后把结果赋值到query参数的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
}

问题来了,这是怎么实现的呢?Dispatch到底做了哪些操作?这样做有什么好处?

下面我来一一解答:

首先,在Dispatch之前,你需要先注册这个方法,也就是调用AddHandler,在这个项目里面可以看到init函数里面有大量这样的代码:

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

其实这个方法的逻辑也很简单,所谓注册也就是把通过一个map把函数名和对应的函数做一个映射关系保存起来,当我们Dispatch的时候其实就是通过参数名查找之前注册过的函数,然后通过反射调用该函数。

Bus结构体里面有几个map成员,在这个项目里面作者定义了3种不同类型的handler,一种是普通的handler,也就是刚才展示的那种,第二种是带上下文的handler,还有一种则是事件订阅用到的handler,我们给一个事件注册多个监听者,当事件触发的时候会依次调用多个监听函数,其实就是一个观察者模式。

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

下面就看看具体的源码,AddHandler方法内容如下:

func(b*InProcBus)AddHandler(handlerHandlerFunc){
handlerType:=reflect.TypeOf(handler)
queryTypeName:=handlerType.In(0).Elem().Name()//获取函数第一个参数的名称,在上面例子里面就是GetAlertByIdQuery
b.handlers[queryTypeName]=handler
}

Dispatch方法的源码如下:

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

withCtx:=true
handler:=b.handlersWithCtx[msgName]//根据参数名查找注册过的函数,先查找带Ctx的handler
ifhandler==nil{
withCtx=false
handler=b.handlers[msgName]
ifhandler==nil{
returnErrHandlerNotFound
}
}
varparams=[]reflect.Value{}
ifwithCtx{
//如果查找到的handler是带Ctx的就给个默认的Background的Ctx
params=append(params,reflect.ValueOf(context.Background()))
}
params=append(params,reflect.ValueOf(msg))

ret:=reflect.ValueOf(handler).Call(params)//通过反射机制调用函数
err:=ret[0].Interface()
iferr==nil{
returnnil
}
returnerr.(error)
}

对于AddHandlerCtxDispatchCtx这个2个方法基本上是一样的,只不过多了一个上下文参数,可以拿来做超时控制或者其它用途。

2.订阅和发布

除此之外,还有2个方法AddEventListenerPublish,即事件的订阅和发布。

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)
}

查看源码可以得知,可以给一个事件注册多个handler函数,而Publish的时候则是依次调用注册的函数,逻辑也不复杂。

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
}

这里面有一点不好,所有订阅函数的调用是顺序的,并没有使用协程,所以如果注册了很多个函数,这样效率也不高啊。

3.好处

可能有人会好奇,为什么明明可以直接调用函数就行,为啥非得绕个弯子,整这么复杂?

况且,每次调用都得使用反射机制,性能也不行。

我觉得主要有以下几点:

1.这种写法逻辑清晰,解耦

2.方便单元测试

3.性能不是最大考量,虽然说反射会降低性能

原文标题:Golang 事件系统 Event Bus

文章出处:【微信公众号:Linux爱好者】欢迎添加关注!文章转载请注明出处。

审核编辑:汤梓红
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 总线
    +关注

    关注

    10

    文章

    3057

    浏览量

    91864
  • 开源
    +关注

    关注

    3

    文章

    4343

    浏览量

    46435
  • 代码
    +关注

    关注

    30

    文章

    4976

    浏览量

    74370

原文标题:Golang 事件系统 Event Bus

文章出处:【微信号:LinuxHub,微信公众号:Linux爱好者】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    深度解析AS32S601芯片CAN Bus Off机制

    在汽车电子、工业自动化等众多领域,CAN 总线作为一种可靠的通信协议被广泛应用。而 AS32S601 芯片凭借其卓越的性能和可靠性,在这些领域也发挥着重要作用。其中,CAN Bus Off 功能作为 CAN 总线通信中的关键错误处理机制
    的头像 发表于 04-22 09:36 354次阅读
    深度解析AS32S601芯片CAN Bus Off<b class='flag-5'>机制</b>

    基于FPGA的轻量级CAN总线控制器实现方案

    CAN总线作为工业和汽车领域最常用的通信总线,具有拓扑结构简洁、可靠性高、传输距离长等优点。CAN总线的非破坏性仲裁机制依赖于帧ID,CAN2.0A和CAN2.0B分别规定了11bit
    的头像 发表于 04-03 10:10 1852次阅读
    基于FPGA的轻量级CAN<b class='flag-5'>总线</b>控制器<b class='flag-5'>实现</b>方案

    基于PXIe总线的多板卡通道同步机制研究

    1引言在上一篇《基于PXIe的单板多通道同步机制研究》中,我们重点讨论了在同一块PXIe数据采集卡内部,如何通过统一时间基准、统一采样时钟与统一启动触发,实现各通道在时间轴上的严格对齐。然而在实际
    的头像 发表于 02-03 14:20 713次阅读
    基于PXIe<b class='flag-5'>总线</b>的多板卡通道同步<b class='flag-5'>机制</b>研究

    请问休眠模式下的定时唤醒机制如何实现

    休眠模式下的定时唤醒机制如何实现
    发表于 12-24 07:58

    CW32系统总线有哪些?

    •系统总线 实现 M0+ 微处理器的外设总线总线矩阵的连接。 •DMA 总线 实现 DMA
    发表于 12-15 07:54

    CW32总线介绍

    •系统总线 实现 M0+ 微处理器的外设总线总线矩阵的连接。 •DMA 总线 实现 DMA
    发表于 12-12 06:21

    基于IAP功能实现远程升级,如何设计Flash双Bank热切换的回滚机制

    基于IAP功能实现远程升级时,如何设计Flash双Bank热切换的回滚机制
    发表于 11-21 07:26

    放大器保护机制的技术原理与实现策略

    现代电子测量系统中,功率放大器和高压放大器通过多级保护机制(过流、过压、过温)确保设备稳定运行,具备自适应和智能控制功能
    的头像 发表于 10-20 09:41 436次阅读

    如何利用Trace机制实现LLCP预览功能

    在蓝牙协议栈开发过程中,有时需要预先知道 LLCP。本文将介绍如何利用 Trace 机制实现 LLCP 预览功能。
    的头像 发表于 10-09 17:55 2075次阅读

    教程来啦!LuatOS中的消息通信机制详解及其应用场景

    在资源受限的嵌入式环境中,LuatOS采用消息机制实现模块间解耦与高效通信。通过预定义消息名称(如“new_msg”),开发者可轻松构建响应式程序结构。接下来我们将深入剖析其实现原理与典型使用方法
    的头像 发表于 09-26 18:59 550次阅读
    教程来啦!LuatOS中的消息通信<b class='flag-5'>机制</b>详解及其应用场景

    【HZ-T536开发板免费体验】5、安装sqlite3和使用golang读写数据库

    如果想在嵌入式设备上实现简单的设备管理功能,需要数据库和服务后端程序。服务端程序,我更倾向使用golang实现。 安装sqlite3,使用ubuntu环境,可以直接用apt install安装程序
    发表于 08-26 00:04

    ADI安全产品如何简化不同机器人控制系统中安全机制实现

    我们将探讨各种机器人安全用例,展示ADI的安全产品如何简化不同机器人控制系统中安全机制实现
    的头像 发表于 08-12 10:43 1.4w次阅读
    ADI安全产品如何简化不同机器人控制系统中安全<b class='flag-5'>机制</b>的<b class='flag-5'>实现</b>

    CAN总线采样点不一致的危害

    参数,遵循CiA等行业标准,并使用位定时计算工具。通过合理的配置和测试,CAN总线可以实现高效、稳定的通信,满足汽车和工业应用的严格要求。
    发表于 06-07 08:55

    NVMe IP之AXI4总线分析

    1AXI4总线协议 AXI4总线协议是由ARM公司提出的一种片内总线协议 ,旨在实现SOC中各模块之间的高效可靠的数据传输和管理。AXI4协议具有高性能、高吞吐量和低延迟等优点,在SO
    发表于 06-02 23:05

    如何验证CAN控制器的错误响应机制

    使用ZPS-CANFD设备验证CAN控制器的错误响应过程。CAN控制器的错误管理机制是保障CAN总线通信可靠性的关键机制,它能检测并处理多种错误情况,即位错误、填充错误、C
    的头像 发表于 04-30 18:24 1017次阅读
    如何验证CAN控制器的错误响应<b class='flag-5'>机制</b>?