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

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

3天内不再提示

关于go-zero如何追踪的原理追溯

OSC开源社区 来源:OSC开源社区 2022-12-22 10:35 次阅读

前言

链路追踪是每个微服务架构下必备的利器,go-zero 当然早已经为我们考虑好了,只需要在配置中添加配置即可使用。

关于 go-zero 如何追踪的原理追溯,之前已经有同学分享,这里我就不再多说,如果有想了解的同学去 https://mp.weixin.qq.com/s/hJEWcWc3PnGfWfbPCHfM9g 这个链接看就好了。默认会在 api 的中间件与 rpc 的 interceptor 添加追踪,如果有不了解 go-zero 默认如何使用默认的链路追踪的,请移步我的开源项目 go-zero-looklook 文档 https://github.com/Mikaelemmmm/go-zero-looklook/blob/main/doc/chinese/12-%E9%93%BE%E8%B7%AF%E8%BF%BD%E8%B8%AA.md。

今天我想讲的是,除了 go-zero 默认在 api 的 middleware 与 rpc 的 interceptor 中帮我们集成好的链路追踪,我们想自己在某些本地方法添加链路追踪代码或者我们想在 api 发送一个消息给 mq 服务时候想把整个链路包含 mq 的 producer、consumer 穿起来,在 go-zero 中该如何做。

场景

我们先简单讲一下我们的小 demo 的场景,一个请求进来调用 api 的 Login 方法,在 Login 方法中先调用 rpc 的 GetUserByMobile 方法,之后在调用 api 本地的 local 方法,紧接着调用 rabbitmq 传递消息到 mq 服务。

go-zero 默认集成了 jaeger、zinpink,这里我们就以 jaeger 为例

我们希望看到的链路是

4ae48258-813f-11ed-8abf-dac502259ad0.pngapi.Login -> rpc.GetUserByMobile

也就是 api 衍生出来三条子链路,api.producerMq 有一条调用 mq.Consumer 的子链路。

我们想要将一个方法添加到链路中需要两个因素,一个 traceId,一个span,当我们在同一个 traceId 下开启 span 把相关的 span 都串联起来,如果想形成父子关系,就要把 span 之间相互串联起来,因为「微服务实践」公众号中讲解原理太多,我这里就简单提一下不涉及过多,如果不是特别熟悉原理可以看文章开头推荐的文章,这里我们只需要知道 traceIdspanId 关系就好。

核心业务代码

1、首先 API 中 LoginLogic 代码

typeLoginLogicstruct{
logx.Logger
ctxcontext.Context
svcCtx*svc.ServiceContext
}

funcNewLoginLogic(ctxcontext.Context,svcCtx*svc.ServiceContext)*LoginLogic{
return&LoginLogic{
Logger:logx.WithContext(ctx),
ctx:ctx,
svcCtx:svcCtx,
}
}

typeMsgBodystruct{
Carrier*propagation.HeaderCarrier
Msgstring
}

func(l*LoginLogic)Login(req*types.RegisterReq)(*types.AccessTokenResp,error){
resp,err:=l.svcCtx.UserRpc.GetUserByMobile(l.ctx,&usercenter.GetUserByMobileReq{
Mobile:req.Mobile,
})
iferr!=nil{
return&types.AccessTokenResp{},nil
}

l.local()

tracer:=otel.GetTracerProvider().Tracer(trace.TraceName)
spanCtx,span :=tracer.Start(l.ctx,"send_msg_mq",oteltrace.WithSpanKind(oteltrace.SpanKindProducer))
carrier:=&propagation.HeaderCarrier{}
otel.GetTextMapPropagator().Inject(spanCtx,carrier)

producer:=rabbit.NewRabbitmqPublisher(RabbitmqDNS)
msg:=&MsgBody{
Carrier:carrier,
Msg:req.Mobile,
}
b,err:=json.Marshal(msg)
iferr!=nil{
panic(err)
}

iferr:=producer.Publish(spanCtx,ExchangeName,RoutineKeys,b);err!=nil{
logx.Errorf("PublishFail,msg:%s,err:%v",msg,err)
}
span.End()

return&types.AccessTokenResp{
AccessExpire:resp.User.Id,
},err
}

