面試題:微服務鏈路追蹤


微服務系統的監控主要包含以下三個方面:

Logging 就是記錄系統行為的離散事件,例如,服務在處理某個請求時打印的錯誤日志,我們可以將這些日志信息記錄到 ElasticSearch 或是其他存儲中,然后通過 Kibana 或是其他工具來分析這些日志了解服務的行為和狀態。大多數情況下,日志記錄的數據很分散,並且相互獨立,比如錯誤日志、請求處理過程中關鍵步驟的日志等等。

Metrics 是系統在一段時間內某一方面的某個度量,例如,電商系統在一分鍾內的請求次數。我們常見的監控系統中記錄的數據都屬於這個范疇,例如 Promethus、Open-Falcon 等,這些監控系統最終給運維人員展示的是一張張二維的折線圖。Metrics 是可以聚合的,例如,為電商系統中每個 HTTP 接口添加一個計數器,計算每個接口的 QPS,之后我們就可以通過簡單的加和計算得到系統的總負載情況。

Tracing 即我們常說的分布式鏈路追蹤。在微服務架構系統中一個請求會經過很多服務處理,調用鏈路會非常長,要確定中間哪個服務出現異常是非常麻煩的一件事。通過分布式鏈路追蹤,運維人員就可以構建一個請求的視圖,這個視圖上展示了一個請求從進入系統開始到返回響應的整個流程。這樣,就可以從中了解到所有服務的異常情況、網絡調用,以及系統的性能瓶頸等

常見 APM 系統

APM 系統(Application Performance Management,即應用性能管理)是對企業的應用系統進行實時監控,實現對應用性能管理和故障定位的系統化解決方案。APM 作為系統運維管理和網絡管理的一個重要方向,能夠對關鍵服務進行監控、追蹤以及告警,幫助開發和運維人員輕松地在復雜的應用系統中找到故障點,提高服務的穩定性,保證用戶得到良好的服務,降低 IT 運維的成本。 當下成熟的互聯網公司都已經建立了全方位監控系統,力求及時發現故障,並為優化系統提供性能數據支持。

國內比較常見的 APM 如下:

  • CAT: 由國內美團點評開源的,基於 Java 語言開發,目前提供 Java、C/C++、Node.js、Python、Go 等語言的客戶端,監控數據會全量統計。國內很多公司在用,例如美團點評、攜程、拼多多等。CAT 需要開發人員手動在應用程序中埋點,對代碼侵入性比較強。

  • Zipkin: 由 Twitter 公司開發並開源,Java 語言實現。侵入性相對於 CAT 要低一點,需要對web.xml 等相關配置文件進行修改,但依然對系統有一定的侵入性。Zipkin 可以輕松與 Spring Cloud 進行集成,也是 Spring Cloud 推薦的 APM 系統。

  • Pinpoint: 韓國團隊開源的 APM 產品,運用了字節碼增強技術,只需要在啟動時添加啟動參數即可實現 APM 功能,對代碼無侵入。目前支持 Java 和 PHP 語言,底層采用 HBase 來存儲數據,探針收集的數據粒度非常細,但性能損耗較大,因其出現的時間較長,完成度也很高,文檔也較為豐富,應用的公司較多。

  • SkyWalking: 國人開源的產品,2019 年 4 月 17 日 SkyWalking 從 Apache 基金會的孵化器畢業成為頂級項目。目前 SkyWalking 支持 Java、.Net、Node.js 等探針,數據存儲支持MySQL、ElasticSearch等。 SkyWalking 與 Pinpoint 相同,Java 探針采用字節碼增強技術實現,對業務代碼無侵入。探針采集數據粒度相較於 Pinpoint 來說略粗,但性能表現優秀。目前,SkyWalking 增長勢頭強勁,社區活躍,中文文檔齊全,沒有語言障礙,支持多語言探針,這些都是 SkyWalking 的優勢所在,還有就是 SkyWalking 支持很多框架,包括很多國產框架,例如,Dubbo、gRPC、SOFARPC 等等,也有很多開發者正在不斷向社區提供更多插件以支持更多組件無縫接入 SkyWalking。

SkyWalking

SkyWalking 整體架構與核心概念

SkyWalking 是一個基於 OpenTracing 規范的、開源的 APM 系統,它是專門為微服務架構以及雲原生架構而設計的。從 SkyWalking 6.0 開始,SkyWalking 將自身定義為一個觀測性分析平台(Observability Analysis Platform,OAP)。SkyWalking 的核心功能有

  • 服務、服務實例、端點指標分析。

  • 服務拓撲圖分析

  • 服務、服務實例和端點(Endpoint)SLA 分析

  • 慢查詢檢測

  • 告警

