<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Go-Zero 是如何追蹤你的請求鏈路?

          共 5062字,需瀏覽 11分鐘

           ·

          2020-11-03 14:14


          “ go-zero 是一個集成了各種工程實踐的 web 和 rpc 框架。通過彈性設(shè)計保障了大并發(fā)服務(wù)端的穩(wěn)定性,經(jīng)受了充分的實戰(zhàn)檢驗。”


          序言

          微服務(wù)架構(gòu)中,調(diào)用鏈可能很漫長,從?http?到?rpc?,又從?rpc?到?http?。而開發(fā)者想了解每個環(huán)節(jié)的調(diào)用情況及性能,最佳方案就是?全鏈路跟蹤

          追蹤的方法就是在一個請求開始時生成一個自己的?spanID?,隨著整個請求鏈路傳下去。我們則通過這個?spanID?查看整個鏈路的情況和性能問題。

          下面來看看?go-zero?的鏈路實現(xiàn)。

          代碼結(jié)構(gòu)

          • spancontext:保存鏈路的上下文信息「traceid,spanid,或者是其他想要傳遞的內(nèi)容」

          • span:鏈路中的一個操作,存儲時間和某些信息

          • propagator:?trace?傳播下游的操作「抽取,注入」

          • noop:實現(xiàn)了空的?tracer?實現(xiàn)



          概念

          SpanContext

          在介紹?span?之前,先引入?context?。SpanContext 保存了分布式追蹤的上下文信息,包括 Trace id,Span id 以及其它需要傳遞到下游的內(nèi)容。OpenTracing 的實現(xiàn)需要將 SpanContext 通過某種協(xié)議 進行傳遞,以將不同進程中的 Span 關(guān)聯(lián)到同一個 Trace 上。對于 HTTP 請求來說,SpanContext 一般是采用 HTTP header 進行傳遞的。

          下面是?go-zero?默認實現(xiàn)的?spanContext

          type spanContext struct {
          traceId string // TraceID 表示tracer的全局唯一ID
          spanId string // SpanId 示單個trace中某一個span的唯一ID,在trace中唯一
          }

          同時開發(fā)者也可以實現(xiàn)?SpanContext?提供的接口方法,實現(xiàn)自己的上下文信息傳遞:

          type SpanContext interface {
          TraceId() string // get TraceId
          SpanId() string // get SpanId
          Visit(fn func(key, val string) bool) // 自定義操作TraceId,SpanId
          }

          Span

          一個 REST 調(diào)用或者數(shù)據(jù)庫操作等,都可以作為一個?span?。?span?是分布式追蹤的最小跟蹤單位,一個 Trace 由多段 Span 組成。追蹤信息包含如下信息:

          type Span struct {
          ctx spanContext // 傳遞的上下文
          serviceName string // 服務(wù)名
          operationName string // 操作
          startTime time.Time // 開始時間戳
          flag string // 標記開啟trace是 server 還是 client
          children int // 本 span fork出來的 childsnums
          }

          從?span?的定義結(jié)構(gòu)來看:在微服務(wù)中, 這就是一個完整的子調(diào)用過程,有調(diào)用開始?startTime?,有標記自己唯一屬性的上下文結(jié)構(gòu)?spanContext?以及 fork 的子節(jié)點數(shù)。

          實例應用

          在?go-zero?中 http,rpc 中已經(jīng)作為內(nèi)置中間件集成。我們以?http,rpc?中,看看?tracing?是怎么使用的:

          HTTP

          func TracingHandler(next http.Handler) http.Handler {
          return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
          // **1**
          carrier, err := trace.Extract(trace.HttpFormat, r.Header)
          // ErrInvalidCarrier means no trace id was set in http header
          if err != nil && err != trace.ErrInvalidCarrier {
          logx.Error(err)
          }

          // **2**
          ctx, span := trace.StartServerSpan(r.Context(), carrier, sysx.Hostname(), r.RequestURI)
          defer span.Finish()
          // **5**
          r = r.WithContext(ctx)

          next.ServeHTTP(w, r)
          })
          }

          func StartServerSpan(ctx context.Context, carrier Carrier, serviceName, operationName string) (
          context.Context, tracespec.Trace) {
          span := newServerSpan(carrier, serviceName, operationName)
          // **4**
          return context.WithValue(ctx, tracespec.TracingKey, span), span
          }

          func newServerSpan(carrier Carrier, serviceName, operationName string) tracespec.Trace {
          // **3**
          traceId := stringx.TakeWithPriority(func() string {
          if carrier != nil {
          return carrier.Get(traceIdKey)
          }
          return ""
          }, func() string {
          return stringx.RandId()
          })
          spanId := stringx.TakeWithPriority(func() string {
          if carrier != nil {
          return carrier.Get(spanIdKey)
          }
          return ""
          }, func() string {
          return initSpanId
          })

          return &Span{
          ctx: spanContext{
          traceId: traceId,
          spanId: spanId,
          },
          serviceName: serviceName,
          operationName: operationName,
          startTime: timex.Time(),
          // 標記為server
          flag: serverFlag,
          }
          }
          1. 將 header -> carrier,獲取 header 中的 traceId 等信息

          2. 開啟一個新的 span,并把「traceId,spanId」封裝在 context 中

          3. 從上述的 carrier「也就是 header」獲取 traceId,spanId

            • 看 header 中是否設(shè)置

            • 如果沒有設(shè)置,則隨機生成返回

          4. 從?request?中產(chǎn)生新的 ctx,并將相應的信息封裝在 ctx 中,返回

          5. 從上述的 context,拷貝一份到當前的?request



          這樣就實現(xiàn)了?span?的信息隨著?request?傳遞到下游服務(wù)。

          RPC

          在 rpc 中存在?client, server?,所以從?tracing?上也有?clientTracing, serverTracing?。?serveTracing?的邏輯基本與 http 的一致,來看看?clientTracing?是怎么使用的?

          func TracingInterceptor(ctx context.Context, method string, req, reply interface{},
          cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
          // open clientSpan
          ctx, span := trace.StartClientSpan(ctx, cc.Target(), method)
          defer span.Finish()

          var pairs []string
          span.Visit(func(key, val string) bool {
          pairs = append(pairs, key, val)
          return true
          })
          // **3** 將 pair 中的data以map的形式加入 ctx
          ctx = metadata.AppendToOutgoingContext(ctx, pairs...)

          return invoker(ctx, method, req, reply, cc, opts...)
          }

          func StartClientSpan(ctx context.Context, serviceName, operationName string) (context.Context, tracespec.Trace) {
          // **1**
          if span, ok := ctx.Value(tracespec.TracingKey).(*Span); ok {
          // **2**
          return span.Fork(ctx, serviceName, operationName)
          }

          return ctx, emptyNoopSpan
          }
          1. 獲取上游帶下來的 span 上下文信息

          2. 從獲取的 span 中創(chuàng)建新的 ctx,span「繼承父 span 的 traceId」

          3. 將生成 span 的 data 加入 ctx,傳遞到下一個中間件,流至下游

          總結(jié)

          go-zero?通過攔截請求獲取鏈路 traceID,然后在中間件函數(shù)入口會分配一個根 Span,然后在后續(xù)操作中會分裂出子 Span,每個 span 都有自己的具體的標識,F(xiàn)insh 之后就會匯集在鏈路追蹤系統(tǒng)中。開發(fā)者可以通過?ELK?工具追蹤?traceID?,看到整個調(diào)用鏈。

          同時?go-zero?并沒有提供整套?trace?鏈路方案,開發(fā)者可以封裝?go-zero?已有的?span?結(jié)構(gòu),做自己的上報系統(tǒng),接入?jaeger, zipkin?等鏈路追蹤工具。

          參考

          • go-zero trace

          • 開放分布式追蹤(OpenTracing)入門與 Jaeger 實現(xiàn)

          同時歡迎大家使用?go-zero?并加入我們,https://github.com/tal-tech/go-zero



          瀏覽 24
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产久| 亚洲欧美视频一区 | 一级久久久 | 久久凹凸视频 | 日本精品在线播放 |