前言
鏈路追蹤是每個微服務(wù)架構(gòu)下必備的利器,go-zero 當(dāng)然早已經(jīng)為我們考慮好了,只需要在配置中添加配置即可使用。
關(guān)于 go-zero 如何追蹤的原理追溯,之前已經(jīng)有同學(xué)分享,這里我就不再多說,如果有想了解的同學(xué)去 https://mp.weixin.qq.com/s/hJEWcWc3PnGfWfbPCHfM9g 這個鏈接看就好了。默認(rèn)會在 api 的中間件與 rpc 的 interceptor 添加追蹤,如果有不了解 go-zero 默認(rèn)如何使用默認(rèn)的鏈路追蹤的,請移步我的開源項目 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 默認(rèn)在 api 的 middleware 與 rpc 的 interceptor 中幫我們集成好的鏈路追蹤,我們想自己在某些本地方法添加鏈路追蹤代碼或者我們想在 api 發(fā)送一個消息給 mq 服務(wù)時候想把整個鏈路包含 mq 的 producer、consumer 穿起來,在 go-zero 中該如何做。
場景
我們先簡單講一下我們的小 demo 的場景,一個請求進(jìn)來調(diào)用 api 的 Login 方法,在 Login 方法中先調(diào)用 rpc 的 GetUserByMobile 方法,之后在調(diào)用 api 本地的 local 方法,緊接著調(diào)用 rabbitmq 傳遞消息到 mq 服務(wù)。
go-zero 默認(rèn)集成了 jaeger、zinpink,這里我們就以 jaeger 為例
我們希望看到的鏈路是
api.Login -> rpc.GetUserByMobile
也就是 api 衍生出來三條子鏈路,api.producerMq 有一條調(diào)用 mq.Consumer 的子鏈路。
我們想要將一個方法添加到鏈路中需要兩個因素,一個 traceId,一個span,當(dāng)我們在同一個 traceId 下開啟 span 把相關(guān)的 span 都串聯(lián)起來,如果想形成父子關(guān)系,就要把 span 之間相互串聯(lián)起來,因為「微服務(wù)實踐」公眾號中講解原理太多,我這里就簡單提一下不涉及過多,如果不是特別熟悉原理可以看文章開頭推薦的文章,這里我們只需要知道 traceId 與 spanId 關(guān)系就好。
核心業(yè)務(wù)代碼
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()
//執(zhí)行你的代碼.....
}
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 默認(rèn)集成
當(dāng)一個請求進(jìn)入 api 后,我們可以在 go-zero 源碼中查看到 https://github.com/zeromicro/go-zero/blob/master/rest/engine.go#L92。go-zero 已經(jīng)在 api 的 middleware 中幫我們添加了第一層 trace,當(dāng)進(jìn)入 Login 方法內(nèi),我們調(diào)用了 rpc 的 GetUserByMobile 方法,通過 go-zero 的源碼 https://github.com/zeromicro/go-zero/blob/master/zrpc/internal/rpcserver.go#L55 可以看到在 rpc 的 interceptor 也默認(rèn)幫我們添加好了,這兩層都是 go-zero 默認(rèn)幫我們做好的。
2、本地方法
當(dāng)調(diào)用完 rpc 的 GetUserByMobile 之后,api 調(diào)用了本地的 local,如果我們想在整個鏈路上體現(xiàn)出來調(diào)用了本地 local 方法,那默認(rèn)的 go-zero 是沒有幫我們做的,需要我們手動來添加。
tracer:=otel.GetTracerProvider().Tracer(trace.TraceName)
_,span :=tracer.Start(l.ctx,"local",oteltrace.WithSpanKind(oteltrace.SpanKindInternal))
deferspan.End()
//執(zhí)行你的代碼.....
我們通過上面代碼拿到 tracer,ctx 之后開啟一個 local 的 span,因為 start 時候會從 ctx 獲取父 span 所以會將 local 方法與 Login 串聯(lián)起父子調(diào)用關(guān)系,這樣就將本次操作加入了這個鏈路
3、mq 的 producer 到 mq 的 consumer
我們在mq傳遞中如何串聯(lián)起來這個鏈路呢?也就是形成 api.Login->api.producer->mq.Consumer。
想一下原理,雖然跨越了網(wǎng)絡(luò),api 可以通過 header 傳遞,rpc 可以通過 metadata 傳遞,那么 mq 是不是也可以通過 header、body 傳遞就可以了,按照這個想法來看下我門的代碼。
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,然后開啟一個 producer 的 span,跟 local 方法一樣,我們開啟 producer 的 span 時候也是通過 ctx 獲取到上一級父級 span,這樣就可以將 producer 的 span 與 Login 形成父子 span 調(diào)用關(guān)系,那我們想將 producer 的 span 與 mq 的 consumer 中的 span 形成調(diào)用父子關(guān)系怎么做?我們將 api.producer 的 spanCtx 注入到 carrier 中,這里我們通過 mq 的 body 將 carrier 發(fā)送給 consumer,發(fā)送完成我們 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.Start、wireContext 創(chuàng)建 consumer 的 span,這樣 consumer 就是 api.producer 的子 span,就形成了調(diào)用鏈路關(guān)系,最終我們得到的關(guān)系就是
api.Login -> rpc.GetUserByMobile
讓我們來調(diào)用一下 Logic 方法,看下 jaeger 中的鏈路如果與我們預(yù)想的鏈路一致,so happy~

項目地址
go-zero 微服務(wù)框架:https://github.com/zeromicro/go-zero
go-zero 微服務(wù)最佳實踐項目:https://github.com/Mikaelemmmm/go-zero-looklook
歡迎使用 go-zero 并 star 支持我們!
審核編輯 :李倩
-
Go
+關(guān)注
關(guān)注
0文章
45瀏覽量
12511 -
微服務(wù)
+關(guān)注
關(guān)注
0文章
147瀏覽量
8019
原文標(biāo)題:玩轉(zhuǎn) Go 鏈路追蹤
文章出處:【微信號:OSC開源社區(qū),微信公眾號:OSC開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
目標(biāo)追蹤的簡易實現(xiàn):模板匹配
為什么可追溯性是汽車制造的關(guān)鍵
NFC技術(shù)如何破解電子制造領(lǐng)域的效率瓶頸與追溯難題
【作品合集】玄鐵BPI-CanMV-K230D-Zero開發(fā)板測評
開源Made with KiCad(134):Icepi Zero - 基于Lattice ECP5的便攜FPGA開發(fā)板
資產(chǎn)追蹤與室內(nèi)導(dǎo)航
【開源分享】:開源小巧的FPGA開發(fā)板——Icepi Zero
兩個關(guān)于PMG1 PoR的問題求解
用 樹莓派 Zero 打造的智能漫游車!
適合于藥品追溯智能一體機的工控主板
RFID電子標(biāo)簽在石油鉆桿追溯管理中的應(yīng)用
智譜推出深度推理模型GLM-Zero預(yù)覽版
智譜GLM-Zero深度推理模型預(yù)覽版正式上線
?Banana Pi BPi-M4 Zero 開源硬件開發(fā)板評測試: 全志科技H618 方案設(shè)計

關(guān)于go-zero如何追蹤的原理追溯
評論