SkyWalking 如下特點:

  • 多語言自動探針,支持 Java、.NET Code 等多種語言。

  • 為多種開源項目提供了插件,為 Tomcat、 HttpClient、Spring、RabbitMQ、MySQL 等常見基礎設施和組件提供了自動探針。

  • 微內核 + 插件的架構,存儲、集群管理、使用插件集合都可以進行自由選擇。

  • 支持告警。

  • 優秀的可視化效果。

SkyWalking 分為三個核心部分:

  • Agent(探針):Agent 運行在各個服務實例中,負責采集服務實例的 Trace 、Metrics 等數據,然后通過 gRPC 方式上報給 SkyWalking 后端。

  • OAP:SkyWalking 的后端服務,其主要責任有兩個。

    • 一個是負責接收 Agent 上報上來的 Trace、Metrics 等數據,交給 Analysis Core (涉及 SkyWalking OAP 中的多個模塊)進行流式分析,最終將分析得到的結果寫入持久化存儲中。SkyWalking 可以使用 ElasticSearch、H2、MySQL 等作為其持久化存儲,一般線上使用 ElasticSearch 集群作為其后端存儲。
    • 另一個是負責響應 SkyWalking UI 界面發送來的查詢請求,將前面持久化的數據查詢出來,組成正確的響應結果返回給 UI 界面進行展示。
  • UI 界面:SkyWalking 前后端進行分離,該 UI 界面負責將用戶的查詢操作封裝為 GraphQL 請求提交給 OAP 后端觸發后續的查詢操作,待拿到查詢結果之后會在前端負責展示。

這里通過電商系統中的一個接口(請求 path 為"/query/userInfo"),來介紹一下 SkyWalking 中的三個核心概念,如下圖所示:

image-20210125204221337

  • Service(服務):用戶服務是一個提供獨立功能的模塊,單獨部署成一個集群並對外提供服務,這就是 SkyWalking 中的 Service(服務),這與微服務架構中的一個服務幾乎是一樣的。

  • ServiceInstance(服務實例):用戶服務的集群是由多個部署了同一套代碼的 JVM 節點構成的,對外提供了相同的處理能力,當請求進入系統時,由接入層進行負載均衡選擇一個節點處理請求。用戶服務中一個 JVM 節點即為一個 ServiceInstance(服務實例)。

  • Endpoint(端點):服務對外暴露的接口,例如這里的 "/query/userInfo" 接口,或是其他的 RPC 接口,就是 SkyWalking 中的 Endpoint(端點)。

Java Agent

Java Agent 是從 JDK1.5 開始引入的,算是一個比較老的技術了。作為 Java 的開發工程師,我們常用的命令之一就是 java 命令,而 Java Agent 本身就是 java 命令的一個參數(即 -javaagent)。-javaagent 參數之后需要指定一個 jar 包,這個 jar 包需要同時滿足下面兩個條件:

  • 在 META-INF 目錄下的 MANIFEST.MF 文件中必須指定 premain-class 配置項。
  • premain-class 配置項指定的類必須提供了 premain() 方法。

在 Java 虛擬機啟動時,執行 main() 函數之前,虛擬機會先找到 -javaagent 命令指定 jar 包,然后執行 premain-class 中的 premain() 方法。用一句概括其功能的話就是:main() 函數之前的一個攔截器。

使用 Java Agent 的步驟大致如下:

  1. 定義一個 MANIFEST.MF 文件,在其中添加 premain-class 配置項。

  2. 創建 premain-class 配置項指定的類,並在其中實現 premain() 方法,方法簽名如下:

public static void premain(String agentArgs, Instrumentation inst){
   ... 
}
  1. 將 MANIFEST.MF 文件和 premain-class 指定的類一起打包成一個 jar 包。

  2. 使用 -javaagent 指定該 jar 包的路徑即可執行其中的 premain() 方法。

Attach API
在 Java 5 中,Java 開發者只能通過 Java Agent 中的 premain() 方法在 main() 方法執行之前進行一些操作,這種方式在一定程度上限制了靈活性。Java 6 針對這種狀況做出了改進,提供了一個 agentmain() 方法,Java 開發者可以在 main() 方法執行以后執行 agentmain() 方法實現一些特殊功能。

agentmain() 方法同樣有兩個重載,它們的參數與 premain() 方法相同,而且前者優先級也是高於后者的。

