微服務(wù)線上問(wèn)題排查困難?不知道問(wèn)題出在哪一環(huán)?那是你還不會(huì)分布式鏈路追蹤

咱們以前單體應(yīng)用里面有很多的應(yīng)用和功能,依賴各個(gè)功能之間相互調(diào)用,使用公共的代碼包等等,排查問(wèn)題,使用類似于 gdb/dlv 工具或者直接查看代碼日志進(jìn)行定位和分析

但是現(xiàn)在我們基本上都是微服務(wù)架構(gòu)了,將以前的單體架構(gòu)拆成了一個(gè)個(gè)獨(dú)立的微服務(wù),現(xiàn)在就變成了多個(gè)微服務(wù)之間的相互調(diào)用的關(guān)系

在一個(gè)業(yè)務(wù)鏈條中,中間可能涉及到幾個(gè),十幾個(gè)甚至幾十個(gè)微服務(wù)的交互和配合,如果中間某一環(huán)出現(xiàn)了問(wèn)題,那么我們是很難排查的,排查問(wèn)題耗時(shí)耗力,且效率極其低下

服務(wù)數(shù)量多,鏈路復(fù)雜,排查困難,大佬們就想出了一個(gè)辦法,使用分布式鏈路追蹤來(lái)處理這個(gè)問(wèn)題

本文分別從以下幾個(gè)方面來(lái)聊聊關(guān)于分布式鏈路追蹤的技術(shù)知識(shí):

  • 什么是分布式鏈路追蹤
  • 分布式鏈路追蹤的基礎(chǔ)原理
  • 目前常用的分布式鏈路追蹤組件
  • Jaeger 的基本架構(gòu)和使用演示

?什么是分布式鏈路追蹤

分布式鏈路追蹤,見(jiàn)名知意,這是用在分布式系統(tǒng)中,用于追蹤服務(wù)調(diào)用鏈路的

文章開(kāi)頭有說(shuō)到,微服務(wù)架構(gòu)中,存在大量的微服務(wù),且維護(hù)的團(tuán)隊(duì)不盡相同,使用的語(yǔ)言也不太一致

線上部署幾百上千臺(tái)服務(wù)器,若鏈路出現(xiàn)了問(wèn)題,性能出現(xiàn)了瓶頸,我們?nèi)绾闻挪椋?如何有效的解決呢?

分布式鏈路追蹤他就可以將一次分布式請(qǐng)求還原成調(diào)用鏈路,將一次分布式請(qǐng)求的調(diào)用狀況集中展示,且他還提供友好的 UI 界面,咱們直接在頁(yè)面上就能直觀的看到每一個(gè)服務(wù)的耗時(shí)請(qǐng)求到具體哪臺(tái)服務(wù)器上以及服務(wù)相應(yīng)的狀態(tài)等等????。

在技術(shù)上通常使用

  • Tracing 表示鏈路追蹤

主要是用于單個(gè)請(qǐng)求的處理流程,包括服務(wù)調(diào)用和服務(wù)處理時(shí)長(zhǎng)等信息

目前分布式上使用的比較多的是 Jaeger

  • Logging 日志記錄

主要是用來(lái)記錄離散的日志事件。可以理解為你程序打印出來(lái)的一些日志

對(duì)于日志記錄,我們一般會(huì)使用 ELK ,這是 elastic 公司提供的一套解決方案,其中每一個(gè)字母代表一個(gè)開(kāi)源組件

E: Elasticsearch

L: Logstash

K:Kibana

  • Metrics 數(shù)據(jù)聚合

用于聚合數(shù)據(jù)的,通常是有時(shí)間順序的數(shù)據(jù)

對(duì)于數(shù)據(jù)聚合和統(tǒng)計(jì)系統(tǒng),我們一般使用 Prometheus 普羅米修斯來(lái)進(jìn)行處理

可以看到上述這三個(gè)概念是相輔相成的,僅僅只使用一種方式,是沒(méi)有辦法完全滿足我們需求的,在實(shí)際生產(chǎn)過(guò)程中,會(huì)將上述進(jìn)行兩兩組合來(lái)達(dá)到我們期望的效果。

