服務鏈路追蹤

微服務之間通過網絡進行通信。在我們提供服務的同時,我們不能保證網絡一定是暢通的,相反,網絡是很脆弱的,網絡資源也有限。因此,我們有必要追蹤每個網絡請求,瞭解其經過了哪些微服務,延遲多少,每個請求所耗費的時間等。只有這樣,我們才能更好的分析系統拼勁,解決系統問題。

下面主要探討服務追蹤組件 Zipkin, SpringCloudSleuth 集成了 Zipkin。

Spring Cloud Sleuth簡介

通過 SpringCloud 來構建微服務架構,我們可以通過 SpringCloudSleuth 實現分佈式追蹤,它集成了 Zipkin。

Sleuth 術語

  • span(跨度):基本工作單元。例如,在一個新建的 span 中發送一個 RPC 等同於發送一個迴應請求給 RPC,span 通過一個 64 位 ID 唯一標識,trace 以另一個 64 位 ID 表示,span 還有其他數據信息,比如摘要、時間戳事件、關鍵值註釋(tags)、span 的 ID,以及進度 ID(通常是 IP 地址)。span 在不斷的啓動和停止,同時記錄了時間信息,當你創建了一個 span,你必須在未來的某個時刻停止它。

  • trace(追蹤):一組共享“root span”的 span 組成的樹狀結構成爲 trace。trace 也用一個 64 位的 ID 唯一標識,trace 中的所有 span 都共享該 trace 的 ID。

  • annotation(標註):用來及時記錄一個事件的存在,一些核心 annotations 用來定義一個請求的開始和結束。
    1)、CS,即 Client Sent,客戶端發起一個請求,這個 annotion 描述了這個 span 的開始。
    2)、SR,即 Server Received,服務端獲得請求並準備開始處理它,如果將其 SR 減去 CS 時間戳便可得到網絡延遲。
    3)、SS,即 Server Sent,註解表明請求處理的完成(當請求返回客戶端),如果 SS 減去 SR 時間戳便可得到服務端需要的處理請求時間。
    4)、CR,即 Client Received,表明 span 的結束,客戶端成功接收到服務端的回覆,如果 CR 減去 CS 時間戳便可得到客戶端從服務端獲取回覆的所有所需時間。

     下圖演示了請求依次經過 SERVICE1 -> SERVICE2 -> SERVICE3 -> SERVICE4 時,span、trace、annotation 的變化:
    

在這裏插入圖片描述

zipkin 簡介

Zipkin 是 Twitter 開源的分佈式跟蹤系統,基於 Dapper 的論文設計而來。它的主要功能是收集系統的時序數據,從而追蹤微服務架構的系統延時等問題。Zipkin 還提供了一個非常友好的界面,便於我們分析追蹤數據。

zipkin服務端安裝

要實現完整的服務器鏈路,需要分爲服務端和客戶端,下面我們來分別介紹服務端和客戶端的實現: 在 Spring Boot 2.0 以前,我們需要自己實現 Zipkin 服務端,從 Spring Boot 2.0 以後,其推出了官方 Zipkin 服務端,我們只需要下載服務端 jar 包,放到服務器上,啓動即可。具體操作如下:
(1)從網絡上下載 Zipkin 服務端的可執行 jar 包,下載地址可點擊這裏獲取

(2)將 zipkin-server-2.9.4-exec.jar 修改爲 zipkin.jar;

(3)命令行終端進入 zipkin.jar 所在目錄,執行命令:java -jar zipkin.jar。

啓動成功後,如圖所示:
在這裏插入圖片描述
Zipkin 服務端的默認啓動端口爲 9411,瀏覽器訪問 http://localhost:9411 即可進入 Zipkin 服務端管理界面,如圖:

enter image description here

單純集成 zipkinServer 還達不到追蹤的目的,還必須使我們的微服務客戶端集成 Zipkin 才能跟蹤微服務,下面是集成步驟。
(1)在 EurekaClient 服務工程的 pom 文件中添加以下依賴:

  <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-sleuth</artifactId>
    </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth-zipkin</artifactId>
        </dependency>

也可以只導入如下依賴,下面依賴包含了上面兩個依賴:

    <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>

(2) 配置文件進行配置

spring:
    zipkin:
        base-url: http://localhost:9411
        sender:
            type: web
    sleuth:
        sampler:
          probability : 1     # 1表示百分百進行採樣

其中,spring.zipkin.base-url 用來指定 zipkinServer 的地址。
spring.sleutch.sampler.probability 用來指定採樣請求的百分比(默認爲 0.1,即 10%)。
(3)依次啓動註冊中心、配置中心、Zipkin、eurekaclient,依次訪問 http://localhost:8763/index,http://localhost:9411,進入 Zipkin 界面後,點擊 Find a trace 按鈕,可以看到 trace 列表:
在這裏插入圖片描述
(4) 自定義日誌級別:

# 修改日誌級別
logging:
  level:
    org.springframework.cloud.openfeign: debug

通過消息中間件實現鏈路追蹤

在之前的實例中,我們使用 HTTP 來收集數據,如果 zipkinServer 的網絡地址發生了變化,每個微服務的 base-url 都需要改變,因此,我們還可以通過消息隊列來收集追蹤數據。