public static void agentmain (String agentArgs, 
      Instrumentation inst);[1]

public static void agentmain (String agentArgs); [2]

gentmain() 方法主要用在 JVM Attach 工具中,Attach API 是 Java 的擴展 API,可以向目標 JVM “附着”(Attach)一個代理工具程序,而這個代理工具程序的入口就是 agentmain() 方法。

Attach API 中有 2 個核心類需要特別說明

  • VirtualMachine 是對一個 Java 虛擬機的抽象,在 Attach 工具程序監控目標虛擬機的時候會用到該類。VirtualMachine 提供了 JVM 枚舉、Attach、Detach 等基本操作。
  • VirtualMachineDescriptor 是一個描述虛擬機的容器類,后面示例中會介紹它如何與 VirtualMachine 配合使用。

Byte Buddy

在 Java 的世界中,代碼生成庫不止 Byte Buddy 一個,以下代碼生成庫在 Java 中也很流行:

Java Proxy
Java Proxy 是 JDK 自帶的一個代理工具,它允許為實現了一系列接口的類生成代理類。Java Proxy 要求目標類必須實現接口是一個非常大限制,例如,在某些場景中,目標類沒有實現任何接口且無法修改目標類的代碼實現,Java Proxy 就無法對其進行擴展和增強了。

CGLIB
CGLIB 誕生於 Java 初期,但不幸的是沒有跟上 Java 平台的發展。雖然 CGLIB 本身是一個相當強大的庫,但也變得越來越復雜。鑒於此,導致許多用戶放棄了CGLIB 。

Javassist
Javassist 的使用對 Java 開發者來說是非常友好的,它使用Java 源代碼字符串和 Javassist 提供的一些簡單 API ,共同拼湊出用戶想要的 Java 類,Javassist 自帶一個編譯器,拼湊好的 Java 類在程序運行時會被編譯成為字節碼並加載到 JVM 中。Javassist 庫簡單易用,而且使用 Java 語法構建類與平時寫 Java 代碼類似,但是 Javassist 編譯器在性能上比不了 Javac 編譯器,而且在動態組合字符串以實現比較復雜的邏輯時容易出錯。

Byte Buddy
Byte Buddy 提供了一種非常靈活且強大的領域特定語言,通過編寫簡單的 Java 代碼即可創建自定義的運行時類。與此同時,Byte Buddy 還具有非常開放的定制性,能夠應付不同復雜度的需求。

Byte Buddy 動態增強代碼總共有三種方式:

  • subclass:對應 ByteBuddy.subclass() 方法。這種方式比較好理解,就是為目標類(即被增強的類)生成一個子類,在子類方法中插入動態代碼。
  • rebasing:對應 ByteBuddy.rebasing() 方法。當使用 rebasing 方式增強一個類時,Byte Buddy 保存目標類中所有方法的實現,也就是說,當 Byte Buddy 遇到沖突的字段或方法時,會將原來的字段或方法實現復制到具有兼容簽名的重新命名的私有方法中,而不會拋棄這些字段和方法實現。從而達到不丟失實現的目的。
  • redefinition:對應 ByteBuddy.redefine() 方法。當重定義一個類時,Byte Buddy 可以對一個已有的類添加屬性和方法,或者刪除已經存在的方法實現。如果使用其他的方法實現替換已經存在的方法實現,則原來存在的方法實現就會消失。

OpenTracing

Trace 簡介

一個 Trace 代表一個事務、請求或是流程在分布式系統中的執行過程。OpenTracing 中的一條 Trace 被認為是一個由多個 Span 組成的有向無環圖( DAG 圖),一個 Span 代表系統中具有開始時間和執行時長的邏輯單元,Span 一般會有一個名稱,一條 Trace 中 Span 是首尾連接的。

Span 簡介

Span 代表系統中具有開始時間和執行時長的邏輯單元,Span 之間通過嵌套或者順序排列建立邏輯因果關系。

每個 Span 中可以包含以下的信息:

  • 操作名稱:例如訪問的具體 RPC 服務,訪問的 URL 地址等;

  • 起始時間;

  • 結束時間;

  • Span Tag:一組鍵值對構成的 Span 標簽集合,其中鍵必須為字符串類型,值可以是字符串、bool 值或者數字;

  • Span Log:一組 Span 的日志集合;

  • SpanContext:Trace 的全局上下文信息;

  • References:Span 之間的引用關系,下面詳細說明 Span 之間的引用關系;

