微服務架構是一個分佈式架構,微服務系統按業務劃分服務單元,一個微服務系統每每有不少個服務單元。因爲服務單元數量衆多,業務的複雜性較高,若是出現了錯誤和異常,很難去定位。主要體如今一個請求可能須要調用不少個服務,而內部服務的調用複雜性決定了問題難以定位。因此在微服務架構中,必須實現分佈式鏈路追蹤,去跟進一個請求到底有哪些服務參與,參與的順序又是怎樣的,從而達到每一個請求的步驟清晰可見,出了問題可以快速定位的目的。html
在微服務系統中,一個來自用戶的請求先到達前端A(如前端界面),而後經過遠程調用,到達系統的中間件B、C(如負載均衡、網關等),最後到達後端服務D、E,後端通過一系列的業務邏輯計算,最後將數據返回給用戶。對於這樣一個請求,經歷了這麼多個服務,怎麼樣將它的請求過程用數據記錄下來呢?這就須要用到服務鏈路追蹤。前端
Spring Cloud Sleuth 爲服務之間調用提供鏈路追蹤。經過 Sleuth 能夠很清楚的瞭解到一個服務請求通過了哪些服務,每一個服務處理花費了多長。從而讓咱們能夠很方便的理清各微服務間的調用關係。此外 Sleuth 能夠幫助咱們:java
Google開源了Dapper鏈路追蹤組件,並在2010年發表了論文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》,這篇論文是業內實現鏈路追蹤的標杆和理論基礎,具備很高的參考價值。mysql
Spring Cloud Sleuth採用了Google的開源項目Dapper的專業術語。git
Annotation:用於記錄一個事件,一些核心註解用於定義一個請求的開始和結束,這些註解以下。github
Spring Cloud Sleuth 也爲咱們提供了一套完整的鏈路解決方案,Spring Cloud Sleuth 能夠結合 Zipkin,將信息發送到 Zipkin,利用 Zipkin 的存儲來存儲鏈路信息,利用 Zipkin UI 來展現數據。web
Zipkin是一種分佈式鏈路追蹤系統。 它有助於收集解決微服務架構中的延遲問題所需的時序數據。 它管理這些數據的收集和查找。 Zipkin的設計基於Google Dapper論文。spring
跟蹤器存在於應用程序中,記錄請求調用的時間和元數據。跟蹤器使用庫,它們的使用對用戶是無感知的。例如,Web服務器會在收到請求時和發送響應時會記錄相應的時間和一些元數據。一次完整鏈路請求所收集的數據被稱爲Span。sql
咱們可使用它來收集各個服務器上請求鏈路的跟蹤數據,並經過它提供的 REST API 接口來輔助咱們查詢跟蹤數據以實現對分佈式系統的監控程序,從而及時地發現系統中出現的延遲升高問題並找出系統性能瓶頸的根源。除了面向開發的 API 接口以外,它也提供了方便的 UI 組件來幫助咱們直觀的搜索跟蹤信息和分析請求鏈路明細,好比:能夠查詢某段時間內各用戶請求的處理時間等。
Zipkin 提供了可插拔數據存儲方式:In-Memory、MySql、Cassandra 以及 Elasticsearch。接下來的測試爲方便直接採用 In-Memory 方式進行存儲,生產推薦 Elasticsearch.docker
上圖展現了 Zipkin 的基礎架構,它主要由 4 個核心組件構成:
在本案例一共有三個應用,分別爲註冊中心,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的服務。
在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,顯示的界面以下:
依次啓動eureka-server,eureka-client,eureka-client-feign的三個應用,等全部應用啓動完成後,在瀏覽器上訪問http://localhost:8765/hi(若是報錯,是服務與發現須要必定的時間,耐心等待幾十秒),訪問成功後,再次在瀏覽器上訪問zipkin-server的頁面,顯示以下:
從上圖能夠看出每次請求所消耗的時間,以及一些span的信息。
從上圖能夠看出具體的服務依賴關係,eureka-feign-client依賴了eureka-client。
在上面的案例中使用的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方式發送鏈路數據同樣的接口。
在頁面上能夠查看每一個請求的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; }
上面的例子是將鏈路數據存在內存中,只要zipkin-server重啓以後,以前的鏈路數據所有查找不到了,zipkin是支持將鏈路數據存儲在mysql、cassandra、elasticsearch中的。
如今講解如何將鏈路數據存儲在Mysql數據庫中。
首先須要初始化zikin存儲在Mysql的數據的scheme,能夠在這裏查看https://github.com/openzipkin...,具體以下:
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、cassandra三、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/,能夠看到鏈路數據。這時去數據庫查看數據,也是能夠看到存儲在數據庫的鏈路數據,以下:
這時重啓應用zipkin.jar,再次在瀏覽器上訪問http://localhost:9411/zipkin/,仍然能夠獲得以前的結果,證實鏈路數據存儲在數據庫中,而不是內存中。
zipkin-server支持將鏈路數據存儲在ElasticSearch中。讀者須要自行安裝ElasticSearch和Kibana,下載地址爲https://www. elastic.co/products/elasticsearch。安裝完成後啓動,其中ElasticSearch的默認端口號爲9200,Kibana的默認端口號爲5601。
同理,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。
鏈路數據存儲在ElasticSearch中,ElasticSearch能夠和Kibana結合,將鏈路數據展現在Kibana上。安裝完成Kibana後啓動,Kibana默認會向本地端口爲9200的ElasticSearch讀取數據。Kibana默認的端口爲5601,訪問Kibana的主頁http://localhost:5601,其界面以下圖所示。
在上圖的界面中,單擊「Management」按鈕,而後單擊「Add New」,添加一個index。咱們將在上節ElasticSearch中寫入鏈路數據的index配置爲「zipkin」,那麼在界面填寫爲「zipkin-*」,單擊「Create」按鈕,界面以下圖所示:
建立完成index後,單擊「Discover」,就能夠在界面上展現鏈路數據了,展現界面以下圖所示。
https://github.com/spring-clo...
https://cloud.spring.io/sprin...
https://github.com/openzipkin...
https://github.com/openzipkin...
https://windmt.com/2018/04/24...
https://segmentfault.com/a/11...
elatstic 版本爲2.6.x,下載地址:https://www.elastic.co/downlo...
http://www.cnblogs.com/JreeyQ...