??Tracing 與 Logging 組合

既有鏈路追蹤又有日志

那么我們就可以達(dá)到的效果是在我們每一個(gè)請(qǐng)求階段,可以看到詳細(xì)的標(biāo)簽數(shù)據(jù)對(duì)應(yīng)的日志數(shù)據(jù)以及錯(cuò)誤原因

??Tracing 與 Metrics 組合

既有鏈路追蹤,又有數(shù)據(jù)統(tǒng)計(jì)

那我們就可以去做單個(gè)請(qǐng)求中的可計(jì)量數(shù)據(jù),比如說(shuō),我們的接口調(diào)用次數(shù)以及調(diào)用時(shí)長(zhǎng)等等

??Logging 與 Metrics 組合

既有日志數(shù)據(jù)又有數(shù)據(jù)統(tǒng)計(jì)

咱們就可以去做數(shù)據(jù)聚合事件,去統(tǒng)計(jì)某一段時(shí)間某一類接口的請(qǐng)求總數(shù),報(bào)錯(cuò)次數(shù),成功率等等。

?分布式鏈路追蹤的基礎(chǔ)原理

那知道上述的一些應(yīng)用場(chǎng)景之后,是否會(huì)對(duì)分布式鏈路追蹤的技術(shù)原理有那么一點(diǎn)興趣了呢?那么我們開(kāi)始吧。

無(wú)論分布式鏈路追蹤組件有多少,他們都有三個(gè)核心的步驟。

  • 代碼 埋點(diǎn)
  • 數(shù)據(jù)存儲(chǔ)
  • 查詢展示

市面上那么多鏈路,追蹤主線那么自然,是要遵循一個(gè)統(tǒng)一的規(guī)范的這個(gè)規(guī)范,就是 OpenTracing

OpenTracing 可以理解為就是一個(gè)標(biāo)準(zhǔn)化的庫(kù),它位于應(yīng)用程序和鏈路追蹤程序之間,它解決了分布式追蹤 API 不兼容的問(wèn)題,我們可以理解為是這樣的。

[圖片上傳失敗...(image-312de8-1695819940905)]

無(wú)論哪一種鏈路追蹤組件一定會(huì)有如下這樣的做法

[圖片上傳失敗...(image-9e6f22-1695819940905)]

通過(guò)上圖就可以看到

  • 需要在應(yīng)用程序中做埋點(diǎn),數(shù)據(jù)上報(bào)到對(duì)應(yīng)的鏈路追蹤組件的收集器上,并對(duì)數(shù)據(jù)存儲(chǔ)
  • 另外一條路便是前端 UI 來(lái)查詢數(shù)據(jù)進(jìn)行展示

?鏈路追蹤如何實(shí)現(xiàn)?

架構(gòu)基本上也知道了,那么它具體的實(shí)現(xiàn)細(xì)節(jié)是什么樣的呢?

鏈路追蹤中一條鏈路也就可以理解為是一個(gè) Trace 樹(shù),一個(gè)樹(shù)上面有多個(gè) Span 基本單元

Span 基本單元有自己的唯一標(biāo)識(shí),通常是 UUID,還有其他的一些信息,例如時(shí)間戳,鍵值對(duì),ParentID 以及當(dāng)前的 SpanID 等等信息。

[圖片上傳失敗...(image-6afafb-1695819940905)]

可以看到整個(gè)鏈路,其實(shí)就是一個(gè)有向無(wú)環(huán)圖。

  • 我們可以看到一條調(diào)用鏈的第1 個(gè) Span ,它的 ParentID是空的,這一個(gè) Span 就被稱為RootSpan
  • 那其他的 Span 自身的 ParentID 就是上一個(gè) SpanID,自己的 SpanID 就是下一個(gè) Span 的 ParentID

?目前常用的分布式鏈路追蹤組件

目前常用的分布式鏈路追蹤組件有這些

  • Twitter Zpikin
  • Jaeger
  • SkyWalking
  • Pinpoint

