go-kit微服務:服務鏈路追蹤

rayson發表於2019-02-28

鏈路追蹤

現代網際網路服務通常是使用複雜的、大規模的分散式系統來實現的。這些應用程式往往是由大量的軟體模組構建的,而且這些軟體模組可能由不同的團隊開發,可能使用不同的程式語言,並且可以跨多個物理設施跨越數千臺機器。在這種環境中,幫助理解系統行為和效能問題推理的工具是非常寶貴的。

微服務架構是一個分散式架構,實際開發中,我們按照業務要求劃分服務單元,一套系統往往會由多個業務單元構成。在這個場景中,一個請求可能需要經歷多個業務單元的處理才能完成響應,如果出現了錯誤或異常,很難定位。

為了解決這個問題,谷歌開源了分散式鏈路追蹤元件Drapper,並發表論文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》介紹了Drapper的設計思想。在該論文的影響下,Twitter設計、研發並開源了分散式鏈路追蹤系統Zipkin。

Zipkin

Zipkin是一個分散式跟蹤系統,它可以幫助收集時間資料,以此解決在微服務架構下的延遲問題。它同時提供了分散式系統時間資料的收集和查詢功能。Zipkin的架構如下圖所示:

Zipkin官方架構圖

通過架構圖可知,Zipkin由Collector、Storage、API、UI共4個元件構成,Reporter由應用系統提供並收集資料,其工作原理大概如下:

  • 在應用程式中嵌入追蹤器(Tracer),它使用Span記錄應用程式動作的時間和後設資料資訊;
  • Reporter把Span傳送至Zipkin的資料收集器Collector;
  • Collector通過Storage元件把資料儲存至資料庫;
  • UI元件通過API介面查詢追蹤資料並顯示;

Zipkin通過Trace結構表示對一次請求的跟蹤,一次請求由若干服務處理,每個服務生成一個Span,同一請求的Span之間存在關聯關係,在UI元件中以樹形式展示。Span的主要資料模型如下所示:

欄位 型別 說明
traceId string 隨機生成,用於唯一標識一個追蹤資訊,所有的span都包含此資訊。
name string span的名稱,可使用method命名,在UI元件中顯示
parentId string 父級span的編號,若為空,則表示為根span
id string span的編號
timestamp integer span建立的時間
duration integer span持續時間
annotations Annotation 關聯一個事件,用時間戳解釋延遲資訊
tags Tags span的標籤,用於搜尋、顯示和分析

Zipkin官方已經推出各種常見語言的支援,如C#、go、Java、JavaScript、Ruby、Scala、PHP,另外社群也貢獻了Python、C/C++、Lua等語言的支援。

實戰演練

Step-0:準備工作

本文將延續“go-kit微服務系列”,使用go-kit整合Zipkin實現算術運算服務的鏈路追蹤,這裡將包含兩個部分:

  • 在閘道器gateway中增加鏈路追蹤採集邏輯,同時在反向代理中增加追蹤設定。
  • 在算術運算服務的傳輸層和Endpoint層增加鏈路追蹤採集邏輯。

go-kit在tracing包中預設新增了zipkin的支援,所以整合工作將會比較輕鬆。在開始之前,需要下載以下依賴:

# zipkin官方庫
go get github.com/openzipkin/zipkin-go

# 下面三個包都是依賴,按需下載
git clone https://github.com/googleapis/googleapis.git [your GOPATH]/src/google.golang.org/genproto

git clone https://github.com/grpc/grpc-go.git [your GOPATH]/src/google.golang.org/grpc

git clone https://github.com/golang/text.git [your GOPATH]/src/golang.org/text
複製程式碼

本次演練使用arithmetic_consul_demo中的gatewayregister兩個服務,複製該目錄並重新命名為arithmetic_trace_demo,刪除discover

Step-1:Docker啟動Zipkin

  1. 開啟檔案docker/docker-compose.yml,在consul的基礎上增加zipkin配置資訊(使用官方推薦的openzipkin/zipkin),最終內容如下所示:
version: '2'

services:
  consul:
    image: progrium/consul:latest
    ports:
      - 8400:8400
      - 8500:8500
      - 8600:53/udp
    hostname: consulserver
    command: -server -bootstrap -ui-dir /ui

  zipkin:
    image: openzipkin/zipkin
    ports:
      - 9411:9411
複製程式碼
  1. 開啟終端切換至docker目錄,執行以下命令,啟動consul和zipkin。
sudo docker-compose up
複製程式碼
  1. 啟動成功後,開啟瀏覽器輸入http://localhost:9411檢查是否啟動成功。

Step-2:修改gateway

gateway將作為鏈路追蹤的第一站和最後一站,我們需要截獲到達gateway的所有請求,記錄追蹤資訊。gateway作為外部請求的服務端,同時作為算術運算服務的客戶端(反向代理內部實現)。

建立追蹤器

結合Zipkin的架構圖,需要在應用程式中整合Reporter元件,我們使用官方提供的go包。程式碼如下(預設設定了zipkin的url):

// 建立環境變數
var (
	// consul環境變數省略
	zipkinURL  = flag.String("zipkin.url", "http://192.168.192.146:9411/api/v2/spans", "Zipkin server url")
	)
flag.Parse()

var zipkinTracer *zipkin.Tracer
{
	var (
		err           error
		hostPort      = "localhost:9090"
		serviceName   = "gateway-service"
		useNoopTracer = (*zipkinURL == "")
		reporter      = zipkinhttp.NewReporter(*zipkinURL)
	)
	defer reporter.Close()
	zEP, _ := zipkin.NewEndpoint(serviceName, hostPort)
	zipkinTracer, err = zipkin.NewTracer(
		reporter, zipkin.WithLocalEndpoint(zEP), zipkin.WithNoopTracer(useNoopTracer),
	)
	if err != nil {
		logger.Log("err", err)
		os.Exit(1)
	}
	if !useNoopTracer {
		logger.Log("tracer", "Zipkin", "type", "Native", "URL", *zipkinURL)
	}
}
複製程式碼

為所有請求增加鏈路追蹤

我們使用的傳輸方式為http,可以使用zipkin-go提供的middleware/http包,它採用裝飾者模式把我們的http.Handler進行封裝,然後啟動監聽即可,程式碼如下所示:

//建立反向代理
proxy := NewReverseProxy(consulClient, zipkinTracer, logger)

tags := map[string]string{
	"component": "gateway_server",
}

handler := zipkinhttpsvr.NewServerMiddleware(
	zipkinTracer,
	zipkinhttpsvr.SpanName("gateway"),
	zipkinhttpsvr.TagResponseSize(true),
	zipkinhttpsvr.ServerTags(tags),
)(proxy)
複製程式碼

反向代理設定

gateway接收請求後,會建立一個span,其中的traceId將作為本次請求的唯一編號,gateway必須把這個traceId“告訴”算術運算服務,算術運算服務才能為該請求持續記錄追蹤資訊。

ReverseProxy中能夠完成這一任務的就是Transport,我們可以使用zipkin-gomiddleware/http包提供的NewTransport替換系統預設的http.DefaultTransport。程式碼如下所示:

// NewReverseProxy 建立反向代理處理方法
func NewReverseProxy(client *api.Client, zikkinTracer *zipkin.Tracer, logger log.Logger) *httputil.ReverseProxy {

	//建立Director
	director := func(req *http.Request) {
        //省略
	}

	// 為反向代理增加追蹤邏輯,使用如下RoundTrip代替預設Transport
	roundTrip, _ := zipkinhttpsvr.NewTransport(zikkinTracer, zipkinhttpsvr.TransportTrace(true))

	return &httputil.ReverseProxy{
		Director:  director,
		Transport: roundTrip,
	}
}
複製程式碼

