微服務中臺技術解析之全鏈路分布式追蹤系統實踐

2020-11-24 InfoQ

Biz-UI團隊在核心業務系統的開發過程中,將具有共性的功能模塊抽象出來,逐漸完成了中臺的構建,為業務邏輯提供了強有力的基礎組件支撐。其中分布式追蹤系統作為一個重要的組成部分,為監控服務之間的調用、定位和調試線上問題,提供了有力的支撐。本文將詳細剖析FreeWheel Biz-UI團隊從0到1構建和改進全鏈路分布式追蹤系統的過程。

微服務-捉蟲記

小志所在的技術部門剛剛對臃腫的單體應用完成了拆解,推行微服務理念,將之前雜糅得不可開交的代碼按業務模塊拆分成一個一個的微服務。隨著項目的推動,大家確實感受到微服務帶來的收益,拆解完以後對單個微服務維護起來也更加方便。但與此同時也帶來了一些之前未曾遇到的問題......

一陣急促的手機鈴聲打斷了小志的思緒,看著熟悉的來電號碼,小志心想真是怕什麼來什麼,新上的服務凌晨又出問題了。

「喂,小志啊,線上報警了,挺緊急的,你趕緊看一下吧,一會線上聊。」

熟練地翻開報警郵件,處理這類問題對小志來說已經輕車熟路。詳細分析了一下報警內容,小志斷定是下遊服務出問題導致的報警。

「老李,我是小志,我們這邊剛剛出來一個報警,挺嚴重的,剛看了一下系統日誌,是在調你們訂單服務的時候出錯了,你幫看一下吧,我一會把日誌發你釘釘。」

老李經過排查,發現是上遊服務的問題,於是抓緊聯繫老錢。「老錢啊,還沒睡呢吧?剛剛小志那邊出問題,影響了不少客戶。查日誌發現是訂單這邊報錯了,我看了一下訂單服務的日誌,是你們那邊的庫存服務報了不少500,你趕緊起來看一下吧。」

屏幕前小志、老李、老錢正在熱火朝天地捉蟲找bug.

微服務,作為一個近幾年非常火熱的話題,切切實實解決了很多單體應用的痛點,但與此同時也帶來了一些新的痛點。

FreeWheel核心業務部門結合自身的實際情況,以微服務的方式對之前的單一應用做了拆分。同時,為了避免上面故事裡的情況發生,我們引入了分布式追蹤系統用來解決【如何在微服務系統中快速定位問題?】、【如何觀察複雜的調用鏈、分析調用的網絡結構?】等等問題。

分布式追蹤系統

分布式追蹤系統(Distributed Tracing System)可以用來解決微服務系統中的常問題定位、bug 追蹤、網絡結構分析等問題。該系統的數據模型最早由Google’s Dapper 論文提出,主要包含如下幾個部分:

  • Trace: 用來描述分布式系統中一個完整的調用鏈,每一個Trace會有一個獨有的Trace ID。
  • Span: 分布式系統中的一個小的調用單元,可以是一個微服務中的service, 也可以是一次方法調用,甚至一個簡單的代碼塊調用。Span可以包含起始時間戳、log等信息。每一個Span會有一個獨有的Span ID.
  • Span Context: 包含額外Trace信息的數據結構,span context可以包含Trace ID、Span ID, 以及其他任何需要向下遊service傳遞的trace信息。

在這基礎上,社區為了實現各個程式語言和各種框架的接口統一,發展出了OpenTracing Specification 以及 OpenTracing API。後來業界也相繼推出了幾款比較成熟的產品,如Zipkin、Jaeger、LightStep、DataDog等。

分布式追蹤系統是如何解決跨服務調用時的問題定位的呢?對於一次客戶調用,分布式追蹤系統會在請求的入口處生成一個TraceID,用這個TraceID把客戶請求進入每個微服務中的調用日誌串聯起來,形成一個時序圖。如下圖所示,假設A的兩端表示一次客戶調用的開始和結束,中間會經過類似B、 C、D、 E等後端服務。此時如果E出問題,就可以很快速地定位到,而不用同時讓A、B、C、D都參與進來查問題。

