Spring Cloud Sleuth 之Greenwich版本全攻略

方誌朋發表於2019-02-12

微服務架構是一個分散式架構,微服務系統按業務劃分服務單元,一個微服務系統往往有很多個服務單元。由於服務單元數量眾多,業務的複雜性較高,如果出現了錯誤和異常,很難去定位。主要體現在一個請求可能需要呼叫很多個服務,而內部服務的呼叫複雜性決定了問題難以定位。所以在微服務架構中,必須實現分散式鏈路追蹤,去跟進一個請求到底有哪些服務參與,參與的順序又是怎樣的,從而達到每個請求的步驟清晰可見,出了問題能夠快速定位的目的。

image

在微服務系統中,一個來自使用者的請求先到達前端A(如前端介面),然後通過遠端呼叫,到達系統的中介軟體B、C(如負載均衡、閘道器等),最後到達後端服務D、E,後端經過一系列的業務邏輯計算,最後將資料返回給使用者。對於這樣一個請求,經歷了這麼多個服務,怎麼樣將它的請求過程用資料記錄下來呢?這就需要用到服務鏈路追蹤。

Spring Cloud Sleuth

Spring Cloud Sleuth 為服務之間呼叫提供鏈路追蹤。通過 Sleuth 可以很清楚的瞭解到一個服務請求經過了哪些服務,每個服務處理花費了多長。從而讓我們可以很方便的理清各微服務間的呼叫關係。此外 Sleuth 可以幫助我們:

  • 耗時分析: 通過 Sleuth 可以很方便的瞭解到每個取樣請求的耗時,從而分析出哪些服務呼叫比較耗時;
  • 視覺化錯誤: 對於程式未捕捉的異常,可以通過整合 Zipkin 服務介面上看到;
  • 鏈路優化: 對於呼叫比較頻繁的服務,可以針對這些服務實施一些優化措施。

Google開源了Dapper鏈路追蹤元件,並在2010年發表了論文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》,這篇論文是業內實現鏈路追蹤的標杆和理論基礎,具有很高的參考價值。

image

Spring Cloud Sleuth採用了Google的開源專案Dapper的專業術語。

  • Span:基本工作單元,傳送一個遠端排程任務就會產生一個Span,Span是用一個64位ID唯一標識的,Trace是用另一個64位ID唯一標識的。Span還包含了其他的資訊,例如摘要、時間戳事件、Span的ID以及程式ID。
  • Trace:由一系列Span組成的,呈樹狀結構。請求一個微服務系統的API介面,這個API介面需要呼叫多個微服務單元,呼叫每個微服務單元都會產生一個新的Span,所有由這個請求產生的Span組成了這個Trace。
  • Annotation:用於記錄一個事件,一些核心註解用於定義一個請求的開始和結束,這些註解如下。
    • cs-Client Sent:客戶端傳送一個請求,這個註解描述了Span的開始。
    • sr-Server Received:服務端獲得請求並準備開始處理它,如果將其sr減去cs時間戳,便可得到網路傳輸的時間。
    • ss-Server Sent:服務端傳送響應,該註解表明請求處理的完成(當請求返回客戶端),用ss的時間戳減去sr時間戳,便可以得到伺服器請求的時間。
    • cr-Client Received:客戶端接收響應,此時Span結束,如果cr的時間戳減去cs時間戳,便可以得到整個請求所消耗的時間。

Spring Cloud Sleuth 也為我們提供了一套完整的鏈路解決方案,Spring Cloud Sleuth 可以結合 Zipkin,將資訊傳送到 Zipkin,利用 Zipkin 的儲存來儲存鏈路資訊,利用 Zipkin UI 來展示資料。

Zipkin

Zipkin是一種分散式鏈路追蹤系統。 它有助於收集解決微服務架構中的延遲問題所需的時序資料。 它管理這些資料的收集和查詢。 Zipkin的設計基於Google Dapper論文。

跟蹤器存在於應用程式中,記錄請求呼叫的時間和後設資料。跟蹤器使用庫,它們的使用對使用者是無感知的。例如,Web伺服器會在收到請求時和傳送響應時會記錄相應的時間和一些後設資料。一次完整鏈路請求所收集的資料被稱為Span。

