go基于grpc構(gòu)建微服務(wù)框架-集成opentracing
1.概述
存在這樣一種場(chǎng)景凿掂,當(dāng)我們進(jìn)行微服務(wù)拆分后反浓,一個(gè)請(qǐng)求將會(huì)經(jīng)過(guò)多個(gè)服務(wù)處理之后再返回,這時(shí),如果在請(qǐng)求的鏈路上某個(gè)服務(wù)出現(xiàn)故障時(shí)豌汇,排查故障將會(huì)比較困難.
我們可能需要將請(qǐng)求經(jīng)過(guò)的服務(wù),挨個(gè)查看日志進(jìn)行分析充岛,當(dāng)服務(wù)有幾十上百個(gè)實(shí)例時(shí)保檐,這無(wú)疑是可怕的.因此為了解決這種問(wèn)題,調(diào)用鏈追蹤應(yīng)運(yùn)而生.
2.opentracing
1.1 opentracing作用
調(diào)用鏈追蹤最先由googel在Dapper這篇論文中提出崔梗,OpenTracing主要定義了相關(guān)的協(xié)議以及接口夜只,這樣各個(gè)語(yǔ)言只要按照Opentracing的接口以及協(xié)議實(shí)現(xiàn)數(shù)據(jù)上報(bào),那么調(diào)用信息就能統(tǒng)一被收集.
如上圖所示蒜魄,接口可能首先經(jīng)過(guò)web框架扔亥,然后調(diào)用auth服務(wù)场躯,通過(guò)調(diào)用鏈,將請(qǐng)求經(jīng)過(guò)的服務(wù)進(jìn)行編號(hào)旅挤,統(tǒng)一收集起來(lái)踢关,形成邏輯上的鏈路,這樣粘茄,我們就可以看到請(qǐng)求經(jīng)過(guò)了哪些服務(wù)签舞,從而形成服務(wù)依賴(lài)的拓?fù)洌?/p>
如上,總鏈路由每段鏈路組成柒瓣,每段鏈路均代表經(jīng)過(guò)的服務(wù)儒搭,耗時(shí)可用于分析系統(tǒng)瓶頸,當(dāng)某個(gè)請(qǐng)求返回較慢時(shí)芙贫,可以通過(guò)排查某一段鏈路的耗時(shí)情況搂鲫,從而分析是哪個(gè)服務(wù)出現(xiàn)延時(shí)較高,今個(gè)到具體的服務(wù)中分析具體的問(wèn)題.
1.2 opentraing關(guān)鍵術(shù)語(yǔ)
- Traces(調(diào)用鏈)
一次調(diào)用的鏈路磺平,由TraceID唯一標(biāo)志魂仍,如一次請(qǐng)求則通常為一個(gè)trace,trace由所有途徑的span組成.
- Spans(調(diào)用跨度)
沒(méi)進(jìn)過(guò)一個(gè)服務(wù)則將span,同樣每個(gè)span由spanID唯一標(biāo)志.
- Span Tags(跨度標(biāo)簽)
span的標(biāo)簽褪秀,如一段span是調(diào)用redis的蓄诽,而可以設(shè)置redis的標(biāo)簽,這樣通過(guò)搜索redis關(guān)鍵字媒吗,我們就可以查詢(xún)出所有相關(guān)的span以及trace.
- Baggage Item(附帶數(shù)據(jù))
附加的數(shù)據(jù),由key:value組成仑氛,通過(guò)附加數(shù)據(jù),可以給調(diào)用鏈更多的描述信息闸英,不過(guò)考慮到傳輸問(wèn)題锯岖,附加數(shù)據(jù)應(yīng)該盡可能少.
1.3 jaeger & zipkin
目前開(kāi)源的實(shí)現(xiàn)有zipkin以及jaeger
- zipkin
zipkin主要由java編寫(xiě),通過(guò)各個(gè)語(yǔ)言的上報(bào)庫(kù)實(shí)現(xiàn)將數(shù)據(jù)上報(bào)到collector,collector再將數(shù)據(jù)存儲(chǔ)甫何,并通過(guò)API提供給前段UI展示.
- jaeger
jaeger由go實(shí)現(xiàn)出吹,由uber開(kāi)發(fā),目前是cloud native項(xiàng)目,流程與zipkin類(lèi)似辙喂,增加jager-agent這樣個(gè)組件捶牢,這個(gè)組件官方建議是每個(gè)機(jī)器都部署一個(gè),通過(guò)這個(gè)組件再將數(shù)據(jù)上報(bào)到collector存儲(chǔ)展示巍耗,另外秋麸,里面做了對(duì)zipkin的適配,其實(shí)一開(kāi)始他們用的也是zipkin炬太,為毛后面要自己造輪子灸蟆?見(jiàn)他們的解釋. 鏈接
總的來(lái)說(shuō)兩者都能基本滿足opentracing的功能,具體的選擇可以結(jié)合自身技術(shù)棧和癖好.
2. grpc集成opentracing
grpc集成opentracing并不難亲族,因?yàn)間rpc服務(wù)端以及調(diào)用端分別聲明了UnaryClientInterceptor以及UnaryServerInterceptor兩個(gè)回調(diào)函數(shù)炒考,因此只需要重寫(xiě)這兩個(gè)回調(diào)函數(shù)可缚,并在重寫(xiě)的回調(diào)函數(shù)中調(diào)用opentracing接口進(jìn)行上報(bào)即可.
初始化時(shí)傳入重寫(xiě)后的回調(diào)函數(shù),同時(shí)二選一初始化jager或者zipkin斋枢,然后你就可以開(kāi)啟分布式調(diào)用鏈追蹤之旅了.
完整的代碼見(jiàn)grpc-wrapper
2.1 client端
// OpenTracingClientInterceptor rewrite client's interceptor with open tracing
func OpenTracingClientInterceptor(tracer opentracing.Tracer) grpc.UnaryClientInterceptor {
return func(
ctx context.Context,
method string,
req, resp interface{},
cc *grpc.ClientConn,
invoker grpc.UnaryInvoker,
opts ...grpc.CallOption,
) error {
//從context中獲取spanContext,如果上層沒(méi)有開(kāi)啟追蹤帘靡,則這里新建一個(gè)
//追蹤,如果上層已經(jīng)有了杏慰,測(cè)創(chuàng)建子span.
var parentCtx opentracing.SpanContext
if parent := opentracing.SpanFromContext(ctx); parent != nil {
parentCtx = parent.Context()
}
cliSpan := tracer.StartSpan(
method,
opentracing.ChildOf(parentCtx),
wrapper.TracingComponentTag,
ext.SpanKindRPCClient,
)
defer cliSpan.Finish()
//將之前放入context中的metadata數(shù)據(jù)取出测柠,如果沒(méi)有則新建一個(gè)metadata
md, ok := metadata.FromOutgoingContext(ctx)
if !ok {
md = metadata.New(nil)
} else {
md = md.Copy()
}
mdWriter := MDReaderWriter{md}
//將追蹤數(shù)據(jù)注入到metadata中
err := tracer.Inject(cliSpan.Context(), opentracing.TextMap, mdWriter)
if err != nil {
grpclog.Errorf("inject to metadata err %v", err)
}
//將metadata數(shù)據(jù)裝入context中
ctx = metadata.NewOutgoingContext(ctx, md)
//使用帶有追蹤數(shù)據(jù)的context進(jìn)行g(shù)rpc調(diào)用.
err = invoker(ctx, method, req, resp, cc, opts...)
if err != nil {
cliSpan.LogFields(log.String("err", err.Error()))
}
return err
}
}
2.2 server端
//OpentracingServerInterceptor rewrite server's interceptor with open tracing
func OpentracingServerInterceptor(tracer opentracing.Tracer) grpc.UnaryServerInterceptor {
return func(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (resp interface{}, err error) {
//從context中取出metadata
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
md = metadata.New(nil)
}
//從metadata中取出最終數(shù)據(jù),并創(chuàng)建出span對(duì)象
spanContext, err := tracer.Extract(opentracing.TextMap, MDReaderWriter{md})
if err != nil && err != opentracing.ErrSpanContextNotFound {
grpclog.Errorf("extract from metadata err %v", err)
}
//初始化server 端的span
serverSpan := tracer.StartSpan(
info.FullMethod,
ext.RPCServerOption(spanContext),
wrapper.TracingComponentTag,
ext.SpanKindRPCServer,
)
defer serverSpan.Finish()
ctx = opentracing.ContextWithSpan(ctx, serverSpan)
//將帶有追蹤的context傳入應(yīng)用代碼中進(jìn)行調(diào)用
return handler(ctx, req)
}
}
由于opentracing定義了相關(guān)的接口缘滥,而jaeger以及zipkin進(jìn)行了相應(yīng)的實(shí)現(xiàn)轰胁,因此這里可以使用jaeger的也可以使用zipkin進(jìn)行上報(bào).
3.效果
jaeger服務(wù)主頁(yè)信息
每條調(diào)用鏈信息