上一篇:《Spring Cloud入門教程(六):API服務(wù)網(wǎng)關(guān)(Zuul) 下》
本人和同事撰寫的《Spring Cloud微服務(wù)架構(gòu)開發(fā)實(shí)戰(zhàn)》一書也在京東、當(dāng)當(dāng)?shù)葧晟霞?,大家可以點(diǎn)擊這里前往購(gòu)買,多謝大家支持和捧場(chǎng)!
當(dāng)我們進(jìn)行微服務(wù)架構(gòu)開發(fā)時(shí),通常會(huì)根據(jù)業(yè)務(wù)來(lái)劃分微服務(wù),各業(yè)務(wù)之間通過REST進(jìn)行調(diào)用。一個(gè)用戶操作,可能需要很多微服務(wù)的協(xié)同才能完成,如果在業(yè)務(wù)調(diào)用鏈路上任何一個(gè)微服務(wù)出現(xiàn)問題或者網(wǎng)絡(luò)超時(shí),都會(huì)導(dǎo)致功能失敗。隨著業(yè)務(wù)越來(lái)越多,對(duì)于微服務(wù)之間的調(diào)用鏈的分析會(huì)越來(lái)越復(fù)雜。
Spring Cloud Sleuth為服務(wù)之間調(diào)用提供鏈路追蹤。通過Sleuth可以很清楚的了解到一個(gè)服務(wù)請(qǐng)求經(jīng)過了哪些服務(wù),每個(gè)服務(wù)處理花費(fèi)了多長(zhǎng)。從而讓我們可以很方便的理清各微服務(wù)間的調(diào)用關(guān)系。此外Sleuth可以幫助我們:
- 耗時(shí)分析: 通過Sleuth可以很方便的了解到每個(gè)采樣請(qǐng)求的耗時(shí),從而分析出哪些服務(wù)調(diào)用比較耗時(shí);
- 可視化錯(cuò)誤: 對(duì)于程序未捕捉的異常,可以通過集成Zipkin服務(wù)界面上看到;
- 鏈路優(yōu)化: 對(duì)于調(diào)用比較頻繁的服務(wù),可以針對(duì)這些服務(wù)實(shí)施一些優(yōu)化措施。
1. Sleuth+Log 示例代碼
我們先用最簡(jiǎn)單的方式集成Sleuth,把Sleuth所跟蹤到的信息輸出到日志中。基礎(chǔ)代碼采用之前所構(gòu)建的商城項(xiàng)目。
1.1 改造Mall-Web
增加bootstrap.properties文件
為了能夠讓日志文件可以獲取到服務(wù)名稱,我們需要將原來(lái)配置在application.properties中的部分內(nèi)容移入到bootstrap.properties配置文件中,這是因?yàn)镾pringBoot在啟動(dòng)時(shí)會(huì)優(yōu)先掃描bootstrap配置源,從而能夠讓日志可以獲取到服務(wù)名稱。
server.port=8080
spring.application.name=MALL-WEB
修改application.properties文件
eureka.client.service-url.defaultZone=http://localhost:8260/eureka
logging.level.org.springframework=INFO
logging.level.org.springframework.web.servlet.DispatcherServlet=DEBUG
這里主要是把DispatcherServlet的日志級(jí)別修改為DEBUG。
修改Logback配文件
在resources目錄中增加一個(gè)名稱為: logback-spring.xml的文件,內(nèi)容如下:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
?
<springProperty scope="context" name="springAppName" source="spring.application.name"/>
<!-- Example for logging into the build folder of your project -->
<property name="LOG_FILE" value="${BUILD_FOLDER:-build}/${springAppName}"/>?
<property name="CONSOLE_LOG_PATTERN"
value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p})
%clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>
%d{yyyy-MM-dd HH:mm:ss SSS} [%thread] %-5level %logger{36} - %msg%n
</Pattern>
</layout>
</appender>
<!-- Appender to log to console -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!-- Minimum logging level to be presented in the console logs-->
<level>DEBUG</level>
</filter>
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- Appender to log to file -->?
<appender name="flatfile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.gz</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
? ?
<root level="INFO">
<appender-ref ref="console"/>
<!-- uncomment this to have also JSON logs -->
<!--<appender-ref ref="logstash"/>-->
<!--<appender-ref ref="flatfile"/>-->
</root>
</configuration>
SpringCloud的參考手冊(cè)中提到:SLF4J MDC總是會(huì)自動(dòng)進(jìn)行設(shè)置,并且如果使用logback,那么trace/span的id則會(huì)立即顯示在日志中。其他的日志系統(tǒng)需要配置各自的格式來(lái)達(dá)到這樣的效果。默認(rèn)的logging.pattern.level設(shè)置為%clr(%5p) %clr([${spring.application.name:},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]){yellow} (這也是一個(gè)Spring Boot整合logback時(shí)有的特性)。 這就意味著,如果使用SLF4J時(shí)不需要手工配置該格式,而其它日志系統(tǒng)則必須手工進(jìn)行配置,否則不會(huì)輸出。
修改POM文件
在pom.xml文件中增加如下依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
1.2 改造Product-Service
改造方式與上面相同。
1.3 啟動(dòng)測(cè)試
按照先后順序分別啟動(dòng)Service-discovery、Product-Service和Mall-Web工程。然后在瀏覽器中輸入: http://localhost:8080/products。然后我們分別觀察Mall-Web和Product-Service控制臺(tái)中日志輸出,可以看到類似下面輸出:
2017-07-10 21:36:24.802 DEBUG [MALL-WEB,e23abdb6268af95d,e23abdb6268af95d,false] [MALL-WEB,e23abdb6268af95d,e23abdb6268af95d,,false] 92827 --- [nio-8080-exec-4] o.s.web.servlet.DispatcherServlet : DispatcherServlet with name 'dispatcherServlet' processing GET request for [/products]
2017-07-10 21:36:24.838 DEBUG [PRODUCT-SERVICE,e23abdb6268af95d,c68a9b1c2ab8a025,false] [PRODUCT-SERVICE,e23abdb6268af95d,c68a9b1c2ab8a025,e23abdb6268af95d,false] 92782 --- [nio-2100-exec-3] o.s.web.servlet.DispatcherServlet : DispatcherServlet with name 'dispatcherServlet' processing GET request for [/products]
日志中類似 [MALL-WEB,e23abdb6268af95d,e23abdb6268af95d,false]、[PRODUCT-SERVICE,e23abdb6268af95d,c68a9b1c2ab8a025,false] 的日志內(nèi)容它們的格式為: [appname,traceId,spanId,exportable],也就是Sleuth的跟蹤數(shù)據(jù)。其中:
- appname: 為微服務(wù)的服務(wù)名稱;
- traceId\spanId: 為Sleuth鏈路追蹤的兩個(gè)術(shù)語(yǔ),后面我們?cè)僮屑?xì)介紹;
- exportable 是否是發(fā)送給Zipkin。
2. Sleuth術(shù)語(yǔ)
因?yàn)镾leuth是根據(jù)Google的Dapper’s論文而來(lái)的,所以在術(shù)語(yǔ)上也借鑒了Dapper。
- Span: 最基本的工作單元。例如: 發(fā)送一個(gè)RPC就是一個(gè)新的span,同樣一次RPC的應(yīng)答也是。Span通過一個(gè)唯一的,長(zhǎng)度為64位的ID來(lái)作為標(biāo)識(shí),另外,再使用一個(gè)64位ID用于服務(wù)調(diào)用跟蹤。Span也可以帶有其他數(shù)據(jù),例如:描述,時(shí)間戳,鍵值對(duì)標(biāo)簽,起始Span的ID,以及處理ID(通常使用IP地址)等等。 Span有起始和結(jié)束,它們用于跟蹤時(shí)間信息。Span應(yīng)該都是成對(duì)出現(xiàn)的,有始必有終,所以一旦創(chuàng)建了一個(gè)span,那就必須在未來(lái)某個(gè)時(shí)間點(diǎn)結(jié)束它。
提示: 起始的Span通常被稱為:
root span。它的id通常也被作為一個(gè)跟蹤記錄的id。
- Trace: 一個(gè)樹結(jié)構(gòu)的Span集合。例如:在分布式大數(shù)據(jù)存儲(chǔ)中,可能每一次請(qǐng)求都是一次跟蹤記錄。
-
Annotation: 用于記錄一個(gè)事件的時(shí)間信息。一些基礎(chǔ)核心的Annotation用于記錄請(qǐng)求的起始和結(jié)束時(shí)間,例如:
- cs: 客戶端發(fā)送(Client Sent的縮寫)。這個(gè)annotation表示一個(gè)span的起始;
-
sr: 服務(wù)端接收(Server Received的縮寫)。表示服務(wù)端接收到請(qǐng)求,并開始處理。如果減去
cs的時(shí)間戳,則可以計(jì)算出網(wǎng)絡(luò)傳輸耗時(shí)。 -
ss: 服務(wù)端完成請(qǐng)求處理,應(yīng)答信息被發(fā)回客戶端(Server Sent的縮寫)。如果減去
sr的時(shí)間戳,則可以計(jì)算出服務(wù)端處理請(qǐng)求的耗時(shí)。 -
cr: 客戶端接收(Client Received的縮寫)。標(biāo)志著Span的結(jié)束。客戶端成功的接收到服務(wù)端的應(yīng)答信息。如果減去
cs的時(shí)間戳,則可以計(jì)算出請(qǐng)求的響應(yīng)耗時(shí)。
下圖,通過可視化的方式描述了Span和Trace的概念:

圖中每一個(gè)顏色都表示著一個(gè)span(總共7個(gè)span,從A到G)。它們都有以下這些數(shù)據(jù)信息:
Trace Id = X
Span Id = D
Client Sent
表示該Span的Trace-Id為X,Span-Id為D。相應(yīng)的事件為Client Sent。
這些Span的上下級(jí)關(guān)系可以通過下圖來(lái)表示:

3. 整合Zipkin服務(wù)
Zipkin是一個(gè)致力于收集分布式服務(wù)的時(shí)間數(shù)據(jù)的分布式跟蹤系統(tǒng)。其主要涉及以下四個(gè)組件:
- collector: 數(shù)據(jù)采集;
- storage: 數(shù)據(jù)存儲(chǔ);
- search: 數(shù)據(jù)查詢;
- UI: 數(shù)據(jù)展示.
Zipkin提供了可插拔數(shù)據(jù)存儲(chǔ)方式:In-Memory、MySql、Cassandra以及Elasticsearch。接下來(lái)的測(cè)試為方便直接采用In-Memory方式進(jìn)行存儲(chǔ),個(gè)人推薦Elasticsearch,特別是后續(xù)當(dāng)我們需要整合ELK時(shí)。
ZipKin在Github源碼地址為:https://github.com/openzipkin/zipkin。
ZipKin運(yùn)行環(huán)境需要Jdk8支持。
在本篇中我們僅通過Http的方式向Zipkin提供跟蹤數(shù)據(jù),關(guān)于使用stream的方式后續(xù)講到Spring Cloud Bus的時(shí)候再說明。我們所要搭建的系統(tǒng)架構(gòu)如下(做了精簡(jiǎn)):