(圖片來源:https://www.jaegertracing.io/img/spans-traces.png)

Biz-UI 分布式追蹤系統實踐

技術選型

在FreeWheel 核心業務系統微服務搭建過程中,我們深度調研了現有的分布式追蹤系統解決方案,針對其中幾個比較重要的選型指標做了深度的討論。

基於上面幾個指標,我們對市面上主流的開源項目進行篩選,包括Jeager、Zipkin等,考慮到市場佔有率、項目成熟度、項目契合度,我們最終選擇了同為Golang開發的Jaeger。Jeager Tracing框架下主要包含三大模塊:TracingAgent, Tracing Collector 和 Tracing Query。

整體的架構如下圖所示:

(圖片來源:https://www.jaegertracing.io/img/architecture-v1.png)

落地實踐與優化改造

新系統實施的第一步往往是分析現有技術環境,目的是儘可能地復用已有的功能、模塊,運維環境等。這樣能大大減少後續的維護、運維等成本。

FreeWheel核心業務平臺現有的基礎環境包括如下幾點:

  • 首先是現有微服務對外提供的接口協議多種多樣,例如gRPC、HTTP(基於gRPC-Gateway)、HTTP(裸HTTP) 等。
  • FreeWheel現有一套ELK+Kafka集群用來收集和分析系統日誌。
  • 微服務的基礎運行環境基於Kubernetes+Istio,除了少許的特殊服務運行在物理機上以外,絕大部分業務服務都運行在K8s集群(AWS EKS)中,也就是說每個服務的實例都作為集群中的一個pod在運行。

基於以上背景,我們設計了Tracing系統實施方案,並對部分模塊進行了升級改造。

首先,由於各個微服務對外提供的接口也不盡統一,現有的接口包括gRPC、gRPC-Gateway、HTTP,甚至WebSocket。我們在Jeager-client基礎上做了一層封裝,實現了一個Tracing client Lib,該lib可以針對不同的通訊協議對流量進行劫持,並將Trace 信息注入到請求中。還擴展性地加入了過濾器(過濾給定特徵的流量)、 TraceID生成、TraceID提取,與Zipkin Header兼容等功能。這部分會隨著平臺的不斷擴展和改造進行持續的更新和維護。

另外,為了充分利用公司現有的ElasticSearch 集群,我們決定用ElasticSearch作為追蹤系統的後端存儲。由於使用場景為寫多讀少,為了保護ElasticSerach,我們決定用Kafka作為緩衝,即對Collecor進行擴展,將數據進行處理並轉換成ElasticSearch可讀的json格式寫入Kafka, 再通過logstash消費寫入ElasticSearch中。

此外,對於Spark dependency Job,同樣需要將數據轉換為對應的Json格式寫入Kafka,最終存儲到ElasticSearch。這裡對Spark Dependency Job的輸出部分做了擴展,讓其支持向Kafka中導入數據。最後,由於微服務系統內部部署環境的差異,我們提供了兼容K8s sidecar, K8s Daemonset, On-perm daeom process 等部署方式。

新設計的架構如下圖所示:

數據採集層

中間件

對於基於Golang開發的微服務,Trace信息在服務內部傳播主要依賴context.Context。FreeWheel核心業務系統中一般來講支持兩種通訊協議:HTTP and GRPC,其中HTTP接口主要依賴GRPC-Gateway自動生成。當然也有一部分服務不涉及GRPC, 直接對外暴露HTTP接口。這裡HTTP主要面向的調用方是OpenAPI或者前端UI。同時,服務與服務之間一般採用GRPC方式通訊。對於這類場景,Tracinglib提供了必要的組件供業務微服務使用。其傳播過程如下圖所示:

針對入口流量,Tracing Client Lib封裝了HTTP中間件、 GRPC中間件,以及與GRPC-Gateway這一層的兼容。針對出口流量,Tracing Client Lib 封裝了GRPC-Client 中間件。這裡的「封裝」不單單指對Jaeger client lib提供方法的簡單wrapper,還包括諸如Tracing狀態監測、請求過濾等功能。比較典型的像 "/check_alive", "/metrics"這類沒有必要trace的請求可以通過請求過濾的功能過濾掉從而不記錄Trace。

Istio集成

了解Istio的同學應該知道,Istio本身支持Jaeger Tracing集成。對於跨服務的請求,Istio可以劫持諸如GRPC/HTTP等類型的流量,生成對應的Trace信息。因此如果能將業務代碼中的Trace信息與Istio進行集成,就能夠監控到整個調用網絡與業務內部Trace的完整信息,方便查看Istio sidecar到服務這個調用過程的網絡情況。

問題在於,Istio集成Tracing時採取了Zipkin B3 Header標準,其格式如下:

X-B3-TraceId: {TraceID}X-B3-ParentSpanId: {ParentSpanID}X-B3-SpanId: {SpanID}X-B3-Sampled: {SampleFlag}X-B3-TraceId: {TraceID}X-B3-ParentSpanId: {ParentSpanID}X-B3-SpanId: {SpanID}X-B3-Sampled: {SampleFlag}

而FreeWheel核心業務系統內部所採用的TracerHeader格式為:

FW-Trace-ID: {TraceID}:{SpanID}:{ParentSpanID}:{SampleFlag}

並且FW Trace Header被廣泛地應用在業務代碼中,集成了諸如log, change_history等服務,一時間難以被完全替換。針對這個問題,我們重寫了Jaeger Client中的將Injector和Extractor,其接口定義如下:

//Span body{ "traceID": "5082be69746ed84a", "spanID": "5082be69746ed84a", "operationName": "HTTP GET", "startTime": ..., "duration": 616, "references": [ { "refType": "CHILD_OF", "spanID": "14a9e000a96a2671", "traceID": "259f404f8409a4d7" } ], "tags": [ { "key": "http.url", "type": "string", "value": "/services/v3/**.xml" }, { "key": "http.status_code", "type": "int64", "value": "500" }, //... ], "logs": [], "process": { "serviceName": "your_service_name", "tags": [ { "key": "hostname", "type": "string", "value": "xx-mac" }, //... ] }}

新實現的Injector和Extractor同時兼容B3 Header和Freewheel Trace Header。服務接收到請求時會優先查看有沒有B3 Header,在生成新Span的時候同時插入FreeWheel Trace Header。即FreeWheel Trace Header繼續在服務內部使用,跨服務之間的調用以B3 Header為主。

X-B3-TraceId: {TraceID}X-B3-ParentSpanId: {ParentSpanID}X-B3-SpanId: {SpanID}X-B3-Sampled: {SampleFlag}FW-Trace-ID: {TraceID}:{SpanID}:{ParentSpanID}:{SampleFlag}

數據緩衝與中轉層

上文提到數據存儲選用ElasticSearch, 數據的採集與存儲是一個典型的寫多讀少的業務場景。對這類場景,我們引入Kafka作為數據的緩衝與中轉層。基於這個思路我們對Collector進行了改造,加入了Collector Kafka Producer組件,在Collector上將span信息轉為json發給Kafka,然後由Logstash作為Consumer存儲到ElasticSearch。對於Trace信息,ElasticSearch存儲主要分為兩大部分:服務/操作索引和Span 索引。服務/操作索引主要用來為query ui提供快速檢索服務(Service Name)和操作(Operation Name), 結構如下:

//Span body{ "traceID": "5082be69746ed84a", "spanID": "5082be69746ed84a", "operationName": "HTTP GET", "startTime": ..., "duration": 616, "references": [ { "refType": "CHILD_OF", "spanID": "14a9e000a96a2671", "traceID": "259f404f8409a4d7" } ], "tags": [ { "key": "http.url", "type": "string", "value": "/services/v3/**.xml" }, { "key": "http.status_code", "type": "int64", "value": "500" }, //... ], "logs": [], "process": { "serviceName": "your_service_name", "tags": [ { "key": "hostname", "type": "string", "value": "xx-mac" }, //... ] }}

Span結構體由Tracing客戶端生成,主要一下幾大部分:

  • 基礎trace信息,如 traceID, spanID, parentID, operationName,duration。
  • Tags,這部分主要包含業務邏輯相關的信息如request method, url, response code等。
  • References,主要用來表示Span的父子從屬關係。
  • Process,服務的基本信息。
  • Logs,用於給業務代碼擴展使用。

//Span body{ "traceID": "5082be69746ed84a", "spanID": "5082be69746ed84a", "operationName": "HTTP GET", "startTime": ..., "duration": 616, "references": [ { "refType": "CHILD_OF", "spanID": "14a9e000a96a2671", "traceID": "259f404f8409a4d7" } ], "tags": [ { "key": "http.url", "type": "string", "value": "/services/v3/**.xml" }, { "key": "http.status_code", "type": "int64", "value": "500" }, //... ], "logs": [], "process": { "serviceName": "your_service_name", "tags": [ { "key": "hostname", "type": "string", "value": "xx-mac" }, //... ] }}

存儲與計算層


這一層主要用於對Trace數據進行持久化和離線分析。利用ElasticSearch會對數據進行分片,分index的存儲,防止歷史數據丟失,方便對歷史問題進行回溯。不過既然提到持久化就難免要考慮數據規模的問題,持續大量的歷史數據寫入到ElasticSeach會不斷增加其負擔,而且對於過於久遠的歷史數據,被檢索到的頻率也相對較小。這裡我們採取定期歸檔的策略,對於超過30天的數據進行歸檔,轉存到ES之外以備不時之需。ElasticSearch只對相對較「熱」的數據提供檢索服務。

離線分析主要用於對EalsticSearch中的Span數據進行分析,上文我們提到一個Span數據結構包含其自身的TraceID和它父節點的TraceID,每一個節點都包含自身從屬與哪個服務。

這裡我們只關心跨服務之間的調用關係,例如上圖,離線分析時只考慮A, B, C, E這幾個節點,由於D節點與C, E節點都在服務3內部,所以將其忽略。分析出來的結果如圖所示

展示層

展示層主要指Query-UI, 功能是從ElacticSearch中查詢數據,對具有相同TraceID的數據進行聚合,並在前端進行渲染。從QueryUI中可以清晰的看到一條請求經歷了幾個不同的服務(以不同顏色標註),在每個服務中的到達時間和結束時間,整個請求總共經歷的時間等。


未來展望

隨著FreeWheel核心業務平臺不斷地擴充和演進,分布式追蹤系統也需要進行不斷升級改造以適配業務需求。例如部分業務代碼正在嘗試Serverless的方式,也就要求Tracing系統支持諸如AWS Lambda等使用場景。對於這種形式的需求,我們將緊跟業務,持續調研,以期服務更多的場景。此外,現有服務調用拓撲網絡是基於離線數據生成的,我們也期望未來能找到一些在線處理的解決方案,如Flink、Spark Streaming等,做到實時的調用關系統計。

參考閱讀

  • Jaeger Tracing https://www.jaegertracing.io/
  • OpenTracingSpecification https://github.com/opentracing/specification
  • Google Dapper paper https://research.google/pubs/pub36356/

作者介紹:

馮剛,FreeWheel Biz-UI 高級研發工程師,數年Golang後端研發經驗,熟悉微服務體系架構,熱衷於探索與分享新技術。目前致力於OpenAPI重構,服務化等相關實踐。

延伸閱讀:

關注我並轉發此篇文章,私信我「領取資料」,即可免費獲得InfoQ價值4999元迷你書,點擊文末「了解更多」,即可移步InfoQ官網,獲取最新資訊~

相關焦點

  • 微服務平臺之全鏈路追蹤
    多個微服務之間存在調用關係,如何在系統運行時總覽一個系統中微服務間的拓撲關係?如何完整還原一次請求的鏈路情況?以上這些問題可以藉助鏈路追蹤技術進行解決。鏈路追蹤組件通過在微服務應用中以代碼侵入或者非侵入的方式進行數據表示、埋點、傳遞、收集、存儲、展示等技術手段,達到將一次分布式請求還原成調用鏈路,將一次分布式請求的調用情況集中展示,比如各個服務節點上的耗時、請求具體到達哪臺機器上、每個服務節點的請求狀態等等。
  • 微服務平臺之全鏈路追蹤
    多個微服務之間存在調用關係,如何在系統運行時總覽一個系統中微服務間的拓撲關係?如何完整還原一次請求的鏈路情況?以上這些問題可以藉助鏈路追蹤技術進行解決。鏈路追蹤組件通過在微服務應用中以代碼侵入或者非侵入的方式進行數據表示、埋點、傳遞、收集、存儲、展示等技術手段,達到將一次分布式請求還原成調用鏈路,將一次分布式請求的調用情況集中展示,比如各個服務節點上的耗時、請求具體到達哪臺機器上、每個服務節點的請求狀態等等。
  • 「技術貼」微服務中臺技術解析之分布式事務方案和實踐
    本文將圍繞分布式事務這一技術議題,介紹FreeWheel核心業務系統在相關領域的業務需求、技術決策和線上實踐。>早期 FreeWheel 核心業務系統是一個單體應用(Monolith):在同一臺伺服器的同一個進程中,完成接收客戶請求、處理請求、數據存儲、返迴響應等步驟。
  • 「系統架構」什麼是鏈路追蹤?分布式系統如何實現鏈路追蹤?
    在分布式系統,尤其是微服務系統中,一次外部請求往往需要內部多個模塊,多個中間件,多臺機器的相互調用才能完成。在這一系列的調用中,可能有些是串行的,而有些是並行的。在這種情況下,我們如何才能確定這整個請求調用了哪些應用?哪些模塊?哪些節點?以及它們的先後順序和各部分的性能如何呢?這就是涉及到鏈路追蹤。什麼是鏈路追蹤?
  • 原來10張圖就可以搞懂分布式鏈路追蹤系統原理
    分布式系統為什麼需要鏈路追蹤隨著網際網路業務快速擴展,軟體架構也日益變得複雜,為了適應海量用戶高並發請求,系統中越來越多的組件開始走向分布式化,如單體架構拆分為微服務、服務內緩存變為分布式緩存、服務組件通信變為分布式消息,這些組件共同構成了繁雜的分布式網絡
  • 分布式鏈路追蹤
    但在級聯錯誤中,錯誤日誌產生的實在是太多了,不同的服務不同的鏈路幾乎都擠在一起,修復時間都主要用在了翻日誌上,翻了好幾頁才找到了相對有效的錯誤信息。如果下一次再出現類似的問題,可不得了,MTTR 太久了,4 個 9 很快就會用完。
  • 阿里P8根據企業需求講解微服務分布式系統開發527頁進階筆記
    雖然Spring Cloud能夠有雙搭P微服務系統,但微服務系統只是分布式系統的一種形式,它並不能解決分布式系統的所有問題,例如,分布式緩存、會話、資料庫及其事務等,都不能通過Spring Cloud來有效處理。但這些問題又是企業實施微服務系統時必須要面對的,甚至是一些企業的難點和痛點。因此,本書在詳細介紹Spring Cloud的基礎上,還會對常用的分布式技術進行講解,以滿足企業的需要。
  • 螞蟻金服的「技術中臺」:億級分布式系統架構實踐
    在微服務架構中,由網關作為接入層,提供輕量級的負載均衡、協議轉換、鑑權等服務,微服務通常有服務治理框架,如DUBBO等,提供服務治理、服務註冊、服務發現、隔離等。二、分布式架構實踐舉例--分布式TA系統
  • 40 張圖看懂分布式追蹤系統原理及實踐
    本文將會從以下幾個方面來闡述分布式追蹤系統原理及作用SkyWalking的原理及架構設計我司在分布式調用鏈上的實踐分布式追蹤系統的原理及作用如何衡量一個接口的性能好壞,一般我們至少會關注以下三個指標每個服務 Service A,B,C,D 都有好幾臺機器。怎麼知道某個請求調用了服務的具體哪臺機器呢?
  • 40張圖看懂分布式追蹤系統原理及實踐
    本文將會從以下幾個方面來闡述分布式追蹤系統原理及作用SkyWalking的原理及架構設計我司在分布式調用鏈上的實踐分布式追蹤系統的原理及作用每個服務 Service A,B,C,D 都有好幾臺機器。怎麼知道某個請求調用了服務的具體哪臺機器呢?
  • 40 張圖看懂分布式追蹤系統原理及實踐
    本文將會從以下幾個方面來闡述:分布式追蹤系統原理及作用SkyWalking的原理及架構設計我司在分布式調用鏈上的實踐分布式追蹤系統的原理及作用如何衡量一個接口的性能好壞,一般我們至少會關注以下三個指標:接口的 RT 你怎麼知道?
  • 40 一圖看懂分布式追蹤系統原理及實踐
    本文將會從以下幾個方面來闡述:分布式追蹤系統原理及作用SkyWalking的原理及架構設計我司在分布式調用鏈上的實踐每個服務 Service A,B,C,D 都有好幾臺機器。怎麼知道某個請求調用了服務的具體哪臺機器呢?
  • 打造企業級微服務平臺架構,分布式應用場景管理
    微服務系統可以在「自己的程序」中運行,並通過「輕量級設備與HTTP型API進行溝通」。關鍵在於該服務可以在自己的程序中運行。通過這一點我們就可以將服務公開與微服務平臺架構(在現有系統中分布一個API)區分開來。在服務公開中,許多服務都可以被內部獨立進程所限制。
  • 40張圖帶你看懂分布式追蹤系統原理及實踐
    本文將會從以下幾個方面來闡述分布式追蹤系統原理及作用SkyWalking的原理及架構設計我司在分布式調用鏈上的實踐分布式追蹤系統的原理及作用每個服務 Service A,B,C,D 都有好幾臺機器。怎麼知道某個請求調用了服務的具體哪臺機器呢?
  • Tempo - 分布式Loki鏈路追蹤利器
    是Grafana Labs在ObservabilityCON 2020大會上新開源的一個用於做分布式式追蹤的後端服務之前小白有提到Grafana Labs的雲原生Observability宇宙只剩下trace部分,那麼今天就拿Loki的分布式追蹤來體驗下這Observability的最後一環吧。
  • 螞蟻金服的「技術中臺」:億級分布式系統架構實踐
    二、分布式架構實踐舉例--分布式TA系統07 傳統TA系統架構(圖片來源:阿里雲峰會)傳統TA系統架構,清算串行效率低,無法通過增加機器線性擴展性能,一般使用大事務,出現問題全部回滾。08 分布式TA系統架構(圖片來源:阿里雲峰會)分布式TA系統架構,結構更合理,也更複雜。分成了:接入層、業務服務層、SOFAStack層、LAAS、運維工具鏈、治理控制。接入層:包括協議轉換、訪問控制、文件傳輸、運維工作檯。業務服務層:即業務核心邏輯服務,如:帳戶、交易、帳單、清算等。
  • 基於Spring Cloud如何構建分布式系統?
    所以說單機系統已經不可能滿足現在網際網路了,為了滿足網際網路的苛刻要求,網站系統已經從單機系統發展為多臺機器協作的系統,因而網際網路系統已經從單機系統演變為多臺機器的系統,我們把這種多臺機器相互協作完成企業業務功能的系統,稱為分布式系統。
  • 一口氣說出「分布式追蹤系統」原理
    在微服務架構中,一次請求往往涉及到多個模塊,多個中間件,多臺機器的相互協作才能完成。本文將會從以下幾個方面來闡述:分布式追蹤系統原理及作用SkyWalking 的原理及架構設計我司在分布式調用鏈上的實踐
  • 輕鬆玩轉全鏈路監控
    在微服務時代,由於服務的拆分,單個用戶請求會經過多個微服務應用,形成複雜的調用鏈路,使傳統的依賴於單機業務日誌的監控手段無從下手,這就需要建立全新的監控機制,幫助開發者全面洞察系統運行狀態,並在系統遇到異常的時候快速的定位和解決問題。什麼是全鏈路監控?
  • 分布式系統全鏈路監控介紹包括架構,原理等
    應用架構由集中式向分布式演進後,整個調用關係變得複雜。分布式架構由複雜且較大規模集群構成,各個應用之間相當獨立,可能由不同團隊、不同語言實現。系統一個完整的調用過程可能橫跨多個服務及數據中心。複雜的調用導致系統出問題後難以定位問題。