在一個 Trace 中,一個 Span 可以和一個或者多個 Span 間存在因果關系。目前,OpenTracing 定義了 ChildOf 和 FollowsFrom 兩種 Span 之間的引用關系。這兩種引用類型代表了子節點和父節點間的直接因果關系。

  • ChildOf 關系:一個 Span 可能是一個父級 Span 的孩子,即為 ChildOf 關系。下面這些情況會構成 ChildOf 關系:
    • 一個 HTTP 請求之中,被調用的服務端產生的 Span,與發起調用的客戶端產生的 Span,就構成了 ChildOf 關系;
    • 一個 SQL Insert 操作的 Span,和 ORM 的 save 方法的 Span 構成 ChildOf 關系。
  • FollowsFrom 關系:在分布式系統中,一些上游系統(父節點)不以任何方式依賴下游系統(子節點)的執行結果,例如,上游系統通過消息隊列向下游系統發送消息。這種情況下,下游系統對應的子 Span 和上游系統對應的父級 Span 之間是 FollowsFrom 關系

很明顯,上述 ChildOf 關系中的父級 Span 都要等待子 Span 的返回,子 Span 的執行時間影響了其所在父級 Span 的執行時間,父級 Span 依賴子 Span 的執行結果。除了串行的任務之外,我們的邏輯中還有很多並行的任務,它們對應的 Span 也是並行的,這種情況下一個父級 Span 可以合並所有子 Span 的執行結果並等待所有並行子 Span 結束。

Logs 簡介

每個 Span 可以進行多次 Logs 操作,每一次 Logs 操作,都需要帶一個時間戳,以及一個可選的附加信息。在前文搭建的環境中,請求 http://localhost:8000/err 得到的 Trace 中就會通過 Logs 記錄異常堆棧信息,如下圖所示,其中不僅包括異常的堆棧信息,還包括了一些說明性的鍵值對信息:

Tags 簡介

每個 Span 可以有多個鍵值對形式的 Tags,Tags 是沒有時間戳的,只是為 Span 添加一些簡單解釋和補充信息。

SpanContext 和 Baggage

SpanContext 表示進程邊界,在跨進調用時需要將一些全局信息,例如,TraceId、當前 SpanId 等信息封裝到 Baggage 中傳遞到另一個進程(下游系統)中。

Baggage 是存儲在 SpanContext 中的一個鍵值對集合。它會在一條 Trace 中全局傳輸,該 Trace 中的所有 Span 都可以獲取到其中的信息。

需要注意的是,由於 Baggage 需要跨進程全局傳輸,就會涉及相關數據的序列化和反序列化操作,如果在 Baggage 中存放過多的數據,就會導致序列化和反序列化操作耗時變長,使整個系統的 RPC 的延遲增加、吞吐量下降。

雖然 Baggage 與 Span Tags 一樣,都是鍵值對集合,但兩者最大區別在於 Span Tags 中的信息不會跨進程傳輸,而 Baggage 需要全局傳輸。因此,OpenTracing 要求實現提供 Inject 和 Extract 兩種操作,SpanContext 可以通過 Inject 操作向 Baggage 中添加鍵值對數據,通過 Extract 從 Baggage 中獲取鍵值對數據。

JDK SPI 機制

什么是微內核架構?
微內核是一種典型的架構模式 ,區別於普通的設計模式,架構模式是一種高層模式,用於描述系統級的結構組成、相互關系及相關約束。微內核架構在開源框架中的應用也比較廣泛,除了 ShardingSphere 之外,在主流的 PRC 框架 Dubbo 中也實現了自己的微內核架構。那么,在介紹什么是微內核架構之前,我們有必要先闡述這些開源框架會使用微內核架構的原因。

為什么要使用微內核架構?
從組成結構上講, 微內核架構包含兩部分組件:內核系統和插件 。這里的內核系統通常提供系統運行所需的最小功能集,而插件是獨立的組件,包含自定義的各種業務代碼,用來向內核系統增強或擴展額外的業務能力。一個概念就是經常在說的 API ,這是系統對外暴露的接口。而另一個概念就是 SPI(Service Provider Interface,服務提供接口),這是插件自身所具備的擴展點。就兩者的關系而言,API 面向業務開發人員,而 SPI 面向框架開發人員。微內核架構本質上是為了提高系統的擴展性 。所謂擴展性,是指系統在經歷不可避免的變更時所具有的靈活性,以及針對提供這樣的靈活性所需要付出的成本間的平衡能力。也就是說,當在往系統中添加新業務時,不需要改變原有的各個組件,只需把新業務封閉在一個新的組件中就能完成整體業務的升級,我們認為這樣的系統具有較好的可擴展性。