我們可以使用它來收集各個伺服器上請求鏈路的跟蹤資料,並通過它提供的 REST API 介面來輔助我們查詢跟蹤資料以實現對分散式系統的監控程式,從而及時地發現系統中出現的延遲升高問題並找出系統效能瓶頸的根源。除了面向開發的 API 介面之外,它也提供了方便的 UI 元件來幫助我們直觀的搜尋跟蹤資訊和分析請求鏈路明細,比如:可以查詢某段時間內各使用者請求的處理時間等。 Zipkin 提供了可插拔資料儲存方式:In-Memory、MySql、Cassandra 以及 Elasticsearch。接下來的測試為方便直接採用 In-Memory 方式進行儲存,生產推薦 Elasticsearch.

image

上圖展示了 Zipkin 的基礎架構,它主要由 4 個核心元件構成:

  • Collector:收集器元件,它主要用於處理從外部系統傳送過來的跟蹤資訊,將這些資訊轉換為 Zipkin 內部處理的 Span 格式,以支援後續的儲存、分析、展示等功能。
  • Storage:儲存元件,它主要對處理收集器接收到的跟蹤資訊,預設會將這些資訊儲存在記憶體中,我們也可以修改此儲存策略,通過使用其他儲存元件將跟蹤資訊儲存到資料庫中。
  • RESTful API:API 元件,它主要用來提供外部訪問介面。比如給客戶端展示跟蹤資訊,或是外接系統訪問以實現監控等。
  • Web UI:UI 元件,基於 API 元件實現的上層應用。通過 UI 元件使用者可以方便而有直觀地查詢和分析跟蹤資訊。

案例實戰

在本案例一共有三個應用,分別為註冊中心,eureka-server、eureka-client、eureka-client-feign,三個應用的基本資訊如下:

應用名 作用
eureka-server 8761 註冊中心
eureka-client 8763 服務提供者
eureka-client-feign 8765 服務消費者

其中eureka-server 應用為註冊中心,其他兩個應用向它註冊。eureka-client為服務提供者,提供了一個RESTAPI,eureka-client-feign為服務消費者,通過Feign Client向服務提供者消費服務。

在之前的文章已經講述瞭如何如何搭建服務註冊中心,在這裡就省略這一部分內容。服務提供者提供一個REST介面,服務消費者通過FeignClient消費服務。

服務提供者

eureka-client服務提供者,對外提供一個RESTAPI,並向服務註冊中心註冊,這部分內容,不再講述,見原始碼。需要在工程的pom檔案加上sleuth的起步依賴和zipkin的起步依賴,程式碼如下:

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
複製程式碼

在工程的配置檔案application.yml需要做以下的配置:

spring:
  sleuth:
    web:
      client:
        enabled: true
    sampler:
      probability: 1.0 # 將取樣比例設定為 1.0,也就是全部都需要。預設是 0.1
  zipkin:
    base-url: http://localhost:9411/ # 指定了 Zipkin 伺服器的地址

複製程式碼

其中spring.sleuth.web.client.enable為true設定的是web開啟sleuth功能;spring.sleuth.sampler.probability可以設定為小數,最大值為1.0,當設定為1.0時就是鏈路資料100%收集到zipkin-server,當設定為0.1時,即10%概率收集鏈路資料;spring.zipkin.base-url設定zipkin-server的地址。

對外提供一個Api,程式碼如下:


@RestController
public class HiController {

    @Value("${server.port}")
    String port;
    @GetMapping("/hi")
    public String home(@RequestParam String name) {
        return "hi "+name+",i am from port:" +port;
    }

}

複製程式碼

服務消費者

服務消費者通過FeignClient消費服務提供者提供的服務。同服務提供者一樣,需要在工程的pom檔案加上sleuth的起步依賴和zipkin的起步依賴,另外也需要在配置檔案application.yml做相關的配置,具體同服務提供者。

服務消費者通過feignClient進行服務消費,feignclient程式碼如下:


@FeignClient(value = "eureka-client",configuration = FeignConfig.class)
public interface EurekaClientFeign {

    @GetMapping(value = "/hi")
    String sayHiFromClientEureka(@RequestParam(value = "name") String name);
}


複製程式碼

servcie層程式碼如下:

@Service
public class HiService {

    @Autowired
    EurekaClientFeign eurekaClientFeign;

 
    public String sayHi(String name){
        return  eurekaClientFeign.sayHiFromClientEureka(name);
    }
}

複製程式碼

controller程式碼如下:

@RestController
public class HiController {
    @Autowired
    HiService hiService;

    @GetMapping("/hi")
    public String sayHi(@RequestParam( defaultValue = "forezp",required = false)String name){
        return hiService.sayHi(name);
    }

複製程式碼

上面的程式碼對外暴露一個API,通過FeignClient的方式呼叫eureka-client的服務。

zipkin-server

在Spring Cloud D版本,zipkin-server通過引入依賴的方式構建工程,自從E版本之後,這一方式改變了,採用官方的jar形式啟動,所以需要通過下載官方的jar來啟動,也通過以下命令一鍵啟動:

curl -sSL https://zipkin.io/quickstart.sh | bash -s
java -jar zipkin.jar

複製程式碼

上面的第一行命令會從zipkin官網下載官方的jar包。 如果是window系統,建議使用gitbash執行上面的命令。

如果用 Docker 的話,使用以下命令:


docker run -d -p 9411:9411 openzipkin/zipkin

複製程式碼

通過java -jar zipkin.jar的方式啟動之後,在瀏覽器上訪問lcoalhost:9411,顯示的介面如下:

1.png

鏈路資料驗證

依次啟動eureka-server,eureka-client,eureka-client-feign的三個應用,等所有應用啟動完成後,在瀏覽器上訪問http://localhost:8765/hi(如果報錯,是服務與發現需要一定的時間,耐心等待幾十秒),訪問成功後,再次在瀏覽器上訪問zipkin-server的頁面,顯示如下:

2.png

從上圖可以看出每次請求所消耗的時間,以及一些span的資訊。

3.png

從上圖可以看出具體的服務依賴關係,eureka-feign-client依賴了eureka-client。

使用rabbitmq進行鏈路資料收集

在上面的案例中使用的http請求的方式將鏈路資料傳送給zipkin-server,其實還可以使用rabbitmq的方式進行服務的消費。使用rabbitmq需要安裝rabbitmq程式,下載地址http://www.rabbitmq.com/。

下載完成後,需要eureka-client和eureka-client-feign的起步依賴加上rabbitmq的依賴,依賴如下:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
複製程式碼

在配置檔案上需要配置rabbitmq的配置,配置資訊如下:

spring:
  rabbitmq:
    host: localhost
    username: guest
    password: guest
    port: 5672

複製程式碼

另外需要把spring.zipkin.base-url去掉。

在上面2個工程中,rabbitmq通過傳送鏈路資料,那麼zipkin-server是怎麼樣知道rabbitmq的地址呢,怎麼監聽收到的鏈路資料呢?這需要在程式啟動的時候,通過環境變數的形式到環境中,然後zikin-server從環境變數中讀取。 可配置的屬性如下:

屬性 環境變數 描述
zipkin.collector.rabbitmq.addresses RABBIT_ADDRESSES 用逗號分隔的 RabbitMQ 地址列表,例如localhost:5672,localhost:5673
zipkin.collector.rabbitmq.password RABBIT_PASSWORD 連線到 RabbitMQ 時使用的密碼,預設為 guest
zipkin.collector.rabbitmq.username RABBIT_USER 連線到 RabbitMQ 時使用的使用者名稱,預設為guest
zipkin.collector.rabbitmq.virtual-host RABBIT_VIRTUAL_HOST 使用的 RabbitMQ virtual host,預設為 /
zipkin.collector.rabbitmq.use-ssl RABBIT_USE_SSL 設定為true則用 SSL 的方式與 RabbitMQ 建立連結
zipkin.collector.rabbitmq.concurrency RABBIT_CONCURRENCY 併發消費者數量,預設為1
zipkin.collector.rabbitmq.connection-timeout RABBIT_CONNECTION_TIMEOUT 建立連線時的超時時間,預設為 60000毫秒,即 1 分鐘
zipkin.collector.rabbitmq.queue RABBIT_QUEUE 從中獲取 span 資訊的佇列,預設為 zipkin

比如,通過以下命令啟動:

RABBIT_ADDRESSES=localhost java -jar zipkin.jar

複製程式碼

上面的命令等同於一下的命令:

java -jar zipkin.jar --zipkin.collector.rabbitmq.addressed=localhost

複製程式碼

用上面的2條命令中的任何一種方式重新啟動zipkin-server程式,並重新啟動eureka-client、eureka-server、eureka-client-feign,動完成後在瀏覽器上訪問http://localhost:8765/hi,再訪問http://localhost:9411/zipkin/,就可以看到通過Http方式傳送鏈路資料一樣的介面。

自定義Tag

在頁面上可以檢視每個請求的traceId,每個trace又包含若干的span,每個span又包含了很多的tag,自定義tag可以通過Tracer這個類來自定義。


@Autowired
Tracer tracer;

