<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
OpenTracing 是開放式分散式追蹤規範,OpenTracing API 是一致,可表達,與供應商無關的API,用於分散式跟蹤和上下文傳播。
OpenTracing 的使用者端庫以及規範,可以到 Github 中檢視:https://github.com/opentracing/
Jaeger 是 Uber 開源的分散式跟蹤系統,詳細的介紹可以自行查閱資料。
這裡我們需要部署一個 Jaeger 範例,以供微服務以及後面學習需要。
使用 Docker 部署很簡單,只需要執行下面一條命令即可:
docker run -d -p 5775:5775/udp -p 16686:16686 -p 14250:14250 -p 14268:14268 jaegertracing/all-in-one:latest
存取 16686 埠,即可看到 UI 介面。
後面我們生成的鏈路追蹤資訊會推播到此服務,而且可以通過 Jaeger UI 查詢這些追蹤資訊。
這裡,我們主要了解一些 Jaeger Client 的介面和結構體,瞭解一些程式碼的使用。
為了讓讀者方便了解 Trace、Span 等,可以看一下這個 Json 的大概結構:
{ "traceID": "2da97aa33839442e", "spans": [ { "traceID": "2da97aa33839442e", "spanID": "ccb83780e27f016c", "flags": 1, "operationName": "format-string", "references": [...], "tags": [...], "logs": [...], "processID": "p1", "warnings": null }, ... ... ], "processes": { "p1": { "serviceName": "hello-world", "tags": [...] }, "p2": ..., "warnings": null }
建立一個 client1 的專案,然後引入 Jaeger client 包。
go get -u github.com/uber/jaeger-client-go/
然後引入包
import ( "github.com/uber/jaeger-client-go" )
鏈路追蹤中的一個程序使用一個 trace 範例標識,每個服務或函數使用一個 span 標識,jaeger 包中有個函數可以建立空的 trace:
tracer := opentracing.GlobalTracer() // 生產中不要使用
然後就是呼叫鏈中,生成父子關係的 Span:
func main() { tracer := opentracing.GlobalTracer() // 建立第一個 span A parentSpan := tracer.StartSpan("A") defer parentSpan.Finish() // 可手動呼叫 Finish() } func B(tracer opentracing.Tracer,parentSpan opentracing.Span){ // 繼承上下文關係,建立子 span childSpan := tracer.StartSpan( "B", opentracing.ChildOf(parentSpan.Context()), ) defer childSpan.Finish() // 可手動呼叫 Finish() }
每個 span 表示呼叫鏈中的一個結點,每個結點都需要明確父 span。
現在,我們知道了,如何生成 trace{span1,span2}
,且 span1 -> span2
即 span1 呼叫 span2,或 span1 依賴於 span2。
由於服務之間的呼叫是跨程序的,每個程序都有一些特點的標記,為了標識這些程序,我們需要在上下文間、span 攜帶一些資訊。
例如,我們在發起請求的第一個程序中,設定 trace,設定服務名稱等。
// 引入 jaegercfg "github.com/uber/jaeger-client-go/config" cfg := jaegercfg.Configuration{ ServiceName: "client test", // 對其發起請求的的呼叫鏈,叫什麼服務 Sampler: &jaegercfg.SamplerConfig{ Type: jaeger.SamplerTypeConst, Param: 1, }, Reporter: &jaegercfg.ReporterConfig{ LogSpans: true, }, }
Sampler 是使用者端取樣率設定,可以通過 sampler.type
和 sampler.param
屬性選擇取樣型別,後面詳細聊一下。
Reporter 可以設定如何上報,後面獨立小節聊一下這個設定。
傳遞上下文的時候,我們可以列印一些紀錄檔:
jLogger := jaegerlog.StdLogger
設定完畢後就可以建立 tracer 物件了:
tracer, closer, err := cfg.NewTracer( jaegercfg.Logger(jLogger), ) defer closer.Close() if err != nil { }
完整程式碼如下:
import ( "github.com/opentracing/opentracing-go" "github.com/uber/jaeger-client-go" jaegercfg "github.com/uber/jaeger-client-go/config" jaegerlog "github.com/uber/jaeger-client-go/log" ) func main() { cfg := jaegercfg.Configuration{ ServiceName: "client test", // 對其發起請求的的呼叫鏈,叫什麼服務 Sampler: &jaegercfg.SamplerConfig{ Type: jaeger.SamplerTypeConst, Param: 1, }, Reporter: &jaegercfg.ReporterConfig{ LogSpans: true, }, } jLogger := jaegerlog.StdLogger tracer, closer, err := cfg.NewTracer( jaegercfg.Logger(jLogger), ) defer closer.Close() if err != nil { } // 建立第一個 span A parentSpan := tracer.StartSpan("A") defer parentSpan.Finish() B(tracer,parentSpan) } func B(tracer opentracing.Tracer, parentSpan opentracing.Span) { // 繼承上下文關係,建立子 span childSpan := tracer.StartSpan( "B", opentracing.ChildOf(parentSpan.Context()), ) defer childSpan.Finish() }
啟動後:
2021/03/30 11:14:38 Initializing logging reporter 2021/03/30 11:14:38 Reporting span 689df7e83255d05d:75668e8ed5ec61da:689df7e83255d05d:1 2021/03/30 11:14:38 Reporting span 689df7e83255d05d:689df7e83255d05d:0000000000000000:1 2021/03/30 11:14:38 DEBUG: closing tracer 2021/03/30 11:14:38 DEBUG: closing reporter
sampler 設定程式碼範例:
Sampler: &jaegercfg.SamplerConfig{ Type: jaeger.SamplerTypeConst, Param: 1, }
這個 sampler 可以使用 jaegercfg.SamplerConfig
,通過 type
、param
兩個欄位來設定取樣器。
為什麼要設定取樣器?因為服務中的請求千千萬萬,如果每個請求都要記錄追蹤資訊並行送到 Jaeger 後端,那麼面對高並行時,記錄鏈路追蹤以及推播追蹤資訊消耗的效能就不可忽視,會對系統帶來較大的影響。當我們設定 sampler 後,jaeger 會根據當前設定的取樣策略做出取樣行為。
詳細可以參考:https://www.jaegertracing.io/docs/1.22/sampling/
jaegercfg.SamplerConfig 結構體中的欄位 Param 是設定取樣率或速率,要根據 Type 而定。
下面對其關係進行說明:
Type | Param | 說明 |
---|---|---|
"const" | 0或1 | 取樣器始終對所有 tracer 做出相同的決定;要麼全部取樣,要麼全部不取樣 |
"probabilistic" | 0.0~1.0 | 取樣器做出隨機取樣決策,Param 為取樣概率 |
"ratelimiting" | N | 取樣器一定的恆定速率對tracer進行取樣,Param=2.0,則限制每秒採集2條 |
"remote" | 無 | 取樣器請諮詢Jaeger代理以獲取在當前服務中使用的適當取樣策略。 |
sampler.Type="remote"
/sampler.Type=jaeger.SamplerTypeRemote
是取樣器的預設值,當我們不做設定時,會從 Jaeger 後端中央設定甚至動態地控制服務中的取樣策略。
看一下 ReporterConfig 的定義。
type ReporterConfig struct { QueueSize int `yaml:"queueSize"` BufferFlushInterval time.Duration LogSpans bool `yaml:"logSpans"` LocalAgentHostPort string `yaml:"localAgentHostPort"` DisableAttemptReconnecting bool `yaml:"disableAttemptReconnecting"` AttemptReconnectInterval time.Duration CollectorEndpoint string `yaml:"collectorEndpoint"` User string `yaml:"user"` Password string `yaml:"password"` HTTPHeaders map[string]string `yaml:"http_headers"` }
Reporter 設定使用者端如何上報追蹤資訊的,所有欄位都是可選的。
這裡我們介紹幾個常用的設定欄位。
QUEUESIZE,設定佇列大小,儲存取樣的 span 資訊,佇列滿了後一次性傳送到 jaeger 後端;defaultQueueSize 預設為 100;
BufferFlushInterval 強制清空、推播佇列時間,對於流量不高的程式,佇列可能長時間不能滿,那麼設定這個時間,超時可以自動推播一次。對於高並行的情況,一般佇列很快就會滿的,滿了後也會自動推播。預設為1秒。
LogSpans 是否把 Log 也推播,span 中可以攜帶一些紀錄檔資訊。
LocalAgentHostPort 要推播到的 Jaeger agent,預設埠 6831,是 Jaeger 接收壓縮格式的 thrift 協定的資料埠。
CollectorEndpoint 要推播到的 Jaeger Collector,用 Collector 就不用 agent 了。
例如通過 http 上傳 trace:
Reporter: &jaegercfg.ReporterConfig{ LogSpans: true, CollectorEndpoint: "http://127.0.0.1:14268/api/traces", },
據黑洞大佬的提示,HTTP 走的就是 thrift,而 gRPC 是 .NET 特供,所以 reporter 格式只有一種,而且填寫 CollectorEndpoint,我們注意要填寫完整的資訊。
完整程式碼測試:
import ( "bufio" "github.com/opentracing/opentracing-go" "github.com/uber/jaeger-client-go" jaegercfg "github.com/uber/jaeger-client-go/config" jaegerlog "github.com/uber/jaeger-client-go/log" "os" ) func main() { var cfg = jaegercfg.Configuration{ ServiceName: "client test", // 對其發起請求的的呼叫鏈,叫什麼服務 Sampler: &jaegercfg.SamplerConfig{ Type: jaeger.SamplerTypeConst, Param: 1, }, Reporter: &jaegercfg.ReporterConfig{ LogSpans: true, CollectorEndpoint: "http://127.0.0.1:14268/api/traces", }, } jLogger := jaegerlog.StdLogger tracer, closer, _ := cfg.NewTracer( jaegercfg.Logger(jLogger), ) // 建立第一個 span A parentSpan := tracer.StartSpan("A") // 呼叫其它服務 B(tracer, parentSpan) // 結束 A parentSpan.Finish() // 結束當前 tracer closer.Close() reader := bufio.NewReader(os.Stdin) _, _ = reader.ReadByte() } func B(tracer opentracing.Tracer, parentSpan opentracing.Span) { // 繼承上下文關係,建立子 span childSpan := tracer.StartSpan( "B", opentracing.ChildOf(parentSpan.Context()), ) defer childSpan.Finish() }
執行後輸出結果:
2021/03/30 15:04:15 Initializing logging reporter 2021/03/30 15:04:15 Reporting span 715e0af47c7d9acb:7dc9a6b568951e4f:715e0af47c7d9acb:1 2021/03/30 15:04:15 Reporting span 715e0af47c7d9acb:715e0af47c7d9acb:0000000000000000:1 2021/03/30 15:04:15 DEBUG: closing tracer 2021/03/30 15:04:15 DEBUG: closing reporter 2021/03/30 15:04:15 DEBUG: flushed 1 spans 2021/03/30 15:04:15 DEBUG: flushed 1 spans
開啟 Jaeger UI,可以看到已經推播完畢(http://127.0.0.1:16686)。
這時,我們可以抽象程式碼程式碼範例:
func CreateTracer(servieName string) (opentracing.Tracer, io.Closer, error) { var cfg = jaegercfg.Configuration{ ServiceName: servieName, Sampler: &jaegercfg.SamplerConfig{ Type: jaeger.SamplerTypeConst, Param: 1, }, Reporter: &jaegercfg.ReporterConfig{ LogSpans: true, // 按實際情況替換你的 ip CollectorEndpoint: "http://127.0.0.1:14268/api/traces", }, } jLogger := jaegerlog.StdLogger tracer, closer, err := cfg.NewTracer( jaegercfg.Logger(jLogger), ) return tracer, closer, err }
這樣可以複用程式碼,呼叫函數建立一個新的 tracer。這個記下來,後面要用。
前面介紹瞭如何設定 tracer 、推播資料到 Jaeger Collector,接下來我們聊一下 Span。請看圖。
下圖是一個由使用者 X 請求發起的,穿過多個服務的分散式系統,A、B、C、D、E 表示不同的子系統或處理過程。
在這個圖中, A 是前端,B、C 是中間層、D、E 是 C 的後端。這些子系統通過 rpc 協定連線,例如 gRPC。
一個簡單實用的分散式鏈路追蹤系統的實現,就是對伺服器上每一次請求以及響應收集跟蹤識別符號(message identifiers)和時間戳(timestamped events)。
這裡,我們只需要記住,從 A 開始,A 需要依賴多個服務才能完成任務,每個服務可能是一個程序,也可能是一個程序中的另一個函數。這個要看你程式碼是怎麼寫的。後面會詳細說一下如何定義這種關係,現在大概瞭解一下即可。
如果有了解過 Jaeger 或讀過 分散式鏈路追蹤框架的基本實現原理 ,那麼已經大概瞭解的 Jaeger 的工作原理。
jaeger 是分散式鏈路追蹤工具,如果不用在跨程序上,那麼 Jaeger 就失去了意義。而微服務中跨程序呼叫,一般有 HTTP 和 gRPC 兩種,下面將來講解如何在 HTTP、gPRC 呼叫中傳遞 Jaeger 的 上下文。
A、B 兩個程序,A 通過 HTTP 呼叫 B 時,通過 Http Header 攜帶 trace 資訊(稱為上下文),然後 B 程序接收後,解析出來,在建立 trace 時跟傳遞而來的 上下文關聯起來。
一般使用中介軟體來處理別的程序傳遞而來的上下文。inject
函數打包上下文到 Header 中,而 extract
函數則將其解析出來。
這裡我們分為兩步,第一步從 A 程序中傳遞上下文資訊到 B 程序,為了方便演示已經實踐,我們使用 client-webserver 的形式,編寫程式碼。
在 A 程序新建一個方法:
// 請求遠端服務,獲得使用者資訊 func GetUserInfo(tracer opentracing.Tracer, parentSpan opentracing.Span) { // 繼承上下文關係,建立子 span childSpan := tracer.StartSpan( "B", opentracing.ChildOf(parentSpan.Context()), ) url := "http://127.0.0.1:8081/Get?username=痴者工良" req,_ := http.NewRequest("GET", url, nil) // 設定 tag,這個 tag 我們後面講 ext.SpanKindRPCClient.Set(childSpan) ext.HTTPUrl.Set(childSpan, url) ext.HTTPMethod.Set(childSpan, "GET") tracer.Inject(childSpan.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header)) resp, _ := http.DefaultClient.Do(req) _ = resp // 丟掉 defer childSpan.Finish() }
然後複用前面提到的 CreateTracer
函數。
main 函數改成:
func main() { tracer, closer, _ := CreateTracer("UserinfoService") // 建立第一個 span A parentSpan := tracer.StartSpan("A") // 呼叫其它服務 GetUserInfo(tracer, parentSpan) // 結束 A parentSpan.Finish() // 結束當前 tracer closer.Close() reader := bufio.NewReader(os.Stdin) _, _ = reader.ReadByte() }
完整程式碼可參考:https://github.com/whuanle/DistributedTracingGo/issues/1
伺服器端我們使用 gin 來搭建。
新建一個 go 專案,在 main.go 目錄中,執行 go get -u github.com/gin-gonic/gin
。
建立一個函數,該函數可以從建立一個 tracer,並且繼承其它程序傳遞過來的上下文資訊。
// 從上下文中解析並建立一個新的 trace,獲得傳播的 上下文(SpanContext) func CreateTracer(serviceName string, header http.Header) (opentracing.Tracer,opentracing.SpanContext, io.Closer, error) { var cfg = jaegercfg.Configuration{ ServiceName: serviceName, Sampler: &jaegercfg.SamplerConfig{ Type: jaeger.SamplerTypeConst, Param: 1, }, Reporter: &jaegercfg.ReporterConfig{ LogSpans: true, // 按實際情況替換你的 ip CollectorEndpoint: "http://127.0.0.1:14268/api/traces", }, } jLogger := jaegerlog.StdLogger tracer, closer, err := cfg.NewTracer( jaegercfg.Logger(jLogger), ) // 繼承別的程序傳遞過來的上下文 spanContext, _ := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(header)) return tracer, spanContext, closer, err }
為了解析 HTTP 傳遞而來的 span 上下文,我們需要通過中介軟體來解析了處理一些細節。
func UseOpenTracing() gin.HandlerFunc { handler := func(c *gin.Context) { // 使用 opentracing.GlobalTracer() 獲取全域性 Tracer tracer,spanContext, closer, _ := CreateTracer("userInfoWebService", c.Request.Header) defer closer.Close() // 生成依賴關係,並新建一個 span、 // 這裡很重要,因為生成了 References []SpanReference 依賴關係 startSpan:= tracer.StartSpan(c.Request.URL.Path,ext.RPCServerOption(spanContext)) defer startSpan.Finish() // 記錄 tag // 記錄請求 Url ext.HTTPUrl.Set(startSpan, c.Request.URL.Path) // Http Method ext.HTTPMethod.Set(startSpan, c.Request.Method) // 記錄元件名稱 ext.Component.Set(startSpan, "Gin-Http") // 在 header 中加上當前程序的上下文資訊 c.Request=c.Request.WithContext(opentracing.ContextWithSpan(c.Request.Context(),startSpan)) // 傳遞給下一個中介軟體 c.Next() // 繼續設定 tag ext.HTTPStatusCode.Set(startSpan, uint16(c.Writer.Status())) } return handler }
別忘記了 API 服務:
func GetUserInfo(ctx *gin.Context) { userName := ctx.Param("username") fmt.Println("收到請求,使用者名稱稱為:", userName) ctx.String(http.StatusOK, "他的部落格是 https://whuanle.cn") }
然後是 main 方法:
func main() { r := gin.Default() // 插入中介軟體處理 r.Use(UseOpenTracing()) r.GET("/Get",GetUserInfo) r.Run("0.0.0.0:8081") // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080") }
完整程式碼可參考:https://github.com/whuanle/DistributedTracingGo/issues/2
分別啟動 webserver、client,會發現列印紀錄檔。並且開啟 jaerger ui 介面,會出現相關的追蹤資訊。
Jaeger 的鏈路追蹤中,可以攜帶 Tag 和 Log,他們都是鍵值對的形式:
{ "key": "http.method", "type": "string", "value": "GET" },
Tag 設定方法是 ext.xxxx
,例如 :
ext.HTTPUrl.Set(startSpan, c.Request.URL.Path)
因為 opentracing 已經規定了所有的 Tag 型別,所以我們只需要呼叫 ext.xxx.Set()
設定即可。
前面寫範例的時候忘記把紀錄檔也加一下了。。。紀錄檔其實很簡單的,通過 span 物件呼叫函數即可設定。
範例(在中介軟體裡面加一下):
startSpan.LogFields( log.String("event", "soft error"), log.String("type", "cache timeout"), log.Int("waited.millis", 1500))
ref 就是多個 span 之間的關係。span 可以是跨程序的,也可以是一個程序內的不同函數中的。
其中 span 的依賴關係表示範例:
"references": [ { "refType": "CHILD_OF", "traceID": "33ba35e7cc40172c", "spanID": "1c7826fa185d1107" }]
spanID 為其依賴的父 span。
可以看下面這張圖。
一個程序中的 tracer 可以包裝一些程式碼和操作,為多個 span 生成一些資訊,或建立父子關係。
而 遠端請求中傳遞的是 SpanContext,傳遞後,遠端服務也建立新的 tracer,然後從 SpanContext 生成 span 依賴關係。
子 span 中,其 reference 列表中,會帶有 父 span 的 span id。
到此這篇關於Jaeger Client Go入門並實現鏈路追蹤的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45