Go2sky -- Golang用skywalking实现全链路追踪
一、背景介紹
? ? ? ? 由于在微服務(wù)架構(gòu)中,服務(wù)之間的調(diào)用關(guān)系多而復(fù)雜,所以有必要對(duì)它們之間的調(diào)用鏈路進(jìn)行追蹤、分析,判斷是哪里出了問(wèn)題,或者哪里耗時(shí)過(guò)多。
? ? ? ? 最近接到了這個(gè)需求,添加全鏈路追蹤,所以研究并實(shí)踐了一下,還不太深刻,若有錯(cuò)誤的地方歡迎指正。
二、OpenTracing相關(guān)概念介紹
? ? ? ? 首先,要實(shí)現(xiàn)全鏈路追蹤,必須先理解OpenTracing的一些基本概念。OpenTracing為分布式鏈路追蹤制定了一個(gè)統(tǒng)一的標(biāo)準(zhǔn)。只要是按照此標(biāo)準(zhǔn)實(shí)現(xiàn)的服務(wù),就能夠完整的進(jìn)行分布式追蹤。
? ? 1. Span
? ? ? ? Span可以被翻譯為跨度,可以理解為一次方法調(diào)用,一個(gè)程序塊的調(diào)用,或者一次RPC/數(shù)據(jù)庫(kù)訪問(wèn)。
? ??? ? Span之間是有關(guān)系的,child of 和 follow of。比如一次RPC的調(diào)用,RPC客戶端和服務(wù)端的span就形成了父子關(guān)系。
? ? 2. Trace
? ? ? ? Trace表示一個(gè)調(diào)用鏈,比如在分布式服務(wù)中,一個(gè)客戶端的請(qǐng)求,在后臺(tái)可能經(jīng)過(guò)了層層的調(diào)用,那么每一次調(diào)用就相當(dāng)于一個(gè)span,而這一整條調(diào)用鏈路,可以理解成一個(gè)trace。
? ??? ? Trace有一個(gè)全局唯一的ID。
三、Go2sky簡(jiǎn)介
? ??? ? Go2sky是Golang提供給開(kāi)發(fā)者實(shí)現(xiàn)SkyWalking agent探針的包,可以通過(guò)它來(lái)實(shí)現(xiàn)向SkyWalking Collector上報(bào)數(shù)據(jù)。
? ??? ? 快速入門:GitHub-Go2Sky
? ??? ??1. 創(chuàng)建Reporter、Tracer
? ??? ??? ? SkyWalking支持http和gRpc兩種方式收集數(shù)據(jù),在Go2sky中,想要上報(bào)數(shù)據(jù),先創(chuàng)建一個(gè)GRPCReporter.
? ? ? ??? ? Tracer代表了本程序中的一條調(diào)用鏈路。
? ? ? ? ? ??
? ? ? ? ? ? 本程序中的所有span都會(huì)與服務(wù)名為example的服務(wù)相關(guān)聯(lián)。
?
? ? ? ? 2. 創(chuàng)建Span
? ? ? ? ? ? Span有三種類型:LocalSpan、EntrySpan、ExitSpan。
? ? ? ? ? ? LocalSpan:可以用來(lái)表示本程序內(nèi)的一次調(diào)用。
? ? ? ? ? ? EntrySpan:用來(lái)從下游服務(wù)提取context信息。
? ? ? ? ? ? ExitSpan:? 用來(lái)向上游服務(wù)注入context信息。
? ? ? ? ? ??
? ? ? ? ? ? 在創(chuàng)建span時(shí),上下文參數(shù)傳入context.Backround() ,就表示它是root span。
?
? ? ? ? 3. 創(chuàng)建sub span
? ? ? ? ? ? 在創(chuàng)建LocalSpan和EntrySpan的時(shí)候,返回值會(huì)返回一個(gè)context信息(ctx),通過(guò)它來(lái)創(chuàng)建sub span,來(lái)與root span形成父子關(guān)系。
? ? ? ? ? ??
? ? ? ? 4. End Span
? ? ? ? ? ? 必須要確保結(jié)束span,它們才可以被上傳給skywalking。
? ? ? ? ? ??
?
? ? ? ? 5. 關(guān)聯(lián)Span
? ? ? ? ? ? 我們?cè)诔绦蛑袆?chuàng)建的span,是怎么關(guān)聯(lián)起來(lái)形成一個(gè)調(diào)用鏈的呢。
? ? ? ? ? ? 在同一個(gè)程序中,向上面那樣,創(chuàng)建root span 和 sub span即可。
? ? ? ? ? ? 在不同的程序中,下游服務(wù)使用ExitSpan向上游注入context信息,上游服務(wù)使用EntrySpan從下游提取context信息。Entry和Exit使得skywalking可以分析,從而生成拓?fù)鋱D和度量指標(biāo)。
? ? ? ? ? ??
?
四、實(shí)戰(zhàn) -- 跨程序追蹤RPC調(diào)用
? ? ? ? 看到這里,有了基本的概念,以及Go2sky的基本用法,但是仍然不能夠?qū)PC進(jìn)行有效的追蹤。
? ? ? ? 因?yàn)樯蠄D中的例子使用的是http請(qǐng)求,它本身就封裝了Get和Set方法,可以很輕松的注入和提取context信息。但是RPC請(qǐng)求并沒(méi)有,想要追蹤別的類型跨程序的調(diào)用也沒(méi)有。
? ? ? ? 所以我們要自己將context信息在進(jìn)行調(diào)用的時(shí)候,從下游服務(wù)傳給上游服務(wù),然后自己定義注入和提取的方法。
? ? ? ? 下面只貼出了鏈路追蹤部分的代碼,其它的比如rpc相關(guān)的部分代碼省略了(不然又臭又長(zhǎng),還難看)。
? ? 1. Client端 (下游服務(wù))
? ? ? ? 定義請(qǐng)求信息的結(jié)構(gòu)體:
type Req struct {A intHeader string // 添加此字段,用于傳遞context信息 }? ? ? ? 定義context信息的注入方法:
func (p *Req) Set(key, value string) error {p.Header = fmt.Sprintf("%s:%s", key, value)return nil }? ? ? ? 創(chuàng)建reporter和tracer:
r, err = reporter.NewGRPCReporter("192.168.204.130:11800") if err != nil {logs.Info("[New GRPC Reporter Error]: [%v]", err)return }// 這個(gè)程序中所有的span都會(huì)跟服務(wù)名叫RTS_Test的服務(wù)關(guān)聯(lián)起來(lái) tracer, err = go2sky.NewTracer("RTS_Test", go2sky.WithReporter(r), go2sky.WithInstance("RTS_Test_1")) if err != nil {logs.Info("[New Tracer Error]: [%v]", err)return } tracer.WaitUntilRegister()? ? ? ? rpc調(diào)用以及創(chuàng)建span:
? ? ? ? 在創(chuàng)建ExitSpan的時(shí)候,傳入了一個(gè)函數(shù),函數(shù)實(shí)現(xiàn)就是我們定義的如何注入context信息的函數(shù)。
? ? ? ? 它會(huì)在CreateExitSpan()函數(shù)的內(nèi)部被調(diào)用,header的值不需要我們管,它在CreateExitSpan函數(shù)內(nèi)部生成的。我們只需要負(fù)責(zé)在上游服務(wù)中把它提取出來(lái)即可。
? ? ? ? 我目前的理解是,只需要在下游服務(wù)中負(fù)責(zé)把這個(gè)header按一定規(guī)則拼接,傳給上游服務(wù),然后在上游服務(wù)中按照規(guī)則將header解析出來(lái),skywalking通過(guò)分析,即可將上下游的span關(guān)聯(lián)起來(lái)。
func OnSnapshot() {// client := GetClinet()// 表示收到客戶端請(qǐng)求,因?yàn)橹蛔粉櫤笈_(tái)服務(wù)之間的鏈路,所以這里不需要提取context信息span2, ctx, err := tracer.CreateEntrySpan(context.Background(), "/API/Snapshot", func() (string, error){return "", nil})if err != nil {logs.Info("[Create Exit Span Error]: [%v]", err)return}span2.SetComponent(5200)// 表示rpc調(diào)用的span,這里需要向上游服務(wù)注入context信息,即參數(shù)中的headerreq := Req{3, ""}span1, err := tracer.CreateExitSpan(ctx, "/Service/OnSnapshot", "RTS_Server", func(header string) error{return req.Set(propagation.Header, header)})if err != nil {logs.Info("[Create Exit Span Error]: [%v]", err)return}span1.SetComponent(5200) // Golang程序使用范圍是[5000, 6000),還要在skywalking中配置,config目錄下的component-libraries.yml文件var res Res// rpc調(diào)用err = conn.Call("Req.Snapshot", req, &res)if err != nil {logs.Info("[RPC Call Snapshot Error]: [%v]", err)return} else {logs.Info("[RPC Call Snapshot Success]: [%s]", res)}span1.End()span2.End() // 一定要確保span被結(jié)束// s1 := ReportedSpan(span1)// s2 := ReportedSpan(span2)// spans := []go2sky.ReportedSpan{s1, s2}// r.Send(spans) }?
? ? 2. Server端 (上游服務(wù))
? ? ? ? 定義請(qǐng)求信息的結(jié)構(gòu)體:
type ReqBody struct {A intHeader string }? ? ? ? 定義context信息的提取方法:
func (p *ReqBody) Get(key string) string {subs := strings.Split(p.Header, ":")if len(subs) != 2 || subs[0] != key {return ""}return subs[1] }? ? ? ? 創(chuàng)建reporter和tracer:
r, err = reporter.NewGRPCReporter("192.168.204.130:11800") if err != nil {logs.Info("[New GRPC Reporter Error]: [%v]\n", err)return }tracer, err = go2sky.NewTracer("Service_Test", go2sky.WithReporter(r), go2sky.WithInstance("Service_Test_1")) if err != nil {logs.Info("[New Tracer Error]: [%v]\n", err)return } tracer.WaitUntilRegister()? ? ? ? 創(chuàng)建span:
? ? ? ? 在創(chuàng)建EntrySpan時(shí),調(diào)用Get()方法提取context信息
func (p *Req)Snapshot(req ReqBody, res *Res) error {// 表示收到 rpc 客戶端的請(qǐng)求,這里需要提取context信息span1, ctx, err := tracer.CreateEntrySpan(context.Background(), "/Service/OnSnapshot/QueringSnapshot", func() (string, error){return req.Get(propagation.Header), nil})if err != nil {logs.Info("[Create Exit Span Error]: [%v]\n", err)return err}span1.SetComponent(5200)// span1.SetPeer("Service_Test")// 表示去請(qǐng)求了一次數(shù)據(jù)庫(kù)span2, err := tracer.CreateExitSpan(ctx, "/database/QuerySnapshot", "APIService", func(header string) error {return nil})span2.SetComponent(5200)time.Sleep(time.Millisecond * 6)*res = "Return Snapshot Info"span2.End()span1.End()// s1 := ReportedSpan(span1)// s2 := ReportedSpan(span2)// spans := []go2sky.ReportedSpan{s1, s2}// r.Send(spans)return nil }?
? ? 3. 結(jié)果展示?
? ? ? ? 鏈路追蹤:
? ? ? ??
? ? ? ? ?拓?fù)鋱D:
? ? ? ??
總結(jié)
以上是生活随笔為你收集整理的Go2sky -- Golang用skywalking实现全链路追踪的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: C# Char类
- 下一篇: 技能高考计算机专业考什么,2016年技能