引言
TCM(Tencent Cloud Mesh)是騰訊云上提供的基于Istio進(jìn)行增強(qiáng),和Istio API完全兼容的Service Mesh托管服務(wù),可以幫助用戶以較小的遷移成本和維護(hù)代價(jià)快速利用到Service Mesh提供的流量管理和服務(wù)治理能力。本系列文章將介紹TCM上的最佳實(shí)踐,本文將介紹如何利用Spring和OpenTracing簡(jiǎn)化應(yīng)用程序的Tracing上下文傳遞,以及如何在Istio提供的進(jìn)程間調(diào)用跟蹤基礎(chǔ)上實(shí)現(xiàn)方法級(jí)別的細(xì)粒度調(diào)用跟蹤。
分布式調(diào)用跟蹤和OpenTracing規(guī)范
什么是分布式調(diào)用跟蹤?
相比傳統(tǒng)的“巨石”應(yīng)用,微服務(wù)的一個(gè)主要變化是將應(yīng)用中的不同模塊拆分為了獨(dú)立的進(jìn)程。在微服務(wù)架構(gòu)下,原來進(jìn)程內(nèi)的方法調(diào)用成為了跨進(jìn)程的RPC調(diào)用。相對(duì)于單一進(jìn)程的方法調(diào)用,跨進(jìn)程調(diào)用的調(diào)試和故障分析是非常困難的,很難用傳統(tǒng)的調(diào)試器或者日志打印來對(duì)分布式調(diào)用進(jìn)行查看和分析。
如上圖所示,一個(gè)來自客戶端的請(qǐng)求經(jīng)過了多個(gè)微服務(wù)進(jìn)程。如果要對(duì)該請(qǐng)求進(jìn)行分析,則必須將該請(qǐng)求經(jīng)過的所有服務(wù)的相關(guān)信息都收集起來并關(guān)聯(lián)在一起,這就是“分布式調(diào)用跟蹤”。
什么是OpenTracing?
CNCF OpenTracing項(xiàng)目
OpenTracing[1]是CNCF[2](云原生計(jì)算基金會(huì))下的一個(gè)項(xiàng)目,其中包含了一套分布式調(diào)用跟蹤的標(biāo)準(zhǔn)規(guī)范,各種語言的API,編程框架和函數(shù)庫。OpenTracing的目的是定義一套分布式調(diào)用跟蹤的標(biāo)準(zhǔn),以統(tǒng)一各種分布式調(diào)用跟蹤的實(shí)現(xiàn)。目前已有大量支持OpenTracing規(guī)范的Tracer實(shí)現(xiàn)[3],包括Jager,Skywalking,LightStep等。在微服務(wù)應(yīng)用中采用OpenTracing API實(shí)現(xiàn)分布式調(diào)用跟蹤,可以避免vendor locking,以最小的代價(jià)和任意一個(gè)兼容OpenTracing的基礎(chǔ)設(shè)施進(jìn)行對(duì)接。
OpenTracing概念模型
OpenTracing的概念模型參見下圖:
圖片圖源自https://opentracing.io/
如圖所示,OpenTracing中主要包含下述幾個(gè)概念:
·Trace:描述一個(gè)分布式系統(tǒng)中的端到端事務(wù),例如來自客戶端的一個(gè)請(qǐng)求。
·Span:一個(gè)具有名稱和時(shí)間長度的操作,例如一個(gè)REST調(diào)用或者數(shù)據(jù)庫操作等。Span是分布式調(diào)用跟蹤的最小跟蹤單位,一個(gè)Trace由多段Span組成。
·Span context:分布式調(diào)用跟蹤的上下文信息,包括Trace id,Span id以及其它需要傳遞到下游服務(wù)的內(nèi)容。一個(gè)OpenTracing的實(shí)現(xiàn)需要將Span context通過某種序列化機(jī)制(Wire Protocol)在進(jìn)程邊界上進(jìn)行傳遞,以將不同進(jìn)程中的Span關(guān)聯(lián)到同一個(gè)Trace上。這些Wire Protocol可以是基于文本的,例如HTTP header,也可以是二進(jìn)制協(xié)議。
OpenTracing數(shù)據(jù)模型
一個(gè)Trace可以看成由多個(gè)相互關(guān)聯(lián)的Span組成的有向無環(huán)圖(DAG圖)。下圖是一個(gè)由8個(gè)Span組成的Trace:
上圖的trace也可以按照時(shí)間先后順序表示如下:
Span的數(shù)據(jù)結(jié)構(gòu)中包含以下內(nèi)容:
·name:Span所代表的操作名稱,例如REST接口對(duì)應(yīng)的資源名稱。
·Start timestamp:Span所代表操作的開始時(shí)間
·Finish timestamp:Span所代表的操作的的結(jié)束時(shí)間
·Tags:一系列標(biāo)簽,每個(gè)標(biāo)簽由一個(gè)key value鍵值對(duì)組成。該標(biāo)簽可以是任何有利于調(diào)用分析的信息,例如方法名,URL等。
·SpanContext:用于跨進(jìn)程邊界傳遞Span相關(guān)信息,在進(jìn)行傳遞時(shí)需要結(jié)合一種序列化協(xié)議(Wire Protocol)使用。
·References:該Span引用的其它關(guān)聯(lián)Span,主要有兩種引用關(guān)系,Childof和FollowsFrom。
·Childof:最常用的一種引用關(guān)系,表示Parent Span和Child Span之間存在直接的依賴關(guān)系。例RPC服務(wù)端Span和RPC客戶端Span,或者數(shù)據(jù)庫SQL插入Span和ORM Save動(dòng)作Span之間的關(guān)系。
·FollowsFrom:如果Parent Span并不依賴Child Span的執(zhí)行結(jié)果,則可以用FollowsFrom表示。例如網(wǎng)上商店購物付款后會(huì)向用戶發(fā)一個(gè)郵件通知,但無論郵件通知是否發(fā)送成功,都不影響付款成功的狀態(tài),這種情況則適用于用FollowsFrom表示。
跨進(jìn)程調(diào)用信息傳播
SpanContext是OpenTracing中一個(gè)讓人比較迷惑的概念。在OpenTracing的概念模型中提到SpanContext用于跨進(jìn)程邊界傳遞分布式調(diào)用的上下文。但實(shí)際上OpenTracing只定義一個(gè)SpanContext的抽象接口,該接口封裝了分布式調(diào)用中一個(gè)Span的相關(guān)上下文內(nèi)容,包括該Span所屬的Trace id,Spanid以及其它需要傳遞到downstream服務(wù)的信息。SpanContext自身并不能實(shí)現(xiàn)跨進(jìn)程的上下文傳遞,需要由Tracer(Tracer是一個(gè)遵循OpenTracing協(xié)議的實(shí)現(xiàn),如Jaeger,Skywalking的Tracer)將SpanContext序列化后通過Wire Protocol傳遞到下一個(gè)進(jìn)程中,然后在下一個(gè)進(jìn)程將SpanContext反序列化,得到相關(guān)的上下文信息,以用于生成Child Span。
為了為各種具體實(shí)現(xiàn)提供最大的靈活性,OpenTracing只是提出了跨進(jìn)程傳遞SpanContext的要求,并未規(guī)定將SpanContext進(jìn)行序列化并在網(wǎng)絡(luò)中傳遞的具體實(shí)現(xiàn)方式。各個(gè)不同的Tracer可以根據(jù)自己的情況使用不同的Wire Protocol來傳遞SpanContext。
在基于HTTP協(xié)議的分布式調(diào)用中,通常會(huì)使用HTTP Header來傳遞SpanContext的內(nèi)容。常見的Wire Protocol包含Zipkin使用的b3 HTTP header[4],Jaeger使用的uber-trace-id HTTP Header[5],LightStep使用的"x-ot-span-context"HTTP Header等。Istio/Envoy支持b3 header和x-ot-span-context header,可以和Zipkin,Jaeger及LightStep對(duì)接。其中b3 HTTP header的示例如下:
Istio對(duì)分布式調(diào)用跟蹤的支持
Istio/Envoy為微服務(wù)提供了開箱即用的分布式調(diào)用跟蹤功能。在安裝了Istio和Envoy的微服務(wù)系統(tǒng)中,Envoy會(huì)攔截服務(wù)的入向和出向請(qǐng)求,為微服務(wù)的每個(gè)調(diào)用請(qǐng)求自動(dòng)生成調(diào)用跟蹤數(shù)據(jù)。通過在服務(wù)網(wǎng)格中接入一個(gè)分布式跟蹤的后端系統(tǒng),例如zipkin或者Jaeger,就可以查看一個(gè)分布式請(qǐng)求的詳細(xì)內(nèi)容,例如該請(qǐng)求經(jīng)過了哪些服務(wù),調(diào)用了哪個(gè)REST接口,每個(gè)REST接口所花費(fèi)的時(shí)間等。
需要注意的是,Istio/Envoy雖然在此過程中完成了大部分工作,但還是要求對(duì)應(yīng)用代碼進(jìn)行少量修改:應(yīng)用代碼中需要將收到的上游HTTP請(qǐng)求中的b3 header拷貝到其向下游發(fā)起的HTTP請(qǐng)求的header中,以將調(diào)用跟蹤上下文傳遞到下游服務(wù)。這部分代碼不能由Envoy代勞,原因是Envoy并不清楚其代理的服務(wù)中的業(yè)務(wù)邏輯,無法將入向請(qǐng)求和出向請(qǐng)求按照業(yè)務(wù)邏輯進(jìn)行關(guān)聯(lián)。這部分代碼量雖然不大,但需要對(duì)每一處發(fā)起HTTP請(qǐng)求的代碼都進(jìn)行修改,非常繁瑣而且容易遺漏。當(dāng)然,可以將發(fā)起HTTP請(qǐng)求的代碼封裝為一個(gè)代碼庫來供業(yè)務(wù)模塊使用,來簡(jiǎn)化該工作。
下面以一個(gè)簡(jiǎn)單的網(wǎng)上商店示例程序來展示Istio如何提供分布式調(diào)用跟蹤。該示例程序由eshop,inventory,billing,delivery幾個(gè)微服務(wù)組成,結(jié)構(gòu)如下圖所示:
eshop微服務(wù)接收來自客戶端的請(qǐng)求,然后調(diào)用inventory,billing,delivery這幾個(gè)后端微服務(wù)的REST接口來實(shí)現(xiàn)用戶購買商品的checkout業(yè)務(wù)邏輯。
本例的代碼可以從github下載:https://github.com/aeraki-framework/method-level-tracing-with-istio
如下面的代碼所示,我們需要在eshop微服務(wù)的應(yīng)用代碼中傳遞b3 HTTP Header。
下面我們來測(cè)試一下eshop實(shí)例程序。我們可以自己搭建一個(gè)Kubernetes集群并安裝Istio以用于測(cè)試。這里為了方便,直接使用騰訊云上提供的全托管的服務(wù)網(wǎng)格TCM[6],并在創(chuàng)建的Mesh中加入了一個(gè)容器服務(wù)TKE[7]集群來進(jìn)行測(cè)試。
在TKE集群中部署該程序,查看Istio分布式調(diào)用跟蹤的效果。
·在瀏覽器中打開地址:【http://${INGRESS_EXTERNAL_IP}/checkout】(http://%24%7Bingress_external_ip%7D/checkout),以觸發(fā)調(diào)用eshop示例程序的REST接口。
·在瀏覽器中打開TCM的界面,查看生成的分布式調(diào)用跟蹤信息。
TCM圖形界面直觀地展示了這次調(diào)用的詳細(xì)信息,可以看到客戶端請(qǐng)求從Ingressgateway進(jìn)入到系統(tǒng)中,然后調(diào)用了eshop微服務(wù)的checkout接口,checkout調(diào)用有三個(gè)child span,分別對(duì)應(yīng)到inventory,billing和delivery三個(gè)微服務(wù)的REST接口。
使用OpenTracing來傳遞分布式跟蹤上下文
OpenTracing提供了基于Spring的代碼埋點(diǎn),因此我們可以使用OpenTracing Spring框架來提供HTTP header的傳遞,以避免這部分硬編碼工作。在Spring中采用OpenTracing來傳遞分布式跟蹤上下文非常簡(jiǎn)單,只需要下述兩個(gè)步驟:
·在Maven POM文件中聲明相關(guān)的依賴,一是對(duì)OpenTracing SPring Cloud Starter的依賴;另外由于Istio采用了Zipkin的上報(bào)接口,我們也需要引入Zipkin的相關(guān)依賴。
·在Spring Application中聲明一個(gè)Tracer bean。如下所示,注意我們需要把Istio中的zpkin上報(bào)地址設(shè)置到OKHttpSernder中。
部署采用OpenTracing進(jìn)行HTTP header傳遞的程序版本,其調(diào)用跟蹤信息如下所示:
從上圖中可以看到,相比在應(yīng)用代碼中直接傳遞HTTP header的方式,采用OpenTracing進(jìn)行代碼埋點(diǎn)后,相同的調(diào)用增加了7個(gè)名稱前綴為spring-boot的Span,這7個(gè)Span是由OpenTracing的tracer生成的。雖然我們并沒有在代碼中顯示創(chuàng)建這些Span,但OpenTracing的代碼埋點(diǎn)會(huì)自動(dòng)為每一個(gè)REST請(qǐng)求生成一個(gè)Span,并根據(jù)調(diào)用關(guān)系關(guān)聯(lián)起來。
OpenTracing生成的這些Span為我們提供了更詳細(xì)的分布式調(diào)用跟蹤信息,從這些信息中可以分析出一個(gè)HTTP調(diào)用從客戶端應(yīng)用代碼發(fā)起請(qǐng)求,到經(jīng)過客戶端的Envoy,再到服務(wù)端的Envoy,最后到服務(wù)端接受到請(qǐng)求各個(gè)步驟的耗時(shí)情況。從圖中可以看到,Envoy轉(zhuǎn)發(fā)的耗時(shí)在1毫秒左右,相對(duì)于業(yè)務(wù)代碼的處理時(shí)長非常短,對(duì)這個(gè)應(yīng)用而言,Envoy的處理和轉(zhuǎn)發(fā)對(duì)于業(yè)務(wù)請(qǐng)求的處理效率基本沒有影響。
在Istio調(diào)用跟蹤鏈中加入方法級(jí)的調(diào)用跟蹤信息
Istio/Envoy提供了跨服務(wù)邊界的調(diào)用鏈信息,在大部分情況下,服務(wù)粒度的調(diào)用鏈信息對(duì)于系統(tǒng)性能和故障分析已經(jīng)足夠。但對(duì)于某些服務(wù),需要采用更細(xì)粒度的調(diào)用信息來進(jìn)行分析,例如一個(gè)REST請(qǐng)求內(nèi)部的業(yè)務(wù)邏輯和數(shù)據(jù)庫訪問分別的耗時(shí)情況。在這種情況下,我們需要在服務(wù)代碼中進(jìn)行埋點(diǎn),并將服務(wù)代碼中上報(bào)的調(diào)用跟蹤數(shù)據(jù)和Envoy生成的調(diào)用跟蹤數(shù)據(jù)進(jìn)行關(guān)聯(lián),以統(tǒng)一呈現(xiàn)Envoy和服務(wù)代碼中生成的調(diào)用數(shù)據(jù)。
在方法中增加調(diào)用跟蹤的代碼是類似的,因此我們用AOP+Annotation的方式實(shí)現(xiàn),以簡(jiǎn)化代碼。首先定義一個(gè)Traced注解和對(duì)應(yīng)的AOP實(shí)現(xiàn)邏輯:
然后在需要進(jìn)行調(diào)用跟蹤的方法上加上Traced注解:
demo程序的master branch已經(jīng)加入了方法級(jí)代碼跟蹤,可以直接部署。
效果如下圖所示,可以看到trace中增加了transfer和save2db兩個(gè)方法級(jí)的Span。
可以打開一個(gè)方法的Span,查看詳細(xì)信息,包括Java類名和調(diào)用的方法名等,在AOP代碼中還可以根據(jù)需要添加出現(xiàn)異常時(shí)的異常堆棧等信息。
總結(jié)
Istio/Envoy為微服務(wù)應(yīng)用提供了分布式調(diào)用跟蹤功能,提高了服務(wù)調(diào)用的可見性。我們可以使用OpenTracing來代替應(yīng)用硬編碼,以傳遞分布式跟蹤的相關(guān)http header;還可以通過OpenTracing將方法級(jí)的調(diào)用信息加入到Istio/Envoy缺省提供的調(diào)用鏈跟蹤信息中,以提供更細(xì)粒度的調(diào)用跟蹤信息。
下一步
除了同步調(diào)用之外,異步消息也是微服務(wù)架構(gòu)中常見的一種通信方式。在下一篇文章中,我將繼續(xù)利用eshop demo程序來探討如何通過OpenTracing將Kafka異步消息也納入到Istio的分布式調(diào)用跟蹤中。
參考資料
[1]OpenTracing:【http://https//opentracing.io】
[2]CNCF:【https://www.cncf.io/】
[3]OpenTracing規(guī)范的Tracer實(shí)現(xiàn):【https://opentracing.io/docs/supported-tracers/】
[4]b3 HTTP header:【https://github.com/openzipkin/b3-propagation】
[5]uber-trace-id HTTP Header:【https://www.jaegertracing.io/docs/1.7/client-libraries/#trace-span-identity】
[6]TCM:【https://console.cloud.tencent.com/tke2/mesh?rid=16】
[7]TKE:【https://console.cloud.tencent.com/tke2/cluster/startUp】
[8]本文中eshop示例程序的源代碼:【https://github.com/aeraki-framework/method-level-tracing-with-istio】
[9]Opentracing docs:【https://opentracing.io/docs/】
[10]Opentracing specification:【https://github.com/opentracing/specification/blob/master/specification.md】
[11]Opentracing wire protocols:【https://github.com/opentracing/specification/blob/master/rfc/trace_identifiers.md】
[12]Istio Trace context propagation:【https://istio.io/docs/tasks/telemetry/distributed-tracing/overview/#trace-context-propagation】
[13]Zipkin-b3-propagation:【https://github.com/apache/incubator-zipkin-b3-propagation】
[14]OpenTracing Project Deep Dive:【https://www.youtube.com/watch?v=ySR_FVNX4bQ&t=184s】