隨著微服務(wù)架構(gòu)的流行,服務(wù)按照不同的維度進(jìn)行拆分,一次請(qǐng)求往往需要涉及到多個(gè)服務(wù)?;ヂ?lián)網(wǎng)應(yīng)用構(gòu)建在不同的軟件模塊集上,這些軟件模塊,有可能是由不同的團(tuán)隊(duì)開(kāi)發(fā)、可能使用不同的編程語(yǔ)言來(lái)實(shí)現(xiàn)、有可能布在了幾千臺(tái)服務(wù)器,橫跨多個(gè)不同的數(shù)據(jù)中心。因此,就需要一些可以幫助理解系統(tǒng)行為、用于分析性能問(wèn)題的工具,以便發(fā)生故障的時(shí)候,能夠快速定位和解決問(wèn)題。在復(fù)雜的微服務(wù)架構(gòu)系統(tǒng)中,幾乎每一個(gè)前端請(qǐng)求都會(huì)形成一個(gè)復(fù)雜的分布式服務(wù)調(diào)用鏈路。一個(gè)請(qǐng)求完整調(diào)用鏈可能如下圖所示:
隨著服務(wù)的越來(lái)越多,對(duì)調(diào)用鏈的分析會(huì)越來(lái)越復(fù)雜。它們之間的調(diào)用關(guān)系也許如下:
隨著業(yè)務(wù)規(guī)模不斷增大、服務(wù)不斷增多以及頻繁變更的情況下,面對(duì)復(fù)雜的調(diào)用鏈路就帶來(lái)一系列問(wèn)題:
- 如何快速發(fā)現(xiàn)問(wèn)題?
- 如何判斷故障影響范圍?
- 如何梳理服務(wù)依賴以及依賴的合理性?
- 如何分析鏈路性能問(wèn)題以及實(shí)時(shí)容量規(guī)劃?
而鏈路追蹤的出現(xiàn)正是為了解決這種問(wèn)題,它可以在復(fù)雜的服務(wù)調(diào)用中定位問(wèn)題,還可以在新人加入后臺(tái)團(tuán)隊(duì)之后,讓其清楚地知道自己所負(fù)責(zé)的服務(wù)在哪一環(huán)。
除此之外,如果某個(gè)接口突然耗時(shí)增加,也不必再逐個(gè)服務(wù)查詢耗時(shí)情況,我們可以直觀地分析出服務(wù)的性能瓶頸,方便在流量激增的情況下精準(zhǔn)合理地?cái)U(kuò)容。
什么是鏈路追蹤
“鏈路追蹤”一詞是在 2010 年提出的,當(dāng)時(shí)谷歌發(fā)布了一篇 Dapper 論文:Dapper,大規(guī)模分布式系統(tǒng)的跟蹤系統(tǒng),介紹了谷歌自研的分布式鏈路追蹤的實(shí)現(xiàn)原理,還介紹了他們是怎么低成本實(shí)現(xiàn)對(duì)應(yīng)用透明的。
單純的理解鏈路追蹤,就是指一次任務(wù)的開(kāi)始到結(jié)束,期間調(diào)用的所有系統(tǒng)及耗時(shí)(時(shí)間跨度)都可以完整記錄下來(lái)。
其實(shí) Dapper 一開(kāi)始只是一個(gè)獨(dú)立的調(diào)用鏈路追蹤系統(tǒng),后來(lái)逐漸演化成了監(jiān)控平臺(tái),并且基于監(jiān)控平臺(tái)孕育出了很多工具,比如實(shí)時(shí)預(yù)警、過(guò)載保護(hù)、指標(biāo)數(shù)據(jù)查詢等。
除了谷歌的 Dapper,還有一些其他比較有名的產(chǎn)品,比如阿里的鷹眼、大眾點(diǎn)評(píng)的 CAT、Twitter 的 Zipkin、Naver(著名社交軟件LINE的母公司)的 PinPoint 以及國(guó)產(chǎn)開(kāi)源的 SkyWalking(已貢獻(xiàn)給 Apache) 等。
什么是 Sleuth
Spring Cloud Sleuth 為 Spring Cloud 實(shí)現(xiàn)了分布式跟蹤解決方案。兼容 Zipkin,HTrace 和其他基于日志的追蹤系統(tǒng),例如 ELK(Elasticsearch 、Logstash、 Kibana)。
Spring Cloud Sleuth 提供了以下功能:
-
鏈路追蹤:通過(guò) Sleuth 可以很清楚的看出一個(gè)請(qǐng)求都經(jīng)過(guò)了那些服務(wù),可以很方便的理清服務(wù)間的調(diào)用關(guān)系等。 -
性能分析:通過(guò) Sleuth 可以很方便的看出每個(gè)采樣請(qǐng)求的耗時(shí),分析哪些服務(wù)調(diào)用比較耗時(shí),當(dāng)服務(wù)調(diào)用的耗時(shí)隨著請(qǐng)求量的增大而增大時(shí), 可以對(duì)服務(wù)的擴(kuò)容提供一定的提醒。 -
數(shù)據(jù)分析,優(yōu)化鏈路:對(duì)于頻繁調(diào)用一個(gè)服務(wù),或并行調(diào)用等,可以針對(duì)業(yè)務(wù)做一些優(yōu)化措施。 -
可視化錯(cuò)誤:對(duì)于程序未捕獲的異常,可以配合 Zipkin 查看。
專業(yè)術(shù)語(yǔ)
點(diǎn)擊鏈接觀看:Sleuth 專業(yè)術(shù)語(yǔ)視頻(獲取更多請(qǐng)關(guān)注公眾號(hào)「哈嘍沃德先生」)
Span
基本工作單位,一次單獨(dú)的調(diào)用鏈可以稱為一個(gè) Span,Dapper 記錄的是 Span 的名稱,以及每個(gè) Span 的 ID 和父 ID,以重建在一次追蹤過(guò)程中不同 Span 之間的關(guān)系,圖中一個(gè)矩形框就是一個(gè) Span,前端從發(fā)出請(qǐng)求到收到回復(fù)就是一個(gè) Span。
開(kāi)始跟蹤的初始跨度稱為
root span。該跨度的 ID 的值等于跟蹤 ID。
Dapper 記錄了 span 名稱,以及每個(gè) span 的 ID 和父 span ID,以重建在一次追蹤過(guò)程中不同 span 之間的關(guān)系。如果一個(gè) span 沒(méi)有父 ID 被稱為 root span。所有 span 都掛在一個(gè)特定的 Trace 上,也共用一個(gè) trace id。
Trace
一系列 Span 組成的樹(shù)狀結(jié)構(gòu),一個(gè) Trace 認(rèn)為是一次完整的鏈路,內(nèi)部包含 n 多個(gè) Span。Trace 和 Span 存在一對(duì)多的關(guān)系,Span 與 Span 之間存在父子關(guān)系。
舉個(gè)例子:客戶端調(diào)用服務(wù) A 、服務(wù) B 、服務(wù) C 、服務(wù) F,而每個(gè)服務(wù)例如 C 就是一個(gè) Span,如果在服務(wù) C 中另起線程調(diào)用了 D,那么 D 就是 C 的子 Span,如果在服務(wù) D 中另起線程調(diào)用了 E,那么 E 就是 D 的子 Span,這個(gè) C -> D -> E 的鏈路就是一條 Trace。如果鏈路追蹤系統(tǒng)做好了,鏈路數(shù)據(jù)有了,借助前端解析和渲染工具,可以達(dá)到下圖中的效果:
Annotation
用來(lái)及時(shí)記錄一個(gè)事件的存在,一些核心 annotations 用來(lái)定義一個(gè)請(qǐng)求的開(kāi)始和結(jié)束。
- cs - Client Sent:客戶端發(fā)起一個(gè)請(qǐng)求,這個(gè) annotation 描述了這個(gè) span 的開(kāi)始;
- sr - Server Received:服務(wù)端獲得請(qǐng)求并準(zhǔn)備開(kāi)始處理它,如果 sr 減去 cs 時(shí)間戳便可得到網(wǎng)絡(luò)延遲;
- ss - Server Sent:請(qǐng)求處理完成(當(dāng)請(qǐng)求返回客戶端),如果 ss 減去 sr 時(shí)間戳便可得到服務(wù)端處理請(qǐng)求需要的時(shí)間;
- cr - Client Received:表示 span 結(jié)束,客戶端成功接收到服務(wù)端的回復(fù),如果 cr 減去 cs 時(shí)間戳便可得到客戶端從服務(wù)端獲取回復(fù)的所有所需時(shí)間。
實(shí)現(xiàn)原理
首先感謝張以諾制作的實(shí)現(xiàn)原理圖。
如果想知道一個(gè)接口在哪個(gè)環(huán)節(jié)出現(xiàn)了問(wèn)題,就必須清楚該接口調(diào)用了哪些服務(wù),以及調(diào)用的順序,如果把這些服務(wù)串起來(lái),看起來(lái)就像鏈條一樣,我們稱其為調(diào)用鏈。
想要實(shí)現(xiàn)調(diào)用鏈,就要為每次調(diào)用做個(gè)標(biāo)識(shí),然后將服務(wù)按標(biāo)識(shí)大小排列,可以更清晰地看出調(diào)用順序,我們暫且將該標(biāo)識(shí)命名為 spanid。
實(shí)際場(chǎng)景中,我們需要知道某次請(qǐng)求調(diào)用的情況,所以只有 spanid 還不夠,得為每次請(qǐng)求做個(gè)唯一標(biāo)識(shí),這樣才能根據(jù)標(biāo)識(shí)查出本次請(qǐng)求調(diào)用的所有服務(wù),而這個(gè)標(biāo)識(shí)我們命名為 traceid。
現(xiàn)在根據(jù) spanid 可以輕易地知道被調(diào)用服務(wù)的先后順序,但無(wú)法體現(xiàn)調(diào)用的層級(jí)關(guān)系,正如下圖所示,多個(gè)服務(wù)可能是逐級(jí)調(diào)用的鏈條,也可能是同時(shí)被同一個(gè)服務(wù)調(diào)用。
所以應(yīng)該每次都記錄下是誰(shuí)調(diào)用的,我們用 parentid 作為這個(gè)標(biāo)識(shí)的名字。
到現(xiàn)在,已經(jīng)知道調(diào)用順序和層級(jí)關(guān)系了,但是接口出現(xiàn)問(wèn)題后,還是不能找到出問(wèn)題的環(huán)節(jié),如果某個(gè)服務(wù)有問(wèn)題,那個(gè)被調(diào)用執(zhí)行的服務(wù)一定耗時(shí)很長(zhǎng),要想計(jì)算出耗時(shí),上述的三個(gè)標(biāo)識(shí)還不夠,還需要加上時(shí)間戳,時(shí)間戳可以更精細(xì)一點(diǎn),精確到微秒級(jí)。
只記錄發(fā)起調(diào)用時(shí)的時(shí)間戳還算不出耗時(shí),要記錄下服務(wù)返回時(shí)的時(shí)間戳,有始有終才能算出時(shí)間差,既然返回的也記了,就把上述的三個(gè)標(biāo)識(shí)都記一下吧,不然區(qū)分不出是誰(shuí)的時(shí)間戳。
雖然能計(jì)算出從服務(wù)調(diào)用到服務(wù)返回的總耗時(shí),但是這個(gè)時(shí)間包含了服務(wù)的執(zhí)行時(shí)間和網(wǎng)絡(luò)延遲,有時(shí)候我們需要區(qū)分出這兩類時(shí)間以方便做針對(duì)性優(yōu)化。那如何計(jì)算網(wǎng)絡(luò)延遲呢?我們可以把調(diào)用和返回的過(guò)程分為以下四個(gè)事件。
- Client Sent 簡(jiǎn)稱 cs,客戶端發(fā)起調(diào)用請(qǐng)求到服務(wù)端。
- Server Received 簡(jiǎn)稱 sr,指服務(wù)端接收到了客戶端的調(diào)用請(qǐng)求。
- Server Sent 簡(jiǎn)稱 ss,指服務(wù)端完成了處理,準(zhǔn)備將信息返給客戶端。
- Client Received 簡(jiǎn)稱 cr,指客戶端接收到了服務(wù)端的返回信息。
假如在這四個(gè)事件發(fā)生時(shí)記錄下時(shí)間戳,就可以輕松計(jì)算出耗時(shí),比如 sr 減去 cs 就是調(diào)用時(shí)的網(wǎng)絡(luò)延遲,ss 減去 sr 就是服務(wù)執(zhí)行時(shí)間,cr 減去 ss 就是服務(wù)響應(yīng)的延遲,cr 減 cs 就是整個(gè)服務(wù)調(diào)用執(zhí)行的時(shí)間。
其實(shí) span 內(nèi)除了記錄這幾個(gè)參數(shù)之外,還可以記錄一些其他信息,比如發(fā)起調(diào)用服務(wù)名稱、被調(diào)服務(wù)名稱、返回結(jié)果、IP、調(diào)用服務(wù)的名稱等,最后,我們?cè)侔严嗤?parentid 的 span 信息合成一個(gè)大的 span 塊,就完成了一個(gè)完整的調(diào)用鏈。
環(huán)境準(zhǔn)備
sleuth-demo 聚合工程。SpringBoot 2.2.4.RELEASE、Spring Cloud Hoxton.SR1。
-
eureka-server:注冊(cè)中心 -
eureka-server02:注冊(cè)中心 -
gateway-server:Spring Cloud Gateway 服務(wù)網(wǎng)關(guān) -
product-service:商品服務(wù),提供了根據(jù)主鍵查詢商品接口http://localhost:7070/product/{id}根據(jù)多個(gè)主鍵查詢商品接口http://localhost:7070/product/listByIds -
order-service:訂單服務(wù),提供了根據(jù)主鍵查詢訂單接口http://localhost:9090/order/{id}且訂單服務(wù)調(diào)用商品服務(wù)。
入門案例
點(diǎn)擊鏈接觀看:Sleuth 入門案例視頻(獲取更多請(qǐng)關(guān)注公眾號(hào)「哈嘍沃德先生」)
添加依賴
在需要進(jìn)行鏈路追蹤的項(xiàng)目中(服務(wù)網(wǎng)關(guān)、商品服務(wù)、訂單服務(wù))添加 spring-cloud-starter-sleuth 依賴。
<!-- spring cloud sleuth 依賴 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
記錄日志
在需要鏈路追蹤的項(xiàng)目中添加 logback.xml 日志文件,內(nèi)容如下(logback 日志的輸出級(jí)別需要是 DEBUG 級(jí)別):
注意修改 <property name="log.path" value="${catalina.base}/gateway-server/logs"/> 中項(xiàng)目名稱。
日志核心配置:%d{yyyy-MM-dd HH:mm:ss.SSS} [${applicationName},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-}] [%thread] %-5level %logger{50} - %msg%n
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志級(jí)別從低到高分為TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果設(shè)置為WARN,則低于WARN的信息都不會(huì)輸出 -->
<!-- scan: 當(dāng)此屬性設(shè)置為true時(shí),配置文件如果發(fā)生改變,將會(huì)被重新加載,默認(rèn)值為true -->
<!-- scanPeriod: 設(shè)置監(jiān)測(cè)配置文件是否有修改的時(shí)間間隔,如果沒(méi)有給出時(shí)間單位,默認(rèn)單位是毫秒。當(dāng)scan為true時(shí),此屬性生效。默認(rèn)的時(shí)間間隔為1分鐘。 -->
<!-- debug: 當(dāng)此屬性設(shè)置為true時(shí),將打印出logback內(nèi)部日志信息,實(shí)時(shí)查看logback運(yùn)行狀態(tài)。默認(rèn)值為false。 -->
<configuration scan="true" scanPeriod="10 seconds">
<!-- 日志上下文名稱 -->
<contextName>my_logback</contextName>
<!-- name的值是變量的名稱,value的值是變量定義的值。通過(guò)定義的值會(huì)被插入到logger上下文中。定義變量后,可以使“${}”來(lái)使用變量。 -->
<property name="log.path" value="${catalina.base}/gateway-server/logs"/>
<!-- 加載 Spring 配置文件信息 -->
<springProperty scope="context" name="applicationName" source="spring.application.name" defaultValue="localhost"/>
<!-- 日志輸出格式 -->
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [${applicationName},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-}] [%thread] %-5level %logger{50} - %msg%n"/>
<!--輸出到控制臺(tái)-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--此日志appender是為開(kāi)發(fā)使用,只配置最底級(jí)別,控制臺(tái)輸出的日志級(jí)別是大于或等于此級(jí)別的日志信息-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<!-- 設(shè)置字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 輸出到文件 -->
<!-- 時(shí)間滾動(dòng)輸出 level為 DEBUG 日志 -->
<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在記錄的日志文件的路徑及文件名 -->
<file>${log.path}/log_debug.log</file>
<!--日志文件輸出格式-->
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset> <!-- 設(shè)置字符集 -->
</encoder>
<!-- 日志記錄器的滾動(dòng)策略,按日期,按大小記錄 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志歸檔 -->
<fileNamePattern>${log.path}/debug/log-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天數(shù)-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只記錄debug級(jí)別的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 時(shí)間滾動(dòng)輸出 level為 INFO 日志 -->
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在記錄的日志文件的路徑及文件名 -->
<file>${log.path}/log_info.log</file>
<!--日志文件輸出格式-->
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 日志記錄器的滾動(dòng)策略,按日期,按大小記錄 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天日志歸檔路徑以及格式 -->
<fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天數(shù)-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只記錄info級(jí)別的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 時(shí)間滾動(dòng)輸出 level為 WARN 日志 -->
<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在記錄的日志文件的路徑及文件名 -->
<file>${log.path}/log_warn.log</file>
<!--日志文件輸出格式-->
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset> <!-- 此處設(shè)置字符集 -->
</encoder>
<!-- 日志記錄器的滾動(dòng)策略,按日期,按大小記錄 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 每個(gè)日志文件最大100MB -->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天數(shù)-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只記錄warn級(jí)別的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 時(shí)間滾動(dòng)輸出 level為 ERROR 日志 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在記錄的日志文件的路徑及文件名 -->
<file>${log.path}/log_error.log</file>
<!--日志文件輸出格式-->
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset> <!-- 此處設(shè)置字符集 -->
</encoder>
<!-- 日志記錄器的滾動(dòng)策略,按日期,按大小記錄 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天數(shù)-->
<maxHistory>15</maxHistory>
<!-- 日志量最大 10 GB -->
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
<!-- 此日志文件只記錄ERROR級(jí)別的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 對(duì)于類路徑以 com.example.logback 開(kāi)頭的Logger,輸出級(jí)別設(shè)置為warn,并且只輸出到控制臺(tái) -->
<!-- 這個(gè)logger沒(méi)有指定appender,它會(huì)繼承root節(jié)點(diǎn)中定義的那些appender -->
<!-- <logger name="com.example.logback" level="warn"/> -->
<!--通過(guò) LoggerFactory.getLogger("myLog") 可以獲取到這個(gè)logger-->
<!--由于這個(gè)logger自動(dòng)繼承了root的appender,root中已經(jīng)有stdout的appender了,自己這邊又引入了stdout的appender-->
<!--如果沒(méi)有設(shè)置 additivity="false" ,就會(huì)導(dǎo)致一條日志在控制臺(tái)輸出兩次的情況-->
<!--additivity表示要不要使用rootLogger配置的appender進(jìn)行輸出-->
<logger name="myLog" level="INFO" additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
<!-- 日志輸出級(jí)別及方式 -->
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="DEBUG_FILE"/>
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="WARN_FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
</configuration>
訪問(wèn)
訪問(wèn):http://localhost:9000/order-service/order/1 ,結(jié)果如下:
服務(wù)網(wǎng)關(guān)打印信息:
[gateway-server,95aa725089b757f8,95aa725089b757f8]
商品服務(wù)打印信息
[product-service,95aa725089b757f8,e494e064842ce4e8]
訂單服務(wù)打印信息
[order-service,95aa725089b757f8,f4ee41a6dcf08717]
通過(guò)打印信息可以得知,整個(gè)鏈路的 traceId 為:95aa725089b757f8,spanId 為:e494e064842ce4e8 和 f4ee41a6dcf08717。
查看日志文件并不是一個(gè)很好的方法,當(dāng)微服務(wù)越來(lái)越多日志文件也會(huì)越來(lái)越多,查詢工作會(huì)變得越來(lái)越麻煩,Spring 官方推薦使用 Zipkin 進(jìn)行鏈路跟蹤。Zipkin 可以將日志聚合,并進(jìn)行可視化展示和全文檢索。
使用 Zipkin 進(jìn)行鏈路跟蹤
什么是 Zipkin
Zipkin 是 Twitter 公司開(kāi)發(fā)貢獻(xiàn)的一款開(kāi)源的分布式實(shí)時(shí)數(shù)據(jù)追蹤系統(tǒng)(Distributed Tracking System),基于 Google Dapper 的論文設(shè)計(jì)而來(lái),其主要功能是聚集各個(gè)異構(gòu)系統(tǒng)的實(shí)時(shí)監(jiān)控?cái)?shù)據(jù)。
它可以收集各個(gè)服務(wù)器上請(qǐng)求鏈路的跟蹤數(shù)據(jù),并通過(guò) Rest API 接口來(lái)輔助我們查詢跟蹤數(shù)據(jù),實(shí)現(xiàn)對(duì)分布式系統(tǒng)的實(shí)時(shí)監(jiān)控,及時(shí)發(fā)現(xiàn)系統(tǒng)中出現(xiàn)的延遲升高問(wèn)題并找出系統(tǒng)性能瓶頸的根源。除了面向開(kāi)發(fā)的 API 接口之外,它還提供了方便的 UI 組件,每個(gè)服務(wù)向 Zipkin 報(bào)告計(jì)時(shí)數(shù)據(jù),Zipkin 會(huì)根據(jù)調(diào)用關(guān)系生成依賴關(guān)系圖,幫助我們直觀的搜索跟蹤信息和分析請(qǐng)求鏈路明細(xì)。Zipkin 提供了可插拔數(shù)據(jù)存儲(chǔ)方式:In-Memory、MySql、Cassandra 以及 Elasticsearch。
分布式跟蹤系統(tǒng)還有其他比較成熟的實(shí)現(xiàn),例如:Naver 的 PinPoint、Apache 的 HTrace、阿里的鷹眼 Tracing、京東的 Hydra、新浪的 Watchman,美團(tuán)點(diǎn)評(píng)的 CAT,Apache 的 SkyWalking 等。
工作原理
共有四個(gè)組件構(gòu)成了 Zipkin:
-
Collector:收集器組件,處理從外部系統(tǒng)發(fā)送過(guò)來(lái)的跟蹤信息,將這些信息轉(zhuǎn)換為 Zipkin 內(nèi)部處理的 Span 格式,以支持后續(xù)的存儲(chǔ)、分析、展示等功能。 -
Storage:存儲(chǔ)組件,處理收集器接收到的跟蹤信息,默認(rèn)將信息存儲(chǔ)在內(nèi)存中,可以修改存儲(chǔ)策略使用其他存儲(chǔ)組件,支持 MySQL,Elasticsearch 等。 -
Web UI:UI 組件,基于 API 組件實(shí)現(xiàn)的上層應(yīng)用,提供 Web 頁(yè)面,用來(lái)展示 Zipkin 中的調(diào)用鏈和系統(tǒng)依賴關(guān)系等。 -
RESTful API:API 組件,為 Web 界面提供查詢存儲(chǔ)中數(shù)據(jù)的接口。
Zipkin 分為兩端,一個(gè)是 Zipkin 服務(wù)端,一個(gè)是 Zipkin 客戶端,客戶端也就是微服務(wù)的應(yīng)用,客戶端會(huì)配置服務(wù)端的 URL 地址,一旦發(fā)生服務(wù)間的調(diào)用的時(shí)候,會(huì)被配置在微服務(wù)里面的 Sleuth 的監(jiān)聽(tīng)器監(jiān)聽(tīng),并生成相應(yīng)的 Trace 和 Span 信息發(fā)送給服務(wù)端。發(fā)送的方式有兩種,一種是消息總線的方式如 RabbitMQ 發(fā)送,還有一種是 HTTP 報(bào)文的方式發(fā)送。
服務(wù)端部署
服務(wù)端是一個(gè)獨(dú)立的可執(zhí)行的 jar 包,官方下載地址:https://search.maven.org/remote_content?g=io.zipkin&a=zipkin-server&v=LATEST&c=exec,使用 java -jar zipkin.jar 命令啟動(dòng),端口默認(rèn)為 9411。我們下載的 jar 包為:zipkin-server-2.20.1-exec.jar,啟動(dòng)命令如下:
java -jar zipkin-server-2.20.1-exec.jar
訪問(wèn):http://localhost:9411/ 結(jié)果如下:
目前最新版界面。
之前舊版本界面。
客戶端部署
點(diǎn)擊鏈接觀看:Zipkin 客戶端部署視頻(獲取更多請(qǐng)關(guān)注公眾號(hào)「哈嘍沃德先生」)
添加依賴
在需要進(jìn)行鏈路追蹤的項(xiàng)目中(服務(wù)網(wǎng)關(guān)、商品服務(wù)、訂單服務(wù))添加 spring-cloud-starter-zipkin 依賴。
<!-- spring cloud zipkin 依賴 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
配置文件
在需要進(jìn)行鏈路追蹤的項(xiàng)目中(服務(wù)網(wǎng)關(guān)、商品服務(wù)、訂單服務(wù))配置 Zipkin 服務(wù)端地址及數(shù)據(jù)傳輸方式。默認(rèn)即如下配置。
spring:
zipkin:
base-url: http://localhost:9411/ # 服務(wù)端地址
sender:
type: web # 數(shù)據(jù)傳輸方式,web 表示以 HTTP 報(bào)文的形式向服務(wù)端發(fā)送數(shù)據(jù)
sleuth:
sampler:
probability: 1.0 # 收集數(shù)據(jù)百分比,默認(rèn) 0.1(10%)
訪問(wèn)
訪問(wèn):http://localhost:9000/order-service/order/1 結(jié)果如下:
新版操作如下:
訪問(wèn):http://localhost:9411/ 根據(jù)時(shí)間過(guò)濾點(diǎn)擊搜索結(jié)果如下:
點(diǎn)擊對(duì)應(yīng)的追蹤信息可查看請(qǐng)求鏈路詳細(xì)。
通過(guò)依賴可以查看鏈路中服務(wù)的依賴關(guān)系。
舊版操作如下:
訪問(wèn):http://localhost:9411/ 點(diǎn)擊查找結(jié)果如下:
點(diǎn)擊對(duì)應(yīng)的追蹤信息可查看請(qǐng)求鏈路詳細(xì)。
通過(guò)依賴可以查看鏈路中服務(wù)的依賴關(guān)系。
Zipkin Server 默認(rèn)存儲(chǔ)追蹤數(shù)據(jù)至內(nèi)存中,這種方式并不適合生產(chǎn)環(huán)境,一旦 Server 關(guān)閉重啟或者服務(wù)崩潰,就會(huì)導(dǎo)致歷史數(shù)據(jù)消失。Zipkin 支持修改存儲(chǔ)策略使用其他存儲(chǔ)組件,支持 MySQL,Elasticsearch 等。
下一篇我們講解 Sleuth 基于 Zipkin 存儲(chǔ)鏈路追蹤數(shù)據(jù)至 MySQL,Elasticsearch 以及使用 MQ 存儲(chǔ)鏈路追蹤數(shù)據(jù)至 MySQL,Elasticsearch,記得關(guān)注噢~
本文采用 知識(shí)共享「署名-非商業(yè)性使用-禁止演繹 4.0 國(guó)際」許可協(xié)議。
大家可以通過(guò) 分類 查看更多關(guān)于 Spring Cloud 的文章。
?? 您的點(diǎn)贊和轉(zhuǎn)發(fā)是對(duì)我最大的支持。
?? 關(guān)注公眾號(hào) 哈嘍沃德先生「文檔 + 視頻」每篇文章都配有專門視頻講解,學(xué)習(xí)更輕松噢 ~