我以 RabbitMQ 作爲消息中間件進行演示。

(1)命令行啓動官網提供的 zipkin.jar,注意,啓動時需要指定 RabbitMQ 的 host 地址,如: java -jar zipkin.jar --RABBIT_ADDRESSES=127.0.0.1

其中,–RABBIT_ADDRESSES 即爲 RabbitMQ 的 host 地址。

(2)啓動完成後,我們訪問 RabbitMQ 的 Web 管理界面,可以看到 Zipkin Server 已經爲我們創建了一個名叫 zipkin 的隊列,如圖:
在這裏插入圖片描述
(3)改造 EurekaClient服務 ,將 pom.xml 添加如下內容:

 <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>

(4)配置文件中 去掉 spring.zipkin.base-url 配置。
(5)依次啓動相應工程,我們發現依然可以正常跟蹤微服務。

存儲追蹤數據

前面的示例中,ZipkinServer 是默認將數據存儲在內存中,一旦 ZipkinServer 重啓或發生故障,將會導致歷史數據丟失,因此我們需要將跟蹤數據保存到硬盤中。

ZipkinServer 支持多種後端數據存儲,比如 MySQL、ElasticSearch、Cassandra 等。

以 MySQL 爲例來演示如何將歷史數據存儲在 MySQL 中。

(1)首先創建一個名爲 zipkin_db 的數據庫,並執行以下腳本:

CREATE TABLE IF NOT EXISTS zipkin_spans (
  `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
  `trace_id` BIGINT NOT NULL,
  `id` BIGINT NOT NULL,
  `name` VARCHAR(255) NOT NULL,
  `parent_id` BIGINT,
  `debug` BIT(1),
  `start_ts` BIGINT COMMENT 'Span.timestamp(): epoch micros used for endTs query and to implement TTL',
  `duration` BIGINT COMMENT 'Span.duration(): micros used for minDuration and maxDuration query'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;

ALTER TABLE zipkin_spans ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `id`) COMMENT 'ignore insert on duplicate';
ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`, `id`) COMMENT 'for joining with zipkin_annotations';
ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTracesByIds';
ALTER TABLE zipkin_spans ADD INDEX(`name`) COMMENT 'for getTraces and getSpanNames';
ALTER TABLE zipkin_spans ADD INDEX(`start_ts`) COMMENT 'for getTraces ordering and range';

CREATE TABLE IF NOT EXISTS zipkin_annotations (
  `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
  `trace_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.trace_id',
  `span_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.id',
  `a_key` VARCHAR(255) NOT NULL COMMENT 'BinaryAnnotation.key or Annotation.value if type == -1',
  `a_value` BLOB COMMENT 'BinaryAnnotation.value(), which must be smaller than 64KB',
  `a_type` INT NOT NULL COMMENT 'BinaryAnnotation.type() or -1 if Annotation',
  `a_timestamp` BIGINT COMMENT 'Used to implement TTL; Annotation.timestamp or zipkin_spans.timestamp',
  `endpoint_ipv4` INT COMMENT 'Null when Binary/Annotation.endpoint is null',
  `endpoint_ipv6` BINARY(16) COMMENT 'Null when Binary/Annotation.endpoint is null, or no IPv6 address',
  `endpoint_port` SMALLINT COMMENT 'Null when Binary/Annotation.endpoint is null',
  `endpoint_service_name` VARCHAR(255) COMMENT 'Null when Binary/Annotation.endpoint is null'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;

ALTER TABLE zipkin_annotations ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `span_id`, `a_key`, `a_timestamp`) COMMENT 'Ignore insert on duplicate';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`, `span_id`) COMMENT 'for joining with zipkin_spans';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTraces/ByIds';
ALTER TABLE zipkin_annotations ADD INDEX(`endpoint_service_name`) COMMENT 'for getTraces and getServiceNames';
ALTER TABLE zipkin_annotations ADD INDEX(`a_type`) COMMENT 'for getTraces';
ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces';

CREATE TABLE IF NOT EXISTS zipkin_dependencies (
  `day` DATE NOT NULL,
  `parent` VARCHAR(255) NOT NULL,
  `child` VARCHAR(255) NOT NULL,
  `call_count` BIGINT
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;

ALTER TABLE zipkin_dependencies ADD UNIQUE KEY(`day`, `parent`, `child`);

(2)重新啓動 zipkin.jar,這次啓動需要指定數據庫連接信息,如:

java -jar zipkin.jar --RABBIT_ADDRESSES=127.0.0.1 --MYSQL_HOST=127.0.0.1 --MYSQL_TCP_PORT=3306 --MYSQL_USER=root --MYSQL_PASS=1qaz2wsx --MYSQL_DB=zipkin_db --STORAGE_TYPE=mysql

注:如果啓動失敗,可能的原因有:

  • 數據庫無法連接
  • MySQL 版本過高(大於等於 8.0),請降低版本,如果是 MariaDB,則最好安裝其官網最新版本。
  • 重啓工程,可以看到數據庫已經存儲了追蹤數據,如圖:

在這裏插入圖片描述
且重啓 Zipkin Server 後,也能通過 http://localhost:9411 查詢到追蹤數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章