SPI(Service Provider Interface)主要是被框架開發人員使用的一種技術。例如,使用 Java 語言訪問數據庫時我們會使用到 java.sql.Driver 接口,每個數據庫廠商使用的協議不同,提供的 java.sql.Driver 實現也不同,在開發 java.sql.Driver 接口時,開發人員並不清楚用戶最終會使用哪個數據庫,在這種情況下就可以使用 Java SPI 機制為 java.sql.Driver 接口尋找具體的實現。

當服務的提供者提供了一種接口的實現之后,需要在 Classpath 下的 META-INF/services/ 目錄里創建一個以服務接口命名的文件,此文件記錄了該 jar 包提供的服務接口的具體實現類。當某個應用引入了該 jar 包且需要使用該服務時,JDK SPI 機制就可以通過查找這個 jar 包的 META-INF/services/ 中的配置文件來獲得具體的實現類名,進行實現類的加載和實例化,最終使用該實現類完成業務功能。

Sleuth+Zipkin

Spring Cloud Sleuth

SpanId
SpanId 一般被稱為跨度 Id。在上圖中,針對服務 A 的訪問請求,通過 SpanId 來標識該請求的到達並返回的具體過程。顯然,對於這個 Span 而言,勢必需要明確 Span 的開始時間和結束時間,這兩個時間之間的差值就是服務 A 對這個請求的處理時間。

TraceId
除了 SpanId 外,我們還需要 TraceId,也就是跟蹤 Id。同樣是在上圖中,要想監控整個鏈路,我們不光需要關注服務 A 中的 Span,而是需要把請求通過所有服務的 Span 都串聯起來。這時候就需要為這個請求生成一個全局的唯一性 Id,通過這個 Id 可以串聯起如上圖所示,從服務 A 到服務 F 的整個調用,這個唯一性 Id 就是 TraceId。

Spring Cloud Sleuth 是 Spring Cloud 的組成部分之一,對於分布式環境下的服務調用鏈路,我們可以通過該框架來完成服務監控和跟蹤方面的各種需求。

當我們將 Spring Cloud Sleuth 添加到系統的類路徑,該框架便會自動建立日志收集渠道,不僅包括常見的使用 RestTemplate 發出的請求,同時也能無縫支持通過 API 網關 Zuul 以及 Spring Cloud Stream 所發送的請求。

針對監控數據的管理,Spring Cloud Sleuth 可以設置常見的日志格式來輸出 TraceId 和 SpanId。我們也可以利用諸如 Logstash 等日志發布組件將日志發布到 ELK 等日志分析工具中進行處理。同時,Spring Cloud Sleuth 也兼容了 Zipkin、HTrace 等第三方工具的應用和集成。

Zipkin

Zipkin 是一個開源的分布式跟蹤系統,每個服務向 Zipkin 報告運行時數據,Zipkin 會根據調用關系通過 Zipkin UI 對整個調用鏈路中的數據實現可視化。

日志的收集組件 Collector,接收來自外部傳輸(Transport)的數據,將這些數據轉換為 Zikpin 內部處理的 Span 格式,相當於兼顧數據收集和格式化的功能。這些收集的數據通過存儲組件 Storage 進行存儲,當前支持 Cassandra、Redis、HBase、MySQL、PostgreSQL、SQLite 等工具,默認存儲在內存中。然后,所存儲數據可以通過 RESTful API 對外暴露查詢接口。更為有用的是,Zipkin 還提供了一套簡單的 Web 界面,基於 API 組件的上層應用,可以方便而直觀的查詢和分析跟蹤信息。

Zipkin 有四個組件:Collector,Storage,Search,Web UI。

Collector:是 Zipkin 的數據收集器,鏈路跟蹤的數據到達 Zipkin 收集器,收集器會進行數據驗證、存儲。

Storage:是存儲組件,Zipkin 默認是在內存中存儲數據,內存存儲是為了方便用戶體驗,真實使用中必須要將數據落地。支持 Elasticsearch 和 MySQL 等存儲方式。

Search:是 Zipkin 的查詢 API,當鏈路跟蹤的數據被存儲后,我們需要查詢這些數據。Search 組件提供了簡單的 JSON API,用於查找和檢索數據。這個 API 的主要使用者是 Web UI。

Web UI:提供了可視化的操作界面,讓我們能夠方便,直觀地查詢鏈路跟蹤的數據。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM