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

    文章

    2868

    浏览量

    87994
  • 开源
    +关注

    关注

    3

    文章

    3254

    浏览量

    42408
  • 代码
    +关注

    关注

    30

    文章

    4751

    浏览量

    68360

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

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

收藏 人收藏

    评论

    相关推荐

    CAN总线与LIN总线的区别

    不同的数据传输速率,从最低的10 kbps到最高的1 Mbps。 拓扑结构: 通常采用双绞线结构,支持多点通信。 错误检测: 具有强大的错误检测机制,包括位错误、帧错误等。 仲裁机制: 使用基于优先级的非破坏性总线仲裁
    的头像 发表于 11-12 10:13 378次阅读

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

    开源硬件平台,通过添加CAN总线模块,也可以实现CAN通信。 硬件准备 Arduino开发板 :可以选择Arduino Uno、Mega等型号。 CAN总线模块 :如MCP2515或MCP2562,这些模块
    的头像 发表于 11-12 10:09 343次阅读

    Golang配置代理方法

    由于一些客观原因的存在,我们开发 Golang 项目的过程总会碰到无法下载某些依赖包的问题。这不是一个小问题,因为你的工作会被打断,即便你使用各种神通解决了问题,很可能这时你的线程已经切换到其他的事情上了(痛恨思路被打断!)。所以最好是一开始我们就重视这个问题,并一劳永逸的解决它。
    的头像 发表于 11-11 11:17 131次阅读
    <b class='flag-5'>Golang</b>配置代理方法

    CAN总线控制器的工作原理

    CAN(Controller Area Network,控制器局域网)总线控制器的工作原理涉及多个方面,包括消息传输、冲突检测与解决、总线仲裁等关键机制。以下是对CAN总线控制器工作原
    的头像 发表于 09-30 11:33 618次阅读

    EN Power Bus二总线接口转接485方案芯片-485接口芯片

    EN20F18 是采用低压直流供电总线通讯技术设计的一款通讯接口芯片,是英锐恩EN Power Bus二总线接口转接485方案芯片,用于两总线终端产品,利用总线电压识别和电流回馈
    发表于 09-29 16:04

    干货分享 | TSMaster—LIN 唤醒与休眠机制

    在汽车总线中常见的唤醒方式有硬线唤醒、网络唤醒和特定信号唤醒,而LIN总线则是通过休眠帧与唤醒电平来实现的,本文将介绍LIN的唤醒与休眠机制。本文关键词:LIN网络管理,休眠,唤醒
    的头像 发表于 09-25 08:03 1376次阅读
    干货分享 | TSMaster—LIN 唤醒与休眠<b class='flag-5'>机制</b>

    【米尔NXP i.MX 93开发板试用评测】4、使用golang搭建Modbus 服务器

    负责处理来自客户端(通常称为Modbus客户端或从站)的请求,并根据请求提供相应的数据或执行操作。 快速开发modbus服务器 可以使用golang快速部署一个modbus服务器。我们先在开发板上安装
    发表于 09-21 22:51

    异步总线中传送操作的控制机制

    据传输过程中存在一定的延迟,因此需要一种有效的控制机制来保证数据传输的准确性和可靠性。 异步总线概述 1.1 异步总线的定义 异步总线是一种在计算机系统中用于数据传输的通信方式,其特点
    的头像 发表于 07-23 09:17 553次阅读

    求助,是否有自带timeout机制的EEPROM?

    请问下各位大佬们是否有自带timeout机制的EEPROM? 如果由于主设备异常复位导致总线死锁,是否有能检测到SDA低于一段时间后,会将自己reset的EEPROM;(主设备没有解决总线死锁的手段) 我找了一圈没有找到,请问下
    发表于 07-05 06:14

    如何快速实现CAN总线故障定位?

    快速实现CAN总线故障定位是汽车电子和工业自动化领域中的一个重要课题。CAN总线作为一种重要的通信网络,其稳定性和可靠性对于整个系统的运行至关重要。
    的头像 发表于 04-09 15:46 786次阅读

    Golang为何舍弃三元运算符

    golang中不存在?:运算符的原因是因为语言设计者已经预见到三元运算符经常被用来构建一些极其复杂的表达式。虽然使用if进行替代会让代码显得更长,但这毫无疑问可读性更强。
    的头像 发表于 04-03 15:13 672次阅读

    CAN总线的可靠通信是依靠什么机制实现的?

    CAN总线采取多种技术措施来消除外界干扰,确保可靠通信。
    的头像 发表于 01-30 09:50 1529次阅读

    如何使用Golang连接MySQL

    首先我们来看如何使用Golang连接MySQL。
    的头像 发表于 01-08 09:42 3261次阅读
    如何使用<b class='flag-5'>Golang</b>连接MySQL

    IIC总线的FPGA实现说明

    DE2_TV中,有关于寄存器的配置的部分,采用的方法是通过IIC的功能,这里对IIC总线的FPGA实现做个说明。
    的头像 发表于 01-05 10:16 1017次阅读
    IIC<b class='flag-5'>总线</b>的FPGA<b class='flag-5'>实现</b>说明

    Golang接口的作用和应用场景

    Golang(Go)作为一门现代的静态类型编程语言,提供了许多强大的特性,其中之一便是接口(interface)。接口是Golang中的一个核心概念,它具有广泛的应用场景,可以帮助开发者实现
    的头像 发表于 12-05 10:44 1096次阅读