⑦SpringCloud 實戰:引入Sleuth組件,完善服務鏈路跟蹤

這是SpringCloud實戰系列中第7篇文章,了解前面第兩篇文章更有助於更好理解本文內容:
①SpringCloud 實戰:引入Eureka組件,完善服務治理
②SpringCloud 實戰:引入Feign組件,發起服務間調用
③SpringCloud 實戰:使用 Ribbon 客戶端負載均衡
④SpringCloud 實戰:引入Hystrix組件,分散式系統容錯
⑤SpringCloud 實戰:引入Zuul組件,開啟網關路由
⑥SpringCloud 實戰:引入gateway組件,開啟網關路由功能

背景

近年來,隨著微服務架構的流行,很多公司都走上了微服務拆分之路。從而使系統變得越來越複雜,原本單體的系統被拆成很多個服務,每個服務之間通過輕量級的 HTTP 協議進行交互。

單體架構時,一個請求的調用鏈路非常清晰,一般由負載均衡器,比如 Nginx。將調用方的請求轉發到後端服務,後端服務進行業務處理後返回給調用方。而當架構變成微服務架構時,可能帶來一系列的問題,比如下面三個問題:

  1. 介面響應慢,怎麼排查?
  2. 服務間的依賴關係如何查看?
  3. 請求貫穿多個微服務,如何將每個請求的日誌串起來?

分散式鏈路跟蹤

分散式鏈路跟蹤原理在於如何能將請求經過的服務節點都關聯起來。當一個請求從客戶端到達網關後,相當於是第一個入口,這時就需要生成一個唯一的請求 ID,作為這次請求的標識。從網關到達服務 A 後,肯定是需要將請求 ID 傳遞到服務 A 中的,這樣才能將網關到服務 A 的請求關聯起來,依次類推,後面會經過多層服務,都需要將資訊一層層傳遞。當然在每一層都需要將數據進行上報、統一存儲、展示等操作。

從我們對這個需求的理解來看,鏈路跟蹤並不是很複雜,而複雜的點在於如何實現這一套跟蹤框架,就拿請求資訊傳遞這件事來說,服務之間交互,有的用的是 Feign 調用介面,有的用的是 RestTemplate 調用介面,要想將資訊傳遞到下游服務,那麼必須得擴展這些調用的框架才可以。

1

核心概念

  • Span

    基本工作單元,例如,發送 RPC 請求是一個新的 Span,發送 HTTP 請求是一個新的 Span,內部方法調用也是一個新的 Span。

  • Trace

    一次分散式調用的鏈路資訊,每次調用鏈路資訊都會在請求入口處生成一個 TraceId。

  • Annotation

    用於記錄事件的資訊。在 Annotation 中會有 CS、SR、SS、CR 這些資訊,前面的C表示客戶端,S表示伺服器端; 後面的S表示sent,也就是發起請求時的動作,R表示Received,也就是接受到請求時的動作;下面分別介紹下這些資訊的作用。

    • CS
      也就是 Client Sent,客戶端發起一個請求,這個 Annotation 表示 Span 的開始。
    • CR
      也就是 Client Received,表示 Span 的結束,客戶端已成功從伺服器端收到響應,用 CR 的時間戳減去 CS 的時間戳就可以知道客戶端從伺服器接收響應所需的全部時間。
    • SS
      也就是 Server Sent,在請求處理完成時將響應發送回客戶端,用 SS 的間戳減去 SR 的時間戳會顯示伺服器端處理請求所需的時間。
    • SR
      也就是 Server Received,伺服器端獲得請求並開始處理它,用 SR 的時間戳減去 CS 的時間戳會顯示網路延遲時間。

請求追蹤過程分解

2

  1. 首先當一個請求訪問 SERVICE1 時,這時是沒有 Trace 和 Span 的,然後會生成 Trace 和 Span,如圖所示生成的 Trace ID 是 X,Span ID 是 A。
  2. 接著 SERVICE1 請求 SERVICE2,這是一次遠程請求,會生成一個新的 Span,Span ID 為 B,Trace ID 不變還是 X。Span B 處於 CS 狀態。
  3. 當請求到達 SERVICE2 後,Trade ID 和 Span ID 就被傳遞過來了,這時,SERVICE2 有內部操作,又生成了一個新的 Span,Span ID 為 C,Trace ID 不變還是 X。
  4. SERVICE2 處理完後向 SERVICE3 發起請求,同時產生新的 Span,Span ID 為 D,Span D 處於 CS 狀態,SERVICE3 接收到請求後,Span D 處於 SR 狀態,同時 SERVICE3 內部操作也會產生新的 Span,Span ID 為 E。
  5. 當 SERVICE3 處理完後,需要將結果響應給調用方,這時 Span D 就處於 SS 的狀態,當 SERVICE2 收到響應後,Span ID 為 D 的 Span 就是 CR 狀態,表示 Span 已經結束了。

Zipkin 介紹

