使用OpenTracing跟蹤Go中的HTTP請求延遲
什麼是分散式跟蹤和OpenTracing?
分散式跟蹤是監測和分析微服務架構系統,匯出結果到為X-TRACE,如谷歌的Dapper和Twitter的Zipkin 。 它們的底層原理是分散式環境傳播 ,其中涉及的某些後設資料與進入系統的每個請求相關聯,並且跨執行緒和程式邊界傳播後設資料跟隨請求進出各種微服務呼叫。 如果我們為每個入站請求分配一個唯一的ID並將其作為分散式上下文的一部分,那麼我們可以將來自多個執行緒和多個程式的各種效能分析資料合併到統一的表示我們系統執行請求的“跟蹤”中。
分散式跟蹤需要使用Hook鉤子和上下文傳播機制來測試應用程式程式碼(或其使用的框架)。 當我們在Uber開始構建我們的分散式跟蹤系統時,我們很快意識到,沒有良好的API為開發人員提供在程式語言之間內部一致性,那就無法繫結到指定的跟蹤系統。原來,不只是我們有這種思維,2015年10月一個新的社群形成,催生了OpenTracing API,一個開放的,廠商中立的,與語言無關的分散式跟蹤標準。你可以閱讀更多關於Ben Sigelman有關OpenTracing動機和設計原理背後的文章 。
讓我們看看程式碼:
import ( "net/http" "net/http/httptrace" "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/log" "golang.org/x/net/context" ) // We will talk about this later var tracer opentracing.Tracer func AskGoogle(ctx context.Context) error { // retrieve current Span from Context var parentCtx opentracing.SpanContext parentSpan := opentracing.SpanFromContext(ctx); if parentSpan != nil { parentCtx = parentSpan.Context() } // start a new Span to wrap HTTP request span := tracer.StartSpan( "ask google", opentracing.ChildOf(parentCtx), ) // make sure the Span is finished once we're done defer span.Finish() // make the Span current in the context ctx = opentracing.ContextWithSpan(ctx, span) // now prepare the request req, err := http.NewRequest("GET", "http://google.com", nil) if err != nil { return err } // attach ClientTrace to the Context, and Context to request trace := NewClientTrace(span) ctx = httptrace.WithClientTrace(ctx, trace) req = req.WithContext(ctx) // execute the request res, err := http.DefaultClient.Do(req) if err != nil { return err } // Google home page is not too exciting, so ignore the result res.Body.Close() return nil } <p class="indent"> |
這裡有幾點要注意:
1.迴避了tracer變數初始化的問題。
2.AskGoogle函式接受context.Context物件。這是為開發分散式應用程式的Go推薦方式,因為上下文物件是要讓分散式環境傳播。
3.我們假設上下文已經包含父跟蹤Span。 OpenTracing API中的Span是用於表示由微服務執行的工作單元。HTTP呼叫就是可以包裹在跟蹤Span中的操作案例。 當我們執行處理入站請求的服務時,服務通常會為每個請求建立一個跟蹤範圍,並將其儲存在上下文中,以便在對另一個服務進行下游呼叫時可用(在我們的示例中為google.com )。
4.我們啟動一個新的子Span來包裝出站HTTP呼叫。 如果父Span缺失,這是好方法。
5.最後,在做出HTTP請求之前,我們例項化一個ClientTrace並將其附加到請求。
ClientTrace結構是httptrace的基本構建塊 。它允許我們在HTTP請求的生命週期內註冊將由HTTP客戶端執行的回撥函式。 例如,ClientTrace結構有這樣的方法:
type ClientTrace struct { ... // DNSStart is called when a DNS lookup begins. DNSStart func(DNSStartInfo) // DNSDone is called when a DNS lookup ends. DNSDone func(DNSDoneInfo) ... } <p class="indent"> |
我們在NewClientTrace方法中建立這個結構的一個例項:
func NewClientTrace(span opentracing.Span) *httptrace.ClientTrace { trace := &clientTrace{span: span} return &httptrace.ClientTrace { DNSStart: trace.dnsStart, DNSDone: trace.dnsDone, } } // clientTrace holds a reference to the Span and // provides methods used as ClientTrace callbacks type clientTrace struct { span opentracing.Span } func (h *clientTrace) dnsStart(info httptrace.DNSStartInfo) { h.span.LogKV( log.String("event", "DNS start"), log.Object("host", info.Host), ) } func (h *clientTrace) dnsDone(httptrace.DNSDoneInfo) { h.span.LogKV(log.String("event", "DNS done")) } <p class="indent"> |
我們為DBBStart和DNSDone事件實現註冊兩個回撥函式,透過私有結構clientTrace保有一個指向跟蹤Span。 在回撥方法中,我們使用Span的鍵值記錄API來記錄事件的資訊,以及Span本身隱含捕獲的時間戳。
你不是說關於UI的東西嗎?
OpenTracing API的工作方式是,一旦呼叫跟蹤Span的Finish()方法,Span捕獲的資料將傳送到跟蹤系統後端,通常在後臺非同步傳送。然後,我們可以使用跟蹤系統UI來查詢跟蹤並在時間線上將其視覺化。 然而,上述例子只是為了說明使用OpenTracing與httptrace的原理。對於真正的工作示例,我們將使用現有的庫https://github.com/opentracing-contrib/go-stdlib 。 使用這個庫我們的客戶端程式碼不需要擔心跟蹤實際的HTTP呼叫。但是,我們仍然希望建立一個頂層跟蹤Span來表示客戶端應用程式的整體執行,並記錄任何錯誤。
package main import ( "fmt" "io/ioutil" "log" "net/http" "github.com/opentracing-contrib/go-stdlib/nethttp" "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext" otlog "github.com/opentracing/opentracing-go/log" "golang.org/x/net/context" ) func runClient(tracer opentracing.Tracer) { // nethttp.Transport from go-stdlib will do the tracing c := &http.Client{Transport: &nethttp.Transport{}} // create a top-level span to represent full work of the client span := tracer.StartSpan(client) span.SetTag(string(ext.Component), client) defer span.Finish() ctx := opentracing.ContextWithSpan(context.Background(), span) req, err := http.NewRequest( "GET", fmt.Sprintf("http://localhost:%s/", *serverPort), nil, ) if err != nil { onError(span, err) return } req = req.WithContext(ctx) // wrap the request in nethttp.TraceRequest req, ht := nethttp.TraceRequest(tracer, req) defer ht.Finish() res, err := c.Do(req) if err != nil { onError(span, err) return } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { onError(span, err) return } fmt.Printf("Received result: %s\n", string(body)) } func onError(span opentracing.Span, err error) { // handle errors by recording them in the span span.SetTag(string(ext.Error), true) span.LogKV(otlog.Error(err)) log.Print(err) } <p class="indent"> |
上面的客戶端程式碼呼叫本地伺服器。 讓我們實現它。
package main import ( "fmt" "io" "log" "net/http" "time" "github.com/opentracing-contrib/go-stdlib/nethttp" "github.com/opentracing/opentracing-go" ) func getTime(w http.ResponseWriter, r *http.Request) { log.Print("Received getTime request") t := time.Now() ts := t.Format("Mon Jan _2 15:04:05 2006") io.WriteString(w, fmt.Sprintf("The time is %s", ts)) } func redirect(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, fmt.Sprintf("http://localhost:%s/gettime", *serverPort), 301) } func runServer(tracer opentracing.Tracer) { http.HandleFunc("/gettime", getTime) http.HandleFunc("/", redirect) log.Printf("Starting server on port %s", *serverPort) http.ListenAndServe( fmt.Sprintf(":%s", *serverPort), // use nethttp.Middleware to enable OpenTracing for server nethttp.Middleware(tracer, http.DefaultServeMux)) } |
注意,客戶端向根端點“/”發出請求,但伺服器將其重定向到“/ gettime”端點。 這樣做允許我們更好地說明如何在跟蹤系統中捕獲跟蹤。
Tracing HTTP request latency in Go with OpenTracin
[該貼被banq於2016-11-25 09:00修改過]
相關文章
- HTTP 請求延遲解決方案HTTP
- 💢線上高延遲請求排查
- go http請求GoHTTP
- 使用 Solon Cloud 的 Jaeger 做請求鏈路跟蹤Cloud
- go http請求流程分析GoHTTP
- go搞笑http請求庫GoHTTP
- 型別安全的 Go HTTP 請求型別GoHTTP
- Go使用net/http庫傳送GET請求GoHTTP
- Go如何響應http請求?GoHTTP
- [原始碼分析] OpenTracing之跟蹤Redis原始碼Redis
- Oracle EBS併發請求啟用跟蹤Oracle
- gRPC的請求追蹤神器 go tool traceRPCGo
- 【HTTP】HTTP請求體中的四種格式HTTP
- 淺嘗flutter中的http請求FlutterHTTP
- Go語言HTTP請求流式寫入bodyGoHTTP
- Go HTTP GET 請求可以傳送 body 嗎GoHTTP
- go-zero 是如何追蹤你的請求鏈路?Go
- Golang:使用go-resty/resty傳送http請求get和postGolangRESTHTTP
- Dubbo 整合 Pinpoint 做分散式服務請求跟蹤分散式
- http請求HTTP
- HTTP 請求HTTP
- 使用httpclient傳送http請求HTTPclient
- HTTP請求頭中的refer欄位HTTP
- HTTP 請求頭中的 X-Forwarded-ForHTTPForward
- 分享一個小工具 Boast:如何從服務端跟蹤所有 HTTP 請求,並方便回放?AST服務端HTTP
- go-zero 是如何追蹤你的請求鏈路的Go
- 在Java中,使用HttpUtils實現傳送HTTP請求JavaHTTP
- go的gin框架使用(五):post請求Go框架
- HTTP請求中的referrer和Referrer-PolicyHTTP
- Http 請求頭中的 Proxy-ConnectionHTTP
- vitess中rpc相容http請求的技巧ViteRPCHTTP
- PostgreSQL中的複製延遲SQL
- golang使用fasthttp 發起http請求GolangASTHTTP
- 使用Feign傳送HTTP請求HTTP
- 使用 $fetch 進行 HTTP 請求HTTP
- HTTP協議---HTTP請求中的常用請求欄位和HTTP的響應狀態碼及響應頭HTTP協議
- http請求概述HTTP
- HTTP請求方法HTTP