其中 Twitter Zpikin 的架構(gòu)和實(shí)現(xiàn)相對(duì)簡(jiǎn)單,Jaeger 也是借鑒了google 的 Dapper 論文和 OpenZipkin 的啟發(fā)

接下來(lái)的兩個(gè)并沒(méi)有提供 golang 版本的庫(kù),因此就不過(guò)多贅述了,接下來(lái)主要著重介紹的是

  • Jaeger

?Jaeger 的基本架構(gòu)

Jaeger Uber 開(kāi)源的分布式鏈路追蹤系統(tǒng),它用于微服務(wù)的監(jiān)控和排查,并且支持分布式上下文傳播和分布式事務(wù)的監(jiān)控,報(bào)錯(cuò)分析,服務(wù)的調(diào)用網(wǎng)絡(luò)分析,和性能/延遲優(yōu)化

它的服務(wù)端的代碼就是 GO 語(yǔ)言實(shí)現(xiàn)的,自然也提供了 GO 語(yǔ)言版本的客戶端代碼庫(kù)

github.com/uber/jaeger-client-go

Jaeger 的基本架構(gòu)圖是這樣的

[圖片上傳失敗...(image-f88122-1695819940905)]

可以看到 Jaeger 的架構(gòu)圖與上述 OpenTracing 的規(guī)范大同小異,只不過(guò)自身服務(wù)端處理的有一些變動(dòng),整體方向上按照規(guī)范來(lái)的

Jaeger 是支持多個(gè)存儲(chǔ)后端,且原生支持 OpenTracing 規(guī)范,擁有可視化友好的UI界面,支持云原生部署,且還能兼容 Zipkin 格式的請(qǐng)求

官方文檔上也可以看到關(guān)于支持的存儲(chǔ)后端有這些:

[圖片上傳失敗...(image-195a77-1695819940905)]

?Jaeger 使用

  1. 現(xiàn)在自己的虛擬機(jī)上面裝一個(gè) Jaeger 的服務(wù)端,官方有提供一鍵 docker 運(yùn)行的版本,叫做 All-in-One ,這個(gè)僅僅是用來(lái)實(shí)驗(yàn),如果是要放在正式環(huán)境,請(qǐng)參考官方文檔進(jìn)行環(huán)境部署

https://www.jaegertracing.io/docs/1.12/deployment/

$ docker run -d --name jaeger \
  -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
  -p 5775:5775/udp \
  -p 6831:6831/udp \
  -p 6832:6832/udp \
  -p 5778:5778 \
  -p 16686:16686 \
  -p 14268:14268 \
  -p 9411:9411 \
  jaegertracing/all-in-one:1.12

安裝完畢之后,我們直接訪問(wèn) Jaeger 的前端即可,前端暴露的端口是 16686 , http://localhost:16686

[圖片上傳失敗...(image-c831b4-1695819940905)]

??Jaeger demo

咱們簡(jiǎn)單寫(xiě)一個(gè) Jaeger 的例子,僅僅是在一個(gè)應(yīng)用中,模擬 test1 -> testtest1-1 的一個(gè)鏈路

package main

import (
   "log"
   "time"
   jaegerCfg "github.com/uber/jaeger-client-go/config"
   "github.com/opentracing/opentracing-go"
   "github.com/uber/jaeger-client-go"
   "context"
)

func main() {
   // 初始 log 日志
   log.SetFlags(log.LstdFlags | log.Lshortfile)
   // 配置 Jaeger
   cfg := jaegerCfg.Configuration{
      Sampler: &jaegerCfg.SamplerConfig{
         Type:  jaeger.SamplerTypeConst,
         Param: 1,
      },
      Reporter: &jaegerCfg.ReporterConfig{
         LogSpans:           true,
         LocalAgentHostPort: "127.0.0.1:6831",
      },
   }
   // 創(chuàng)建一個(gè)全局的 Jaeger tracer
   closer, err := cfg.InitGlobalTracer(
      "testSvr",
   )
   if err != nil {
      log.Printf("InitGlobalTracer error: %s", err.Error())
      return
   }

   var ctx = context.TODO()
   span1, ctx := opentracing.StartSpanFromContext(ctx, "test1")
   // 模擬業(yè)務(wù)處理
   time.Sleep(time.Second)

   span11, _ := opentracing.StartSpanFromContext(ctx, "test1-1")
   // 模擬業(yè)務(wù)處理
   time.Sleep(time.Second)
   span11.Finish()

   span1.Finish()

   defer closer.Close()
}