func(l*LoginLogic)local(){
tracer:=otel.GetTracerProvider().Tracer(trace.TraceName)
_,span :=tracer.Start(l.ctx,"local",oteltrace.WithSpanKind(oteltrace.SpanKindInternal))
deferspan.End()

//执行你的代码.....
}

2、rpc 中 GetUserByMobile 的代码

func(s*Logic)GetUserByMobile(context.Context,*usercenterPb.GetUserByMobileReq)(*usercenterPb.GetUserByMobileResp,error){
vo:=&usercenterPb.UserVo{
Id:1,
}
return&usercenterPb.GetUserByMobileResp{
User:vo,
},nil
}

3、mq 中 Consumer 的代码

typeMsgBodystruct{
Carrier*propagation.HeaderCarrier
Msgstring
}

func(c*consumer)Consumer(ctxcontext.Context,data[]byte)error{
varmsgMsgBody
iferr:=json.Unmarshal(data,&msg);err!=nil{
logx.Errorf("consumererr:%v",err)
}else{
logx.Infof("consumerOneConsumer,msg:%+v",msg)

wireContext:=otel.GetTextMapPropagator().Extract(ctx,msg.Carrier)
tracer:=otel.GetTracerProvider().Tracer(trace.TraceName)
_,span :=tracer.Start(wireContext,"mq_consumer_msg",oteltrace.WithSpanKind(oteltrace.SpanKindConsumer))

deferspan.End()
}

returnnil
}

代码详解

1、go-zero 默认集成

当一个请求进入 api 后,我们可以在 go-zero 源码中查看到 https://github.com/zeromicro/go-zero/blob/master/rest/engine.go#L92。go-zero 已经在 api 的 middleware 中帮我们添加了第一层 trace,当进入 Login 方法内,我们调用了 rpc 的 GetUserByMobile 方法,通过 go-zero 的源码 https://github.com/zeromicro/go-zero/blob/master/zrpc/internal/rpcserver.go#L55 可以看到在 rpc 的 interceptor 也默认帮我们添加好了,这两层都是 go-zero 默认帮我们做好的。

2、本地方法

当调用完 rpc 的 GetUserByMobile 之后,api 调用了本地的 local,如果我们想在整个链路上体现出来调用了本地 local 方法,那默认的 go-zero 是没有帮我们做的,需要我们手动来添加。

tracer:=otel.GetTracerProvider().Tracer(trace.TraceName)
_,span :=tracer.Start(l.ctx,"local",oteltrace.WithSpanKind(oteltrace.SpanKindInternal))
deferspan.End()

//执行你的代码.....

我们通过上面代码拿到 tracer,ctx 之后开启一个 local 的 span,因为 start 时候会从 ctx 获取父 span 所以会将 local 方法与 Login 串联起父子调用关系,这样就将本次操作加入了这个链路

3、mq 的 producer 到 mq 的 consumer

我们在mq传递中如何串联起来这个链路呢?也就是形成 api.Login->api.producer->mq.Consumer

想一下原理,虽然跨越了网络,api 可以通过 header 传递,rpc 可以通过 metadata 传递,那么 mq 是不是也可以通过 headerbody 传递就可以了,按照这个想法来看下我门的代码。

tracer:=otel.GetTracerProvider().Tracer(trace.TraceName)
spanCtx,span :=tracer.Start(l.ctx,"send_msg_mq",oteltrace.WithSpanKind(oteltrace.SpanKindProducer))
carrier:=&propagation.HeaderCarrier{}
otel.GetTextMapPropagator().Inject(spanCtx,carrier)

producer:=rabbit.NewRabbitmqPublisher(RabbitmqDNS)
msg:=&MsgBody{
Carrier:carrier,
Msg:req.Mobile,
}
b,err:=json.Marshal(msg)
iferr!=nil{
panic(err)
}

iferr:=producer.Publish(spanCtx,ExchangeName,RoutineKeys,b);err!=nil{
logx.Errorf("PublishFail,msg:%s,err:%v",msg,err)
}
span.End()

首先获取到了这个全局的 tracer,然后开启一个 producerspan,跟 local 方法一样,我们开启 producerspan 时候也是通过 ctx 获取到上一级父级 span,这样就可以将 producerspanLogin 形成父子 span 调用关系,那我们想将 producerspan 与 mq 的 consumer 中的 span 形成调用父子关系怎么做?我们将 api.producerspanCtx 注入到 carrier 中,这里我们通过 mq 的 bodycarrier 发送给 consumer,发送完成我们 stop 我们的 producer,那么 producer 的这层链路完成了。

随后我们来看 mq-consumer 在接收到 body 消息之后怎么做的。

typeMsgBodystruct{
Carrier*propagation.HeaderCarrier
Msgstring
}

func(c*consumer)Consumer(ctxcontext.Context,data[]byte)error{
varmsgMsgBody
iferr:=json.Unmarshal(data,&msg);err!=nil{
logx.Errorf("consumererr:%v",err)
}else{
logx.Infof("consumerOneConsumer,msg:%+v",msg)

wireContext:=otel.GetTextMapPropagator().Extract(ctx,msg.Carrier)
tracer:=otel.GetTracerProvider().Tracer(trace.TraceName)
_,span :=tracer.Start(wireContext,"mq_consumer_msg",oteltrace.WithSpanKind(oteltrace.SpanKindConsumer))

deferspan.End()
}

returnnil
}

consumer 接收到消息后反序列化出来 Carrier *propagation.HeaderCarrier,然后通过 otel.GetTextMapPropagator().Extract 取出来 api.producer 注入的 wireContext,在通过 tracer.StartwireContext 创建 consumerspan,这样 consumer 就是 api.producer 的子 span,就形成了调用链路关系,最终我们得到的关系就是

4ae48258-813f-11ed-8abf-dac502259ad0.pngapi.Login -> rpc.GetUserByMobile

让我们来调用一下 Logic 方法,看下 jaeger 中的链路如果与我们预想的链路一致,so happy~

4b64a802-813f-11ed-8abf-dac502259ad0.jpg

项目地址

go-zero 微服务框架:https://github.com/zeromicro/go-zero

go-zero 微服务最佳实践项目:https://github.com/Mikaelemmmm/go-zero-looklook

欢迎使用 go-zerostar 支持我们!

审核编辑 :李倩


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

    关注

    0

    文章

    43

    浏览量

    12241
  • 微服务
    +关注

    关注

    0

    文章

    134

    浏览量

    7328

原文标题:玩转 Go 链路追踪

文章出处:【微信号:OSC开源社区,微信公众号:OSC开源社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    在学习go语言的过程踩过的坑

    作为一个5年的phper,这两年公司和个人都在顺应技术趋势,新项目慢慢从php转向了go语言,从2021年到现在,笔者手上也先后开发了两个go项目。在学习go语言的过程中也学习并总结了一些相关的东西,这篇文章就分享下自己踩过的一
    的头像 发表于 11-11 09:22 114次阅读

    go语言如何解决并发问题

    作为一个后端开发,日常工作中接触最多的两门语言就是PHP和GO了。无可否认,PHP确实是最好的语言(手动狗头哈哈),写起来真的很舒爽,没有任何心智负担,字符串和整型压根就不用区分,开发速度真的是比
    的头像 发表于 10-23 13:38 113次阅读
    <b class='flag-5'>go</b>语言如何解决并发问题

    关于 PCM5122 zero data detect寄存器配置疑问,求助解答

    请问PCM5122的zero Data Detect这里寄存器Bit:0,Bit:1,Bit:2三个都配置成0 如下图所示,对使用有没有什么其他的注意事项,会不会带来其他风险。 期待您的回复,感谢
    发表于 10-09 09:00

    如何远离网络追踪

    ​随着科技不断发展,生活、工作等都离不开网络。但网络追踪技术也随着科技的进步不断发展,人们在网络上的在线活动越来越容易被追踪和监控。这会威胁到个人隐私、信息泄露、身份盗窃等严重后果。所以,今天我想
    的头像 发表于 09-03 16:57 334次阅读
    如何远离网络<b class='flag-5'>追踪</b>

    三十分钟入门基础Go Java小子版

    前言 Go语言定义 Go(又称 Golang)是 Google 的 Robert Griesemer,Rob Pike 及 Ken Thompson 开发的一种静态、强类型、编译型语言。Go 语言
    的头像 发表于 08-12 14:32 678次阅读
    三十分钟入门基础<b class='flag-5'>Go</b> Java小子版

    动态追溯方法:彻底革新软件测试

    动态追溯方法为解决软件开发中追溯的挑战提供了创新的解决方案。通过自动和动态地链接需求和测试用例,使测试过程显著提高效率和精确度。该方法减少了手工操作,改善了故障分析,并确保了持续的追溯性,最终提升了软件的质量和可靠性。采用动态
    的头像 发表于 07-05 17:40 360次阅读
    动态<b class='flag-5'>追溯</b>方法:彻底革新软件测试

    求助,关于PSoC4000S POWER_DRILL2GO下降压摆率的疑问求解

    我不确定来自哪个版本,但数据表中列出了 PSoC4000S POWER_DRILL2GO下降压摆率。(1V/ms 最小,最大 67V/ms) 目前的设计并不能满足这个要求
    发表于 05-21 08:14

    请问STVP+COSMIC环境下的go to definition怎么用?

    STVP+COSMIC环境下的go to definition怎么用? 我现在go to definition在一个宏定义的时候有效果,但是函数什么的没用,是怎么回事呢,是不是工程里面没有设置好,求大家帮帮忙?
    发表于 05-11 06:11

    关于go中接口类型的表示方法

    go是一个静态性语言,每个变量都有静态的类型,因此每个变量在编译阶段中有明确的变量类型,比如像:int、float32、MyType。
    的头像 发表于 04-28 10:13 336次阅读

    MES系统对生产追溯的好处

    。 2. 提高生产效率:MES系统能够对生产过程进行优化和调度,确保生产线的平衡和高效运转,减少生产停机时间和资源浪费,提高生产效率。 3. 提供可追溯性:MES系统可以记录和追踪产品的生产过程,包括原材料的来源、加工过程中的各项参数、人员
    的头像 发表于 04-08 10:30 497次阅读
    MES系统对生产<b class='flag-5'>追溯</b>的好处

    追踪跳线都用哪些场景

    鹰眼追踪跳线是一种用于机房管理的工具,可以查询铜缆和光缆(包括超五类、六类、超六类屏蔽/非屏蔽,单模/多模光纤等)。其应用场景包括但不限于: 机房管理:机房是网络设备的核心区域,鹰眼追踪跳线可用
    的头像 发表于 03-21 10:03 394次阅读

    在CYUSB3304-68LTXC上电时将RESETN输入切换为L/H,可以联动控制POWER_DRILL2GO端子吗?

    我有一个关于 HX3 的问题。 如果在 CYUSB3304-68LTXC 上电时将 RESETN 输入切换为 L/H,是否可以联动控制POWER_DRILL2GO端子? 例如,当RESETN输入为L时, POWER_DRILL2GO
    发表于 03-06 07:45

    PMG1 PoR复位时POWER_DRILL2GO什么时候进入?

    我有两个关于 PMG1 PoR 的问题。 1.复位时POWER_DRILL2GO什么时候进入? 也许您想在电压低于预设值时强制 EZ-PD™ PMG1-S1 MCU 设备复位
    发表于 03-06 06:03

    Go配置TM 软件中心用户指南

    电子发烧友网站提供《Go配置TM 软件中心用户指南.pdf》资料免费下载
    发表于 01-03 09:48 0次下载
    <b class='flag-5'>Go</b>配置TM 软件中心用户指南

    使用go语言实现一个grpc拦截器

    在开发grpc服务时,我们经常会遇到一些通用的需求,比如:日志、链路追踪、鉴权等。这些需求可以通过grpc拦截器来实现。本文使用go语言来实现一个 grpc一元模式(Unary)拦截器,上报链路追踪信息。
    的头像 发表于 12-18 10:13 638次阅读
    使用<b class='flag-5'>go</b>语言实现一个grpc拦截器