 @GetMapping("/hi")
    public String home(@RequestParam String name) {
        tracer.currentSpan().tag("name","forezp");
        return "hi "+name+",i am from port:" +port;
    }

複製程式碼

將鏈路資料儲存在Mysql資料庫中

上面的例子是將鏈路資料存在記憶體中,只要zipkin-server重啟之後,之前的鏈路資料全部查詢不到了,zipkin是支援將鏈路資料儲存在mysql、cassandra、elasticsearch中的。 現在講解如何將鏈路資料儲存在Mysql資料庫中。 首先需要初始化zikin儲存在Mysql的資料的scheme,可以在這裡檢視https://github.com/openzipkin/zipkin/blob/master/zipkin-storage/mysql-v1/src/main/resources/mysql.sql,具體如下:

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 and autocomplete values';
ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces and autocomplete values';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id`, `span_id`, `a_key`) COMMENT 'for dependencies job';

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

ALTER TABLE zipkin_dependencies ADD UNIQUE KEY(`day`, `parent`, `child`);
複製程式碼

在資料庫中初始化上面的指令碼之後,需要做的就是zipkin-server如何連線資料庫。zipkin如何連資料庫同連線rabbitmq一樣。zipkin連線資料庫的屬性所對應的環境變數如下:

屬性 環境變數 描述
zipkin.torage.type STORAGE_TYPE 預設的為mem,即為記憶體,其他可支援的為cassandra、cassandra3、elasticsearch、mysql
zipkin.torage.mysql.host MYSQL_HOST 資料庫的host,預設localhost
zipkin.torage.mysql.port MYSQL_TCP_PORT 資料庫的埠,預設3306
zipkin.torage.mysql.username MYSQL_USER 連線資料庫的使用者名稱,預設為空
zipkin.torage.mysql.password MYSQL_PASS 連線資料庫的密碼,預設為空
zipkin.torage.mysql.db MYSQL_DB zipkin使用的資料庫名,預設是zipkin
zipkin.torage.mysql.max-active MYSQL_MAX_CONNECTIONS 最大連線數,預設是10
STORAGE_TYPE=mysql MYSQL_HOST=localhost MYSQL_TCP_PORT=3306 MYSQL_USER=root MYSQL_PASS=123456 MYSQL_DB=zipkin java -jar zipkin.jar
複製程式碼

等同於以下的命令

java -jar zipkin.jar --zipkin.torage.type=mysql --zipkin.torage.mysql.host=localhost --zipkin.torage.mysql.port=3306 --zipkin.torage.mysql.username=root --zipkin.torage.mysql.password=123456
複製程式碼

使用上面的命令啟動zipkin.jar工程,然後再瀏覽數上訪問http://localhost:8765/hi,再訪問http://localhost:9411/zipkin/,可以看到鏈路資料。這時去資料庫檢視資料,也是可以看到儲存在資料庫的鏈路資料,如下:

微信截圖_20190129154520.png

這時重啟應用zipkin.jar,再次在瀏覽器上訪問http://localhost:9411/zipkin/,仍然可以得到之前的結果,證明鏈路資料儲存在資料庫中,而不是記憶體中。

將鏈路資料存在在Elasticsearch中

zipkin-server支援將鏈路資料儲存在ElasticSearch中。讀者需要自行安裝ElasticSearch和Kibana,下載地址為https://www. elastic.co/products/el…

同理,zipkin連線elasticsearch也是從環境變數中讀取的,elasticsearch相關的環境變數和對應的屬性如下:

屬性 環境變數 描述
zipkin.torage.elasticsearch.hosts ES_HOSTS ES_HOSTS,預設為空
zipkin.torage.elasticsearch.pipeline ES_PIPELINE ES_PIPELINE,預設為空
zipkin.torage.elasticsearch.max-requests ES_MAX_REQUESTS ES_MAX_REQUESTS,預設為64
zipkin.torage.elasticsearch.timeout ES_TIMEOUT ES_TIMEOUT,預設為10s
zipkin.torage.elasticsearch.index ES_INDEX ES_INDEX,預設是zipkin
zipkin.torage.elasticsearch.date-separator ES_DATE_SEPARATOR ES_DATE_SEPARATOR,預設為“-”
zipkin.torage.elasticsearch.index-shards ES_INDEX_SHARDS ES_INDEX_SHARDS,預設是5
zipkin.torage.elasticsearch.index-replicas ES_INDEX_REPLICAS ES_INDEX_REPLICAS,預設是1
zipkin.torage.elasticsearch.username ES_USERNAME ES的使用者名稱,預設為空
zipkin.torage.elasticsearch.password ES_PASSWORD ES的密碼,預設是為空

採用以下命令啟動zipkin-server:


STORAGE_TYPE=elasticsearch ES_HOSTS=http://localhost:9200 ES_INDEX=zipkin java -jar zipkin.jar

複製程式碼
java -jar zipkin.jar --STORAGE_TYPE=elasticsearch --ES_HOSTS=http://localhost:9200 --ES_INDEX=zipkin 

複製程式碼
java -jar zipkin.jar --STORAGE_TYPE=elasticsearch --ES_HOSTS=http://localhost:9200 --ES_INDEX=zipkin 
複製程式碼
java -jar zipkin.jar --zipkin.torage.type=elasticsearch --zipkin.torage.elasticsearch.hosts=http://localhost:9200 --zipkin.torage.elasticsearch.index=zipkin 

複製程式碼

啟動完成後,然後在瀏覽數上訪問http://localhost:8765/hi,再訪問http://localhost:9411/zipkin/,可以看到鏈路資料。這時鏈路資料儲存在ElasticSearch。

在zipkin上展示鏈路資料

鏈路資料儲存在ElasticSearch中,ElasticSearch可以和Kibana結合,將鏈路資料展示在Kibana上。安裝完成Kibana後啟動,Kibana預設會向本地埠為9200的ElasticSearch讀取資料。Kibana預設的埠為5601,訪問Kibana的主頁http://localhost:5601,其介面如下圖所示。

圖片1.png

在上圖的介面中,單擊“Management”按鈕,然後單擊“Add New”,新增一個index。我們將在上節ElasticSearch中寫入鏈路資料的index配置為“zipkin”,那麼在介面填寫為“zipkin-*”,單擊“Create”按鈕,介面如下圖所示:

圖片2.png

建立完成index後,單擊“Discover”,就可以在介面上展示鏈路資料了,展示介面如下圖所示。

圖片3.png

參考資料

zipkin.io/

github.com/spring-clou…

cloud.spring.io/spring-clou…

github.com/openzipkin/…

github.com/openzipkin/…

windmt.com/2018/04/24/…

segmentfault.com/a/119000001…

elatstic 版本為2.6.x,下載地址:www.elastic.co/downloads/p…

www.cnblogs.com/JreeyQi/p/9…

Spring Cloud Sleuth 之Greenwich版本全攻略
掃碼關注有驚喜

(轉載本站文章請註明作者和出處 方誌朋的部落格

相關文章