在微服務架構的系統中,請求在各服務之間流轉,調用鏈錯綜複雜,一旦出現了問題和異常,很難追查定位,這個時候就須要鏈路追蹤來幫忙了。鏈路追蹤系統能追蹤並記錄請求在系統中的調用順序,調用時間等一系列關鍵信息,從而幫助咱們定位異常服務和發現性能瓶頸。git
Opentracing是分佈式鏈路追蹤的一種規範標準,是CNCF(雲原生計算基金會)下的項目之一。和通常的規範標準不一樣,Opentracing不是傳輸協議,消息格式層面上的規範標準,而是一種語言層面上的API標準。以Go語言爲例,只要某鏈路追蹤系統實現了Opentracing規定的接口(interface),符合Opentracing定義的表現行爲,那麼就能夠說該應用符合Opentracing標準。這意味着開發者只需修改少許的配置代碼,就能夠在符合Opentracing標準的鏈路追蹤系統之間自由切換。github
github.com/opentracing…docker
在使用Opentracing來實現全鏈路追蹤前,有必要先了解一下它所定義的數據模型。api
Span是一條追蹤鏈路中的基本組成要素,一個span表示一個獨立的工做單元,好比能夠表示一次函數調用,一次http請求等等。span會記錄以下基本要素:bash
Tags以K/V鍵值對的形式保存用戶自定義標籤,主要用於鏈路追蹤結果的查詢過濾。例如: http.method="GET",http.status_code=200
。其中key值必須爲字符串,value必須是字符串,布爾型或者數值型。 span中的tag僅本身可見,不會隨着 SpanContext傳遞給後續span。 例如:網絡
span.SetTag("http.method","GET")
span.SetTag("http.status_code",200)
複製代碼
Logs與tags相似,也是K/V鍵值對形式。與tags不一樣的是,logs還會記錄寫入logs的時間,所以logs主要用於記錄某些事件發生的時間。logs的key值一樣必須爲字符串,但對value類型則沒有限制。例如:架構
span.LogFields(
log.String("event", "soft error"),
log.String("type", "cache timeout"),
log.Int("waited.millis", 1500),
)
複製代碼
Opentracing列舉了一些慣用的Tags和Logs: github.com/opentracing…異步
SpanContext攜帶着一些用於跨服務通訊的(跨進程)數據,主要包含:分佈式
span_id,trace_id
。Baggage Items與tags相似,也是K/V鍵值對。與tags不一樣的是:函數
Opentracing定義了兩種引用關係:ChildOf
和FollowFrom
。
ChildOf: 父span的執行依賴子span的執行結果時,此時子span對父span的引用關係是ChildOf
。好比對於一次RPC調用,服務端的span(子span)與客戶端調用的span(父span)是ChildOf
關係。
FollowFrom:父span的執不依賴子span執行結果時,此時子span對父span的引用關係是FollowFrom
。FollowFrom
經常使用於異步調用的表示,例如消息隊列中consumer
span與producer
span之間的關係。
Trace表示一次完整的追蹤鏈路,trace由一個或多個span組成。下圖示例表示了一個由8個span組成的trace:
[Span A] ←←←(the root span)
|
+------+------+
| |
[Span B] [Span C] ←←←(Span C is a `ChildOf` Span A)
| |
[Span D] +---+-------+
| |
[Span E] [Span F] >>> [Span G] >>> [Span H]
↑
↑
↑
(Span G `FollowsFrom` Span F)
複製代碼
時間軸的展示方式會更容易理解:
––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time
[Span A···················································]
[Span B··············································]
[Span D··········································]
[Span C········································]
[Span E·······] [Span F··] [Span G··] [Span H··]
複製代碼
示例來源: github.com/opentracing…
對Opentracing的概念有初步瞭解後,下面使用Jaeger來演示如何在程序中使用實現鏈路追蹤。
更多更詳細的示例可參考: Opentracing Go Tutorial
Jaeger\ˈyā-gər\ 是Uber開源的分佈式追蹤系統,是遵循Opentracing的系統之一,也是CNCF項目。本篇將使用Jaeger來演示如何在系統中引入分佈式追蹤。
Jaeger提供了all-in-one鏡像,方便咱們快速開始測試:
$ docker run -d --name jaeger \
-e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
-p 5775:5775/udp \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \
-p 14268:14268 \
-p 9411:9411 \
jaegertracing/all-in-one:1.14
複製代碼
鏡像啓動後,經過http://localhost:16686能夠打開Jaeger UI。
下載客戶端library:
go get github.com/jaegertracing/jaeger-client-go
複製代碼
初始化Jaeger tracer:
import (
"context"
"errors"
"fmt"
"io"
"time"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/log"
"github.com/uber/jaeger-client-go"
jaegercfg "github.com/uber/jaeger-client-go/config"
)
// initJaeger 將jaeger tracer設置爲全局tracer
func initJaeger(service string) io.Closer {
cfg := jaegercfg.Configuration{
// 將採樣頻率設置爲1,每個span都記錄,方便查看測試結果
Sampler: &jaegercfg.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &jaegercfg.ReporterConfig{
LogSpans: true,
// 將span發往jaeger-collector的服務地址
CollectorEndpoint: "http://localhost:14268/api/traces",
},
}
closer, err := cfg.InitGlobalTracer(service, jaegercfg.Logger(jaeger.StdLogger))
if err != nil {
panic(fmt.Sprintf("ERROR: cannot init Jaeger: %v\n", err))
}
return closer
}
複製代碼
建立tracer,生成root span:
func main() {
closer := initJaeger("in-process")
defer closer.Close()
// 獲取jaeger tracer
t := opentracing.GlobalTracer()
// 建立root span
sp := t.StartSpan("in-process-service")
// main執行完結束這個span
defer sp.Finish()
// 將span傳遞給Foo
ctx := opentracing.ContextWithSpan(context.Background(), sp)
Foo(ctx)
}
複製代碼
上述代碼建立了一個root span,並將該span經過context
傳遞給Foo
方法,以便在Foo
方法中將追蹤鏈繼續延續下去:
func Foo(ctx context.Context) {
// 開始一個span, 設置span的operation_name=Foo
span, ctx := opentracing.StartSpanFromContext(ctx, "Foo")
defer span.Finish()
// 將context傳遞給Bar
Bar(ctx)
// 模擬執行耗時
time.Sleep(1 * time.Second)
}
func Bar(ctx context.Context) {
// 開始一個span,設置span的operation_name=Bar
span, ctx := opentracing.StartSpanFromContext(ctx, "Bar")
defer span.Finish()
// 模擬執行耗時
time.Sleep(2 * time.Second)
// 假設Bar發生了某些錯誤
err := errors.New("something wrong")
span.LogFields(
log.String("event", "error"),
log.String("message", err.Error()),
)
span.SetTag("error", true)
}
複製代碼
Foo
方法調用了Bar
,假設在Bar
中發生了一些錯誤,能夠經過span.LogFields
和span.SetTag
將錯誤記錄在追蹤鏈中。 經過上面的例子能夠發現,若是要確保追蹤鏈在程序中不斷開,須要將函數的第一個參數設置爲context.Context
,經過opentracing.ContextWithSpan
將保存到context
中,經過opentracing.StartSpanFromContext
開始一個新的子span。
執行完上面的程序後,打開Jaeger UI: http://localhost:16686/search,能夠看到鏈路追蹤的結果:
點擊詳情能夠查看具體信息: 經過鏈路追蹤系統,咱們能夠方便的掌握鏈路中各span的調用順序,調用關係,執行時間軸,以及記錄一些tag和log信息,極大的方便咱們定位系統中的異常和發現性能瓶頸。