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

    文章

    3014

    浏览量

    91313
  • 开源
    +关注

    关注

    3

    文章

    4038

    浏览量

    45579
  • 代码
    +关注

    关注

    30

    文章

    4942

    浏览量

    73160

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

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

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

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

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

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

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

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

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

    在资源受限的嵌入式环境中,LuatOS采用消息机制实现模块间解耦与高效通信。通过预定义消息名称(如“new_msg”),开发者可轻松构建响应式程序结构。接下来我们将深入剖析其实现原理与典型使用方法
    的头像 发表于 09-26 18:59 246次阅读
    教程来啦!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 9247次阅读
    ADI安全产品如何简化不同机器人控制系统中安全<b class='flag-5'>机制</b>的<b class='flag-5'>实现</b>

    NVMe IP之AXI4总线分析

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

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

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

    Java的SPI机制详解

    作者:京东物流 杨苇苇 1.SPI简介 SPI(Service Provicer Interface)是Java语言提供的一种接口发现机制,用来实现接口和接口实现的解耦。简单来说,就是系统只需要定义
    的头像 发表于 03-05 11:35 1124次阅读
    Java的SPI<b class='flag-5'>机制</b>详解

    CAN总线的电路保护器件,通过二极管阵列的工作原理实现了对CAN总线的高效保护

    CAN总线的电路保护器件,通过二极管阵列的工作原理实现了对CAN总线的高效保护。它具有高速响应、低电流泄露、低电压降和高温工作的特点,适用于汽车和工业控制系统中的CAN总线保护
    的头像 发表于 03-03 13:26 887次阅读
    CAN<b class='flag-5'>总线</b>的电路保护器件,通过二极管阵列的工作原理<b class='flag-5'>实现</b>了对CAN<b class='flag-5'>总线</b>的高效保护

    总线通信协议解析及应用

    方式。这些规则包括数据的编码、传输速率、同步机制、错误检测和处理等。总线通信协议确保了数据传输的一致性和可靠性,是计算机系统稳定运行的基石。 总线通信协议的类型 1. 并行总线通信协议
    的头像 发表于 12-31 10:07 1945次阅读

    总线与以太网的区别与联系

    在现代计算机和网络技术中,数据传输是核心功能之一。总线和以太网是实现这一功能的关键技术。尽管它们在某些方面有相似之处,但它们在设计、用途和实现上有着明显的区别。 总线(Bus)
    的头像 发表于 12-31 09:46 2294次阅读

    如何选择合适的总线协议

    传输的方式,包括数据的编码、传输速率、同步机制和错误检测等。一个好的总线协议应该能够满足系统的性能需求,同时保持足够的灵活性以适应不同的应用场景。 2. 确定系统需求 在选择总线协议之前,首先需要明确系统的具体需求。这
    的头像 发表于 12-31 09:41 980次阅读

    RISC-V芯片中使用的各种常用总线释义

    (Slave)之间的并发数据传输,具有分离的读/写通道,以及复杂的流量控制和错误检测机制。特点 :高吞吐率、低延迟、支持复杂的数据传输模式。AHB总线释义 :AHB(Advanced
    发表于 12-28 17:53

    CAN总线在工业自动化中的作用

    。以下是对CAN总线在工业自动化中作用的介绍: 一、CAN总线的基本特性 实时性 :CAN总线支持实时通信,能够确保数据的及时传输,这对于需要快速响应的工业自动化应用至关重要。 可靠性 :通过错误检测和重传
    的头像 发表于 12-23 09:12 1943次阅读

    如何使用Arduino实现CAN总线通信呢

    的硬件模块实现CAN总线通信。 硬件需求 Arduino板 :任何支持Arduino IDE的板子都可以,例如Arduino Uno、Mega等。 CAN总线模块 :例如MCP2515或MCP2562
    的头像 发表于 12-23 09:06 2828次阅读