Zipkin 是 Twitter 的一個開源項目,是一個致力於收集所有服務監控數據的分散式跟蹤系統,它提供了收集數據和查詢數據兩大介面服務。有了 Zipkin 我們就可以很直觀地查看調用鏈,並且可能很方便看出服務之間的調用關係,以及調用耗費的時間。

Zipkin還提供了可插拔數據存儲方式:In-Memory、MySql、Cassandra以及Elasticsearch。測試方便可直接採用In-Memory方式進行存儲,生產推薦使用Elasticsearch。

安裝 Zipkin

如果使用了 Java 8 或者更高的版本,可以獲取最新的可執行 jar 包來進行啟動。

  1. 下載jar包:

    curl -sSL //zipkin.io/quickstart.sh | bash -s
    

    如果下載太慢,可以直接訪問Maven地址進行下載最新的jar。

    其他方式安裝,可以查看官網的quickstart。

  2. 啟動服務

    java -jar zipkin.jar
    
  3. 訪問Zipkin

    成功啟動服務後,訪問//127.0.0.1:9411/zipkin/即可。

    3

Sleuth 介紹

Spring Cloud Sleuth 是一種分散式的服務鏈路跟蹤解決方案,通過使用 Spring Cloud Sleuth 可以讓我們快速定位某個服務的問題,以及釐清服務間的依賴關係。

Sleuth 可以添加鏈路資訊到日誌中,這樣的好處是可以統一將日誌進行收集展示,並且可以根據鏈路的資訊將日誌進行串聯。

Sleuth 中的鏈路數據可直接上報給 Zipkin,在 Zipkin 中就可以直接查看調用關係和每個服務的耗時情況.

Sleuth 中內置了很多框架的埋點,比如:Zuul、Feign、Hystrix、RestTemplate 等。正因為有了這些框架的埋點,鏈路資訊才能一直往下傳遞。

通過 Http 結合Zipkin

  1. 在我們的微服務項目中添加Zipkin依賴

    <dependency> 
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-zipkin</artifactId>
    </dependency>
    
  2. 配置Zipkin地址

    spring.zipkin.base-url=//127.0.0.1:9411/
    
  3. 配置取樣比例
    實際使用中可能調用了 10 次介面,但是 Zipkin 中只有一條數據,這是因為收集資訊是有一定比例的,Zipkin 中的數據條數與調用介面次數默認比例是 10:1,通過下面的配置來改變這個比例值:

    spring.sleuth.sampler.probability=1.0
    
  4. 驗證
    啟動我們的微服務,訪問 //localhost:9000/eureka-client/sayHello 介面,介面由網關路由到eureka-client 服務,eureka-client 服務再調用eureka-provider服務,介面返回eureka-provider服務的埠等資訊。
    然後訪問 //127.0.0.1:9411/zipkin ,點擊查詢,即可查看到相關訪問記錄
    4

    點擊菜單上面的依賴,可以查看項目的依賴關係

    5

使用 RabbitMQ or Kafka 代替 HTTP 發送調用鏈數據

數據的發送如果採用 HTTP 對性能還是有影響的。如果Zipkin 的服務端在重啟或者掛掉時,那麼將丟失部分採集數據。為了解決這些問題,我們可以集成 RabbitMQ 或者Kafka 來發送採集數據,利用消息隊列來提高發送性能,保證數據不丟失;

  1. 如果要使用RabbitMQ或Kafka而不是HTTP,需要引入spring-rabbit or spring-kafka 相關依賴。

    <dependency> 
        <groupId>org.springframework.amqp</groupId>
        <artifactId>spring-rabbit</artifactId>
    </dependency>
    
  2. 然後在配置文件修改相關配置:

    # WEB、KAFKA、RABBIT、ACTIVEMQ
    spring.zipkin.sender.type=kafka
    
  3. 刪除之前配置的 spring.zipkin.base-url

  4. 配置kafka、rabbit

自定義 Zipkin 配置

每個跟蹤系統都需要具有Reporter <Span>和Sender,如果要覆蓋提供的bean,則需要給它們指定一個特定的名稱 ZipkinAutoConfiguration.REPORTER_BEAN_NAME and ZipkinAutoConfiguration.SENDER_BEAN_NAME。

下面是示例:

@Configuration
protected static class MyConfig {

    @Bean(ZipkinAutoConfiguration.REPORTER_BEAN_NAME)
    Reporter<zipkin2.Span> myReporter() {
        return AsyncReporter.create(mySender());
    }

    @Bean(ZipkinAutoConfiguration.SENDER_BEAN_NAME)
    MySender mySender() {
        return new MySender();
    }

    static class MySender extends Sender {

        private boolean spanSent = false;

        boolean isSpanSent() {
            return this.spanSent;
        }

        @Override
        public Encoding encoding() {
            return Encoding.JSON;
        }

        @Override
        public int messageMaxBytes() {
            return Integer.MAX_VALUE;
        }

        @Override
        public int messageSizeInBytes(List<byte[]> encodedSpans) {
            return encoding().listSizeInBytes(encodedSpans);
        }

        @Override
        public Call<Void> sendSpans(List<byte[]> encodedSpans) {
            this.spanSent = true;
            return Call.create(null);
        }

    }

}