這一步很關鍵!若不設定,會導致整個鏈路追蹤不完整。為了解決這個問題,花費了不少時間,最後還是通過zipkin-goREADME解開了疑惑。

完成以上過程,就可以編譯執行了。

Step-3:修改算術服務

建立追蹤器

這一步與gateway的處理方式一樣,不再描述。

追蹤Endpoint

go-kit提供了對zipkin-go的封裝,可直接呼叫中介軟體TraceEndpoint對算術運算服務的兩個Endpoint進行設定。程式碼如下:

endpoint := MakeArithmeticEndpoint(svc)
endpoint = NewTokenBucketLimitterWithBuildIn(ratebucket)(endpoint)
//新增追蹤,設定span的名稱為calculate-endpoint
endpoint = kitzipkin.TraceEndpoint(zipkinTracer, "calculate-endpoint")(endpoint)

//建立健康檢查的Endpoint
healthEndpoint := MakeHealthCheckEndpoint(svc)
healthEndpoint = NewTokenBucketLimitterWithBuildIn(ratebucket)(healthEndpoint)
//新增追蹤,設定span的名稱為health-endpoint
healthEndpoint = kitzipkin.TraceEndpoint(zipkinTracer, "health-endpoint")(healthEndpoint)
複製程式碼

追蹤Transport

  1. 修改transports.goMakeHttpHandler方法。增加引數zipkinTracer,然後在ServerOption中設定追蹤引數。程式碼如下:
// MakeHttpHandler make http handler use mux
func MakeHttpHandler(ctx context.Context, endpoints ArithmeticEndpoints, zipkinTracer *gozipkin.Tracer, logger log.Logger) http.Handler {
	r := mux.NewRouter()

	zipkinServer := zipkin.HTTPServerTrace(zipkinTracer, zipkin.Name("http-transport"))

	options := []kithttp.ServerOption{
		kithttp.ServerErrorLogger(logger),
		kithttp.ServerErrorEncoder(kithttp.DefaultErrorEncoder),
		zipkinServer,
	}

	//省略程式碼

	return r
}
複製程式碼
  1. main.go中呼叫MakeHttpHandler
//建立http.Handler
r := MakeHttpHandler(ctx, endpts, zipkinTracer, logger)
複製程式碼

至此,所有的程式碼修改工作已經完成,下一步就是啟動測試了。

Step-4:執行&測試

確保ConsulZipkingatewayregister四個服務已經正常執行,然後使用Postman進行請求測試(與之前類似,為了方便檢視資料,可多點幾次)。

在瀏覽器中開啟http://localhost:9411,點選“Find Traces”按鈕,即可看到如下介面。詳細顯示了每個請求執行的時間、span的數量、途徑的服務名稱等資訊。

go-kit微服務:服務鏈路追蹤

開啟第一個請求,進入該請求的鏈路追蹤介面,如下圖所示。

鏈路追蹤

  • 上半部分顯示:,該請求的執行時間為10.970毫秒、途徑的服務為2個、鏈路深度為3、span數量為3。
  • 下半部分顯示:以樹形方式顯示span,直觀展示每個span的途徑服務、執行時間、span名稱等資訊。

通過該介面,我們可以知道請求鏈路中比較耗時的環節為gateway-service。原因是:每次請求過來,程式都要到Consul中查詢服務例項,動態建立服務地址。

另外,點選樹形結構的每個span,可以檢視span的描述資訊,這裡不再展開描述。

總結

本文使用go-kit的tracing元件和zipkin-go包,為閘道器服務和算術運算服務增加了鏈路追蹤功能,以例項方式演示了在go-kit中整合Zipkin的方式。示例比較簡單,希望對你有用!

本文參考

本文首發於本人微信公眾號【兮一昂吧】,歡迎掃碼關注!

go-kit微服務:服務鏈路追蹤

相關文章