Go語言--opentracing-go

關於opentracing的簡單介紹

什麼是opentracing?

OpenTracing的數據模型
opentracing中的跟蹤鏈由span隱性的定義。可別的,跟蹤鏈可以認爲是span的有向無環圖(DAG),spans之間的邊稱爲References。
以下是一個跨度的例子圖:
在這裏插入圖片描述
解析爲時間軸可視化的圖:
在這裏插入圖片描述
每個span封裝以下的狀態:
(1)操作名稱
(2)開始時間戳
(3)完成時間戳
(4)一組零個或多個key:value跨度標籤。 鍵必須是字符串。 值可以是字符串,布爾型或數字類型。
(5)一組零個或多個跨度日誌,每個跨度日誌本身就是與時間戳配對的key:value映射。 鍵必須是字符串,儘管值可以是任何類型。 並非所有OpenTracing實現都必須支持每種值類型。
(6)SpanContext(請參見下文)
(7)零個或多個因果相關的Span的引用(通過那些相關Span的SpanContext)
每個SpanContext封裝以下狀態
(1)引用跨過程span的不同process所需的任何OpenTracing實現依賴狀態(例如,跟蹤和跨度ID)
(2)Baggage Items,僅是k:v鍵值對,跨越process的邊界

span之間的References

Span可以引用因果相關的零個或多個其他SpanContext。
Span可以引用因果相關的零個或多個其他SpanContext。 OpenTracing當前定義了兩種類型的引用:ChildOf和FollowsFrom。 兩種參考類型都專門爲子Span和父Span之間的直接因果關係建模。 將來,OpenTracing可能還會支持具有非因果關係的Span的引用類型(例如,批處理在一起的Span,卡在同一隊列中的Span等)。
ChildOf:
span可以是父親span的子,在ChildOf的reference中父的span某些情況下依賴於子的span,以下所有內容將構成ChildOf關係:
(1)代表RPC的服務器端的Span可以是代表該RPC客戶端的Span的ChildOf
(2)表示SQL插入的Span可以是代表ORM保存方法的Span的ChildOf
(3)許多同時執行(可能是分佈式的)工作的Span可能全部是單個父Span的ChildOf,它合併了在截止期限內返回的所有子代的結果
這些對於作爲父級ChildOf的子級來說都是有效的時序圖。
在這裏插入圖片描述
FollowsFrom:
一些父Span完全不依賴於其子Span的結果。 在這些情況下,我們僅說因果關係上子Span從父Span跟隨。 有許多不同的FollowsFrom參考子類別,在將來的OpenTracing版本中,它們可能會更正式地加以區分。
這些對於“FollowsFrom”的孩子來說都是有效的時序圖。
在這裏插入圖片描述

The OpenTracing API

OpenTracing規範中包含三種關鍵且相互關聯的類型:Tracer,Span和SpanContext。
粗略地說,每種行爲在典型的編程語言中都成爲一種“方法”,儘管由於類型重載等原因,它實際上可能是一組相關的同級方法。
--------------------------------------------------------------Tracer--------------------------------------------------
Tracer接口創造span並且瞭解如何將span跨process序列化和反序列化他們。Tracer接口具有以下功能。
1.開始一個新的span:
必要參數:
操作名稱,例子:get_account
可選參數:
1.零個或多個對相關SpanContext的引用,如果可能的話,包括ChildOf和FollowsFrom引用類型的簡寫。
2.可選的顯式開始時間戳記; 如果省略,則默認使用當前的walltime
3.零個或多個標籤
返回已經啓動(但尚未完成)的Span實例
2.將SpanContext注入載體
必要參數:
1.SpanContext實例
2.格式描述符(通常但不一定是字符串常量),它告訴Tracer實現如何在載波參數中對SpanContext進行編碼
3.載體,其類型由格式決定。 Tracer實現將根據格式在此載體對象中對SpanContext進行編碼。
3.從運營商中提取SpanContext
必要參數:
1.格式描述符(通常但不一定是字符串常量),它告訴Tracer實現如何從載波參數中解碼SpanContext
2.載體,其類型由格式決定。 Tracer實現將根據格式從該載體對象解碼SpanContext。
當通過Tracer啓動新的Span時,返回適合用作參考的SpanContext實例。

注意:注射和提取所需的格式
注入和提取均依賴於可擴展的格式參數,該參數指示關聯的“載體”的類型以及如何在該載體中編碼SpanContext。 所有Tracer實現都必須支持以下所有格式。

1.文本映射(Text Map):任意字符串到字符串的映射,其中鍵和值的字符集不受限制
2.HTTP標頭(Http Headers):具有適用於HTTP標頭(例如RFC 7230)的鍵和值的字符串到字符串的映射。 在實踐中,由於HTTP頭的處理方式存在“多樣性”,因此強烈建議Tracer實現使用有限的HTTP頭密鑰空間並保守地轉義值。
3.二進制(二進制):表示SpanContext的(單個)任意二進制Blob
--------------------------------------------------------------Span--------------------------------------------------
可以做的事情:
1.檢索跨度SpanContext
2.覆蓋操作名稱
3.完成跨度
4.設置跨度標籤
5.記錄結構化數據
6.Set a baggage item
7.Get a baggage item
----------------------------------------------------------SpanContext----------------------------------------------
SpanContext不僅僅是通用OpenTracing層上的有用功能,而是更多的“概念”。 也就是說,這對於OpenTracing實現至關重要,並且確實提供了自己的瘦API。 大多數OpenTracing用戶在啓動新的Span或向某些傳輸協議注入/從某些傳輸協議提取跟蹤時,僅通過引用與SpanContext進行交互。

在OpenTracing中,我們強制SpanContext實例是不可變的,以避免圍繞Span精加工和引用的複雜生命週期問題。

----------------------------------------------------------NoopTracer----------------------------------------------
所有OpenTracing語言API也必須提供某種NoopTracer實現,該實現可用於標記控制OpenTracing或注入對測試無害的東西(等等)。 在某些情況下(例如Java),NoopTracer可能位於其自己的打包工件中。

opentracing-go使用實例

初始化tracer

func TraceInit(serviceName string, samplerType string, samplerParam float64) (opentracing.Tracer, io.Closer) {
	cfg := &config.Configuration{
		ServiceName: serviceName,
		Sampler: &config.SamplerConfig{
			Type:  samplerType,
			Param: samplerParam,
		},
		Reporter: &config.ReporterConfig{
			LocalAgentHostPort: "127.0.0.1:6831",
			LogSpans:           true,
		},
	}

	tracer, closer, err := cfg.NewTracer(config.Logger(jaeger.StdLogger))
	if err != nil {
		panic(fmt.Sprintf("Init failed: %v\n", err))
	}

	return tracer,closer
}

客戶端注入信息

  func makeSomeRequest(ctx context.Context) ... {
        if span := opentracing.SpanFromContext(ctx); span != nil {
            httpClient := &http.Client{}
            httpReq, _ := http.NewRequest("GET", "http://myservice/", nil)

            // Transmit the span's TraceContext as HTTP headers on our
            // outbound request.
            opentracing.GlobalTracer().Inject(
                span.Context(),
                opentracing.HTTPHeaders,
                opentracing.HTTPHeadersCarrier(httpReq.Header))

            resp, err := httpClient.Do(httpReq)
            ...
        }
        ...
    }

服務端解析信息

    http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        var serverSpan opentracing.Span
        appSpecificOperationName := ...
        wireContext, err := opentracing.GlobalTracer().Extract(
            opentracing.HTTPHeaders,
            opentracing.HTTPHeadersCarrier(req.Header))
        if err != nil {
            // Optionally record something about err here
        }

        // Create the span referring to the RPC client if available.
        // If wireContext == nil, a root span will be created.
        serverSpan = opentracing.StartSpan(
            appSpecificOperationName,
            ext.RPCServerOption(wireContext))

        defer serverSpan.Finish()

        ctx := opentracing.ContextWithSpan(context.Background(), serverSpan)
        ...
    }

以下是簡化他人的客戶端代碼

package main

import (
//	"context"
	"fmt"
	"github.com/uber/jaeger-client-go"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"

	"github.com/opentracing/opentracing-go"
//	"github.com/opentracing/opentracing-go/log"
	"github.com/uber/jaeger-client-go/config"
)

const (
	URL        = "http://localhost:8080"
	LIST_API   = "/getList"
	RESULT_API = "/getResult"
)

var (
	flag = make(chan bool)
)



func TraceInit(serviceName string, samplerType string, samplerParam float64) (opentracing.Tracer, io.Closer) {
	cfg := &config.Configuration{
		ServiceName: serviceName,
		Sampler: &config.SamplerConfig{
			Type:  samplerType,
			Param: samplerParam,
		},
		Reporter: &config.ReporterConfig{
			LocalAgentHostPort: "127.0.0.1:6831",
			LogSpans:           true,
		},
	}

	tracer, closer, err := cfg.NewTracer(config.Logger(jaeger.StdLogger))
	if err != nil {
		panic(fmt.Sprintf("Init failed: %v\n", err))
	}

	return tracer,closer
}


func sendRequest(req *http.Request) {
	go func(req *http.Request) {
		resp, err := http.DefaultClient.Do(req)

		if err != nil {
			fmt.Printf("Do send requst failed(%s)\n", err)
			return
		}

		defer resp.Body.Close()

		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			fmt.Printf("ReadAll error(%s)\n", err)
			return
		}

		if resp.StatusCode != 200 {
			return
		}
		fmt.Printf("Response:%s\n", string(body))
		flag <- true
	}(req)
}

func main() {
	if len(os.Args) < 2 {
		fmt.Println("Argument error(getlist or getresult number) ")
		os.Exit(1)
	}

	tracer, closer := TraceInit("CS-tracing", "const", 1)
	defer closer.Close()
	opentracing.SetGlobalTracer(tracer)
	span := tracer.StartSpan(fmt.Sprintf("%s trace", os.Args[1]))
	span.SetTag("trace to", os.Args[1])
	defer span.Finish()
	api := ""
	var err error

	if os.Args[1] == "getlist" {
		api = LIST_API
	}

	reqURL := URL + api
	req, err := http.NewRequest("GET", reqURL, nil)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	span.Tracer().Inject(
		span.Context(),
		opentracing.HTTPHeaders,
		opentracing.HTTPHeadersCarrier(req.Header),
	)

	if os.Args[1] == "getresult" {
		q := req.URL.Query()
		q.Add("num", os.Args[2])
		req.URL.RawQuery = q.Encode()
	}
	sendRequest(req)

	<-flag
}

對應的服務端代碼

package main

import (
	"fmt"
	"github.com/uber/jaeger-client-go"
	"io"
	"net/http"

	"github.com/uber/jaeger-client-go/config"
	"github.com/opentracing/opentracing-go"
	"github.com/opentracing/opentracing-go/ext"
)

var (
	tracer opentracing.Tracer
)

func TraceInit(serviceName string, samplerType string, samplerParam float64) (opentracing.Tracer, io.Closer) {
	cfg := &config.Configuration{
		ServiceName: serviceName,
		Sampler: &config.SamplerConfig{
			Type:  samplerType,
			Param: samplerParam,
		},
		Reporter: &config.ReporterConfig{
			LocalAgentHostPort: "127.0.0.1:6831",
			LogSpans:           true,
		},
	}

	tracer, closer, err := cfg.NewTracer(config.Logger(jaeger.StdLogger))
	if err != nil {
		panic(fmt.Sprintf("Init failed: %v\n", err))
	}

	return tracer,closer
}


func GetListProc(w http.ResponseWriter, req *http.Request) {

	spanCtx, _ := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
	span := tracer.StartSpan("GetListProc", ext.RPCServerOption(spanCtx))
	defer span.Finish()

	fmt.Println("Get request getList")
	respList := []string{"l1", "l2", "l3", "l4", "l5"}
	respString := ""

	for _, v := range respList {
		respString += v + ","
	}

	fmt.Println(respString)
	io.WriteString(w, respString)
}

func main() {
	var closer io.Closer
	tracer, closer = TraceInit("Trace-Server", "const", 1)
	defer closer.Close()

	http.HandleFunc("/getList", GetListProc)

	http.ListenAndServe(":8080", nil)
}

參考文件:
https://opentracing.io/specification/(概念)
https://github.com/opentracing/opentracing-go
https://segmentfault.com/a/1190000021273636?utm_source=tag-newest
https://blog.csdn.net/liyunlong41/article/details/88043604
https://blog.csdn.net/liyunlong41/article/details/87932953

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章