3.1 構(gòu)建Zipkin-Server
編寫pom.xml文件
還是繼承自我們之前的parent:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>twostepsfromjava.cloud</groupId>
<artifactId>twostepsfromjava-cloud-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../parent</relativePath>
</parent>
<artifactId>zipkin-server</artifactId>
<name>Spring Cloud Sample Projects: Zipkin Server</name>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
這里需要說明的時(shí)zipkin-autoconfigure-ui包提供了可視化界面。
編寫啟動(dòng)類
/**
* TwoStepsFromJava Cloud -- Zipkin Server Project
*
* @author CD826(CD826Dong@gmail.com)
* @since 1.0.0
*/
@SpringBootApplication
@EnableZipkinServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
這里在Application的注解中增加@EnableZipkinServer,開啟Zipkin服務(wù)。
編寫bootstrap.properties配置文件
server.port=8240
spring.application.name=ZIPKIN-SERVER
我們把Zipkin服務(wù)的端口設(shè)置為:8240。
3.2 修改Mall-Web工程
修改pom.xml文件
在pom文件中增加以下依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
同時(shí)可以刪除之前的:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
應(yīng)為,在spring-cloud-starter-zipkin中已經(jīng)包含了對(duì)spring-cloud-starter-sleuth的依賴。
修改application.properties配置文件
在application.properties增加以下內(nèi)容:
spring.zipkin.base-url=http://localhost:8240
spring.sleuth.sampler.percentage=1.0
spring.zipkin.base-url指定了Zipkin服務(wù)器的地址,spring.sleuth.sampler.percentage將采樣比例設(shè)置為1.0,也就是全部都需要。關(guān)于采樣可以參考下面的說明。
3.3 修改Product-Service工程
改造方式與上面相同。
3.4 啟動(dòng)測(cè)試
按照先后順序分別啟動(dòng)Service-discovery、Zipkin-Server、Product-Service和Mall-Web工程。
查看Zipkin服務(wù)器
啟動(dòng)后我們可以訪問:http://localhost:8240,可以看到如下界面:

說明Zipkin服務(wù)器啟動(dòng)成功。
訪問幾次Mall-Web所提供的服務(wù)
我們?cè)跒g覽器中訪問幾次Mall-Web所提供的服務(wù),然后轉(zhuǎn)到Zipkin服務(wù)器,可以看到如下界面:

可以看到,Zipkin已經(jīng)獲取到幾次服務(wù)的調(diào)用跟蹤信息了。我們可以點(diǎn)擊其中的一個(gè)請(qǐng)求,可以看到如下界面:

該界面對(duì)本次請(qǐng)求進(jìn)行了更詳細(xì)的展現(xiàn)。同樣我們還可以再點(diǎn)擊,以查看更為詳細(xì)的數(shù)據(jù),可以看到如下界面:

在該界面中我們可以看到之前所講的各個(gè)時(shí)間跟蹤信息。
在Zipkin界面中我們還可以點(diǎn)擊[Dependencies]查看各服務(wù)之間的依賴關(guān)系,如下圖:

錯(cuò)誤信息
Zipkin可以在跟蹤記錄中顯示錯(cuò)誤信息。當(dāng)異常拋出并且沒有捕獲,Zipkin就會(huì)自動(dòng)的換個(gè)顏色顯示。在跟蹤記錄的清單中,當(dāng)看到紅色的記錄時(shí),就表示有異常拋出了。如上面圖中的第一個(gè)根據(jù)數(shù)據(jù)就顯示了錯(cuò)誤信息。我們還可以點(diǎn)擊進(jìn)去以獲取更詳細(xì)的錯(cuò)誤信息。
3.5 采樣率
在生成環(huán)境中,由于業(yè)務(wù)量比較大,所產(chǎn)生的跟蹤數(shù)據(jù)可能會(huì)非常大,如果全部采集一是對(duì)業(yè)務(wù)有一定影響,二是對(duì)存儲(chǔ)壓力也會(huì)比較大,所以采樣變的很重要。一般來(lái)說,我們也不需要把每一個(gè)發(fā)生的動(dòng)作都進(jìn)行記錄。
Spring Cloud Sleuth有一個(gè)Sampler策略,可以通過這個(gè)實(shí)現(xiàn)類來(lái)控制采樣算法。采樣器不會(huì)阻礙span相關(guān)id的產(chǎn)生,但是會(huì)對(duì)導(dǎo)出以及附加事件標(biāo)簽的相關(guān)操作造成影響。 Sleuth默認(rèn)采樣算法的實(shí)現(xiàn)是Reservoir sampling,具體的實(shí)現(xiàn)類是PercentageBasedSampler,默認(rèn)的采樣比例為: 0.1(即10%)。不過我們可以通過spring.sleuth.sampler.percentage來(lái)設(shè)置,所設(shè)置的值介于0.0到1.0之間,1.0則表示全部采集。
也可以通過實(shí)現(xiàn)bean的方式來(lái)設(shè)置采樣為全部采樣(AlwaysSampler)或者不采樣(NeverSampler):如
@Bean public Sampler defaultSampler() {
return new AlwaysSampler();
}
這也是為何之前我們需要修改
Mall-Web和Product-Service中的spring.sleuth.sampler.percentage配置,如果是默認(rèn)值很可能我們?cè)赯ipkin服務(wù)器上根本看不到。
你可以到這里下載本篇的代碼。