通過(guò)程序代碼,我們可以知道

  • 是給 Jaeger 的 6831 這個(gè)端口發(fā)送 ******Udp**
  • Jaeger 是 opentracing.StartSpanFromContext ,在上下文上傳入我們當(dāng)前的 operationName 來(lái)進(jìn)行處理的,效果可以見(jiàn)后續(xù)的圖

運(yùn)行代碼的時(shí)候,如果你的環(huán)境里面不是 golang 1.18 的話,則會(huì)出現(xiàn)這樣的報(bào)錯(cuò),此時(shí)需要先卸載當(dāng)前環(huán)境中的 golang,再去安裝新版本的 golang

[圖片上傳失敗...(image-24d99b-1695819940905)]

如果不是先卸載,再安裝,那么會(huì)出現(xiàn)一些庫(kù)報(bào)錯(cuò)的問(wèn)題,例如這樣

[圖片上傳失敗...(image-ca5b7a-1695819940905)]

環(huán)境 ok 之后,我們直接訪問(wèn)環(huán)境地址+上16686端口就可以看到如下頁(yè)面

  • 選擇 Service 為 testSvr
  • 查看具體的 Trace

[圖片上傳失敗...(image-4bba1f-1695819940905)]

[圖片上傳失敗...(image-212635-1695819940905)]

此處可以看到調(diào)用關(guān)系為 test1 調(diào)用了 test1-1,其中 test1 自身處理事項(xiàng)耗時(shí) 1s,等待 test1-1 處理事項(xiàng) 1s,因此整個(gè)鏈路耗時(shí) 2s

對(duì)于鏈路追蹤,咱們需要知道的是原理,知道原理之后,編碼都是很簡(jiǎn)單的事情,上述僅是一個(gè)簡(jiǎn)單的 demo,主要是展示如何去使用這個(gè)鏈路追蹤組件

實(shí)際業(yè)務(wù)中,我們會(huì)對(duì)微服務(wù)之間的交互進(jìn)行鏈路追蹤,并且會(huì)從前端請(qǐng)求進(jìn)來(lái)就會(huì)開(kāi)始記錄

這個(gè)時(shí)候,我們涉及到 http 中的代碼 埋點(diǎn),grpc 中的代碼埋點(diǎn),自然 Jaeger 都是有相應(yīng)的中間件和攔截器來(lái)進(jìn)行使用的,實(shí)際上都是對(duì) ctx 上下文上面做文章,這里就不過(guò)多贅述了,將 Jaeger 的代碼下載到本地,稍微閱讀一下就可以知道了

使用鏈路追蹤,我們就可以很清晰的看到一條完整的調(diào)用鏈,每一個(gè)環(huán)節(jié)耗時(shí)多少,整體來(lái)看性能的瓶頸在哪里就可以做到一清二楚,先用起來(lái)了吧,看看源碼

會(huì)使用到這些庫(kù)

"github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"
"github.com/uber/jaeger-client-go/config"
"github.com/opentracing/opentracing-go"
"github.com/uber/jaeger-client-go"

感謝閱讀,歡迎交流,點(diǎn)個(gè)贊,關(guān)注一波 再走吧

歡迎點(diǎn)贊,關(guān)注,收藏

朋友們,你的支持和鼓勵(lì),是我堅(jiān)持分享,提高質(zhì)量的動(dòng)力

[圖片上傳失敗...(image-b2de93-1695819940905)]

好了,本次就到這里

技術(shù)是開(kāi)放的,我們的心態(tài),更應(yīng)是開(kāi)放的。擁抱變化,向陽(yáng)而生,努力向前行。

我是阿兵云原生,歡迎點(diǎn)贊關(guān)注收藏,下次見(jiàn)~

文中提到的技術(shù)點(diǎn),感興趣的可以查看這些文章:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容