服务化改造实践(三) | Dubbo + Zipkin
隨著業務的發展,應用的規模不斷的擴大,傳統的應用架構無法滿足訴求,服務化架構改造勢在必行,以 Dubbo 為代表的分布式服務框架成為了服務化改造架構中的基石。隨著微服務理念逐漸被大眾接受,應用進一步向更細粒度拆分,并且,不同的應用由不同的開發團隊獨立負責,整個分布式系統變得十分復雜。沒有人能夠清晰及時的知道當前系統整體的依賴關系。當出現問題時,也無法及時知道具體是鏈路上的哪個環節出了問題。
在這個背景下,Google 發表了?Dapper?的論文,描述了如何通過一個分布式追蹤系統解決上述問題。基于該論文,各大互聯網公司實現并部署了自己的分布式追蹤系統,其中比較出名的有阿里巴巴的 EagleEye。本文中提到的 Zipkin 是 Twitter 公司開源的分布式追蹤系統。下面會詳細介紹如何在 Dubbo 中使用 Zipkin 來實現分布式追蹤。
Zipkin 簡介
Zipkin 是基于?Dapper?論文實現,由 Twitter 開源的分布式追蹤系統,通過收集分布式服務執行時間的信息來達到追蹤服務調用鏈路、以及分析服務執行延遲等目的。
Zipkin 架構
?
Collector 收集器、Storage 存儲、API、UI 用戶界面等幾部分構成了 Zipkin Server 部分,對應于 GitHub 上?openzipkin/zipkin?這個項目。而收集應用中調用的耗時信息并將其上報的組件與應用共生,并擁有各個語言的實現版本,其中 Java 的實現是 GitHub 上?openzipkin/brave。除了 Java 客戶端實現之外,openzipkin 還提供了許多其他語言的實現,其中包括了 go、php、JavaScript、.net、ruby 等,具體列表可以參閱 Zipkin 的?Exiting instrumentations。
Zipkin 的工作過程
當用戶發起一次調用時,Zipkin 的客戶端會在入口處為整條調用鏈路生成一個全局唯一的 trace id,并為這條鏈路中的每一次分布式調用生成一個 span id。span 與 span 之間可以有父子嵌套關系,代表分布式調用中的上下游關系。span 和 span 之間可以是兄弟關系,代表當前調用下的兩次子調用。一個 trace 由一組 span 組成,可以看成是由 trace 為根節點,span 為若干個子節點的一棵樹。
?
Span 由調用邊界來分隔,在 Zipkin 中,調用邊界由以下四個 annotation 來表示:
- cs - Clent Sent 客戶端發送了請求
- sr - Server Receive 服務端接受到請求
- ss - Server Send 服務端處理完畢,向客戶端發送回應
- cr - Client Receive 客戶端收到結果
顯然,通過這四個 annotation 上的時間戳,可以輕易的知道一次完整的調用在不同階段的耗時,比如:
- sr - cs 代表了請求在網絡上的耗時
- ss - sr 代表了服務端處理請求的耗時
- cr - ss 代表了回應在網絡上的耗時
- cr - cs 代表了一次調用的整體耗時
Zipkin 會將 trace 相關的信息在調用鏈路上傳遞,并在每個調用邊界結束時異步的把當前調用的耗時信息上報給 Zipkin Server。Zipkin Server 在收到 trace 信息后,將其存儲起來,Zipkin 支持的存儲類型有 inMemory、MySql、Cassandra、以及 ElasticsSearch 幾種方式。隨后 Zipkin 的 Web UI 會通過 API 訪問的方式從存儲中將 trace 信息提取出來分析并展示,如下圖所示:
?
在 Dubbo 中使用
由于?Brave?對 Dubbo 已經主動做了支持,在 Dubbo 中集成基于 Zipkin 的鏈路追蹤變的十分簡單。下面會按照 Brave 中關于?Dubbo RPC 支持的指引來說明如何在 Dubbo 中使用 Zipkin。
安裝 Zipkin Server
按照?Zipkin 官方文檔中的快速開始?來安裝 Zipkin,如下所示:
$ curl -sSL https://zipkin.io/quickstart.sh | bash -s $ java -jar zipkin.jar按照這種方式安裝的 Zipkin Server 使用的存儲類型是 inMemory 的。當服務器停機之后,所有收集到的 trace 信息會丟失,不適用于生產系統。如果在生產系統中使用,需要配置另外的存儲類型。Zipkin 支持 MySql、Cassandra、和 ElasticSearch。推薦使用 Cassandra 和 ElasticSearch,相關的配置請自行查閱官方文檔。
本文為了演示方便,使用的存儲是 inMemory 類型。成功啟動之后,可以在終端看到如下的提示:
$ java -jar zipkin.jar Picked up JAVA_TOOL_OPTIONS: -Djava.awt.headless=true********** *** *** **** **** **** ********************** ********** **** ************************************************************************************** **** ******* ************* ** ***** ** ** ** ** **** ** ** * *** ** **** **** ** ***** **** ** ** ********* ** ** ** ** ** ** **:: Powered by Spring Boot :: (v2.0.5.RELEASE)...o.s.b.w.e.u.UndertowServletWebServer : Undertow started on port(s) 9411 (http) with context path '' 2018-10-10 18:40:31.605 INFO 21072 --- [ main] z.s.ZipkinServer : Started ZipkinServer in 6.835 seconds (JVM running for 8.35)然后在瀏覽器中訪問?http://localhost:9411?驗證 WEB 界面。
配置 Maven 依賴
引入 Brave 依賴
新建一個新的 Java 工程,并在 pom.xml 中引入 Brave 相關的依賴如下:
<properties><brave.version>5.4.2</brave.version><zipkin-reporter.version>2.7.9</zipkin-reporter.version></properties><dependencyManagement><dependencies><!-- 引入 zipkin brave 的 BOM 文件 --><dependency><groupId>io.zipkin.brave</groupId><artifactId>brave-bom</artifactId><version>${brave.version}</version><type>pom</type><scope>import</scope></dependency><!-- 引入 zipkin repoter 的 BOM 文件 --><dependency><groupId>io.zipkin.reporter2</groupId><artifactId>zipkin-reporter-bom</artifactId><version>${zipkin-reporter.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><!-- 1. brave 對 dubbo 的集成 --><dependency><groupId>io.zipkin.brave</groupId><artifactId>brave-instrumentation-dubbo-rpc</artifactId></dependency><!-- 2. brave 的 spring bean 支持 --><dependency><groupId>io.zipkin.brave</groupId><artifactId>brave-spring-beans</artifactId></dependency><!-- 3. 在 SLF4J 的 MDC (Mapped Diagnostic Context) 中支持 traceId 和 spanId --><dependency><groupId>io.zipkin.brave</groupId><artifactId>brave-context-slf4j</artifactId></dependency><!-- 4. 使用 okhttp3 作為 reporter --><dependency><groupId>io.zipkin.reporter2</groupId><artifactId>zipkin-sender-okhttp3</artifactId></dependency></dependencies>其中:
引入 Dubbo 相關依賴
Dubbo 相關的依賴是 Dubbo 本身以及 Zookeeper 客戶端,在下面的例子中,我們將會使用獨立的 Zookeeper Server 作為服務發現。
<dependencies><!-- 1. Zookeeper 客戶端依賴 --><dependency><groupId>org.apache.curator</groupId><artifactId>curator-framework</artifactId><version>2.12.0</version><exclusions><exclusion><groupId>io.netty</groupId><artifactId>netty</artifactId></exclusion></exclusions></dependency><!-- 2. Dubbo 依賴 --><dependency><groupId>com.alibaba</groupId><artifactId>dubbo</artifactId><version>2.6.2</version></dependency></dependencies>其中:
實現
我們這里構造的場景是一個有兩個節點的服務依賴鏈,也就是,當一個 Dubbo 客戶端調用服務 A 時,服務 A 將會繼續調用服務 B。在這個例子中,服務 A 是 greeting service,它所依賴的下游服務服務 B 是 hello service。
定義服務接口
為此需要事先定義兩個服務接口 GreetingService 以及 HelloService
com.alibaba.dubbo.samples.api.GreetingService
package com.alibaba.dubbo.samples.api;public interface GreetingService {String greeting(String message); }com.alibaba.dubbo.samples.api.HelloService
package com.alibaba.dubbo.samples.api;public interface HelloService {String hello(String message); }實現服務接口
為了區分對待,所有和 HelloService 相關的實現代碼都放在 hello 子包下,同理 GreetingService 相關的放在 greeting 子包下。
實現 com.alibaba.dubbo.samples.api.HelloService
package com.alibaba.dubbo.samples.service.hello;import com.alibaba.dubbo.samples.api.HelloService;import java.util.Random;public class HelloServiceImpl implements HelloService {@Overridepublic String hello(String message) {try {// 通過 sleep 模擬業務邏輯處理時間Thread.sleep(new Random(System.currentTimeMillis()).nextInt(1000));} catch (InterruptedException e) {// no op}return "hello, " + message;} }實現 com.alibaba.dubbo.samples.api.GreetingService
package com.alibaba.dubbo.samples.service.greeting;import com.alibaba.dubbo.samples.api.GreetingService; import com.alibaba.dubbo.samples.api.HelloService;import java.util.Random;public class GreetingServiceImpl implements GreetingService {// 下游依賴服務,運行時靠 spring 容器注入 HelloService 的服務代理private HelloService helloService;public void setHelloService(HelloService helloService) {this.helloService = helloService;}@Overridepublic String greeting(String message) {try {// 通過 sleep 模擬業務邏輯處理時間Thread.sleep(new Random(System.currentTimeMillis()).nextInt(1000));} catch (InterruptedException e) {// no op}return "greeting, " + helloService.hello(message);} }這里需要注意的是,GreetingServiceImpl 的實現中聲明了一個類型是 HelloService 的成員變量,并在 greeting 方法中,執行完自己邏輯之后又調用了 HelloService 上的 hello 方法。這里的 helloService 的實現將會在運行態由外部注入,注入的不是 HelloServiceImpl 的實現,而是 HelloService 的遠程調用代理。通過這樣的方式,完成了在一個 Dubbo 服務中繼續調用另一個遠程 Dubbo 服務的目的。從鏈路追蹤的角度來說,客戶端調用 GreetingService 是一個 span,GreetingService 調用 HelloService 是另一個 span,并且兩者有父子關系,同屬于一個 trace,也就是屬于同一條調用鏈路。
另外,在 GreetingServiceImpl 和 HelloServiceImpl 的實現中,通過 Thread.sleep 來模擬了處理業務邏輯的耗時,以便在 Zipkin UI 上更好的展示。
配置
為了專注在展示如何使用 Zipkin 這一點上,本文在配置和編程模型上沒有采用更多的高級技術,而是使用了最傳統的 Spring XML 的配置方式,幫助讀者理解。更高級的通過 annotation 甚至 spring boot 的方式,讀者可以自行查閱 Dubbo 和 Zipkin 相關的文檔。
暴露 HelloService 服務
在 resouces/spring/hello-service.xml 中增加以下的配置來將 HelloServiceImpl 暴露成一個 Dubbo 服務:
- 使用了本地啟動的 Zookeeper Server 作為注冊中心,地址為默認值 zookeeper://127.0.0.1:2181
- 用 Dubbo 原生服務在端口 20880 上暴露服務
- 將 HelloServiceImpl 注冊成 id 是?helloService?的 Spring Bean,這樣就可以在后續的?<dubbo:service>?中引用到這個實現類
- 通過?<dubbo:service>?將 HelloServiceImpl 暴露成 Dubbo 服務
增加 Zipkin 相關的配置
在 resources/spring/hello-service.xml 中增加 Zipkin 相關的配置:
- 修改 dubbo 服務暴露的配置,添加 Zipkin 的 tracing filter 到 Dubbo 的 filter chain 中
- 按照?https://github.com/openzipkin/brave/blob/master/spring-beans/README.md?來配置 Zipkin 的 sender 和 tracing 的 spring bean
增加 HelloService 的啟動類
在 com.alibaba.dubbo.samples.service.hello.Application 中通過 ClassPathXmlApplicationContext 讀取 剛才配置的 spring/hello-service.xml 來初始化一個 spring context 并啟動
package com.alibaba.dubbo.samples.service.hello;import org.springframework.context.support.ClassPathXmlApplicationContext;import java.io.IOException;public class Application {public static void main(String[] args) throws IOException {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/hello-service.xml");context.start();System.out.println("Hello service started");// press any key to exitSystem.in.read();} }暴露 GreetingService 服務,并使用 Zipkin
在 resources/spring/greeting-service.xml 中配置 GreetingService。相關步驟與 HelloService 類似,不再贅述,重點關注如何在 GreetingService 中配置下游服務的依賴。完整的 XML 配置如下:
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"xmlns="http://www.springframework.org/schema/beans"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"><!-- 1. 定義 GreetingService 的應用名 --><dubbo:application name="greeting-service-provider"/><!-- 2. 指定注冊中心地址 --><dubbo:registry address="zookeeper://127.0.0.1:2181"/><!-- 3. 使用 Dubbo 原生協議在 20881 端口上暴露服務 --><dubbo:protocol name="dubbo" port="20881"/><!-- 4. 聲明 HelloService 的遠程代理,并在 Dubbo 的 filter chain 中增加 tracing filter --><dubbo:reference id="helloService" check="false" interface="com.alibaba.dubbo.samples.api.HelloService" filter="tracing"/><!-- 5. 將 GreetingServiceImpl 的實現聲明成一個 spring bean,并將 HelloService 的遠程代理裝配進去 --><bean id="greetingService" class="com.alibaba.dubbo.samples.service.greeting.GreetingServiceImpl"><property name="helloService" ref="helloService"/></bean><!-- 6. 將 GreetingServiceImpl 聲明成一個 Dubbo 服務,并在 Dubbo 的 filter chain 中增加 tracing filter --><dubbo:service interface="com.alibaba.dubbo.samples.api.GreetingService" ref="greetingService" filter="tracing"/><!-- 7. zipkin 相關的配置 --><bean id="sender" class="zipkin2.reporter.beans.OkHttpSenderFactoryBean"><property name="endpoint" value="http://localhost:9411/api/v2/spans"/></bean><bean id="tracing" class="brave.spring.beans.TracingFactoryBean"><property name="localServiceName" value="greeting-service"/><property name="spanReporter"><bean class="zipkin2.reporter.beans.AsyncReporterFactoryBean"><property name="sender" ref="sender"/><!-- wait up to half a second for any in-flight spans on close --><property name="closeTimeout" value="500"/></bean></property><property name="currentTraceContext"><bean class="brave.spring.beans.CurrentTraceContextFactoryBean"><property name="scopeDecorators"><bean class="brave.context.slf4j.MDCScopeDecorator" factory-method="create"/></property></bean></property></bean> </beans>這里的配置與上面的 HelloService 類似,需要重點關注的有兩點:
- 第 3 步中注意服務需要暴露在不同的端口上,否則會和 HelloService 沖突,本例中選擇的是 20881 這個端口
- 通過第 4 步先聲明 HelloService 的遠程代理,然后在第 5 步中將其組裝給 GreetingService 來完成服務上下游依賴的聲明
增加 GreeeingService 的啟動類,與 HelloService 類似,通過 spring/greeting-service.xml 的配置來初始化一個新的 spring context 來完成。
實現客戶端
通過 resources/spring/client.xml 初始化一個 spring context,從其中獲取 GreetingService 的遠程代理,發起遠程調用。
package com.alibaba.dubbo.samples.client;import com.alibaba.dubbo.samples.api.GreetingService; import org.springframework.context.support.ClassPathXmlApplicationContext;public class Application {public static void main(String[] args) {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/client.xml");context.start();// 獲取遠程代理并發起調用GreetingService greetingService = (GreetingService) context.getBean("greetingService");System.out.println(greetingService.greeting("world"));} }resource/spring/client.xml 中的配置與 Dubbo 服務的配置類似,主要是配置遠程代理,以及配置 Zipkin
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"xmlns="http://www.springframework.org/schema/beans"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"><!-- 1. 定義 dubbo 客戶端的應用名 --><dubbo:application name="dubbo-client"/><!-- 2. 指定注冊中心地址 --><dubbo:registry address="zookeeper://127.0.0.1:2181"/><!-- 3. 聲明 GreetingService 的遠程代理,并在 Dubbo 的 filter chain 中增加 tracing filter --><dubbo:reference id="greetingService" check="false" interface="com.alibaba.dubbo.samples.api.GreetingService" filter="tracing"/><!-- 4. zipkin 相關的配置 --><bean id="sender" class="zipkin2.reporter.beans.OkHttpSenderFactoryBean"><property name="endpoint" value="http://localhost:9411/api/v2/spans"/></bean><bean id="tracing" class="brave.spring.beans.TracingFactoryBean"><property name="localServiceName" value="client"/><property name="spanReporter"><bean class="zipkin2.reporter.beans.AsyncReporterFactoryBean"><property name="sender" ref="sender"/><!-- wait up to half a second for any in-flight spans on close --><property name="closeTimeout" value="500"/></bean></property><property name="currentTraceContext"><bean class="brave.spring.beans.CurrentTraceContextFactoryBean"><property name="scopeDecorators"><bean class="brave.context.slf4j.MDCScopeDecorator" factory-method="create"/></property></bean></property></bean> </beans>完成之后的工程的目錄結構如下:
?
運行
現在讓我們把整個鏈路運行起來,看看 Zipkin 鏈路追蹤的效果。
啟動 Zookeeper Server
執行以下命令在本地啟動一個 Zookeeper Server,如果沒有安裝,請自行從?ZooKeeper 官網?下載:
$ zkServer start啟動 Zipkin Server
執行以下命令在本地啟動一個 Zipkin Server:
$ curl -sSL https://zipkin.io/quickstart.sh | bash -s $ java -jar zipkin.jar啟動 HelloService
使用下面的命令啟動 HelloService,當然也可以直接在 IDE 中啟動:
$ mvn exec:java -Dexec.mainClass=com.alibaba.dubbo.samples.service.hello.Application啟動成功后應該可以在終端上看到 “Hello service started” 的字樣。
啟動 GreetingService
使用下面的命令啟動 GreetingService,當然也可以直接在 IDE 中啟動:
$ mvn exec:java -Dexec.mainClass=com.alibaba.dubbo.samples.service.greeting.Application啟動成功后應該可以在終端上看到 “Greeting service started” 的字樣。
運行 Dubbo 客戶端
使用下面的命令運行 Dubbo 客戶端向 GreetingService 發起遠程調用,當然也可以直接在 IDE 中運行:
$ mvn exec:java -Dexec.mainClass=com.alibaba.dubbo.samples.client.Application執行成功后,客戶端會在終端上輸出 “greeting, hello, world”。
鏈路追蹤
打開瀏覽器訪問 "http://localhost:9411" 并通過 "Find Traces" 按鈕來搜索,可以找到剛剛調用的鏈路追蹤,效果如下圖所示:
?
還可以進一步的選擇每一個 span 來查看本次調用邊界內的詳情,比如,hello-service 這個 span 的詳情如下:
?
總結
本文介紹了鏈路追蹤的基本概念以及 Zipkin 的基本用法,然后用 Dubbo 構建了一條最簡單的調用鏈路,并引入了 Zipkin 做全鏈路追蹤。由于 Zipkin 對 Dubbo 做了很好的支持,整個集成的過程還是十分簡單明了的。
Zipkin 對 Dubbo 的支持是構建在 Dubbo 的 filter 擴展機制上的,有興趣的讀者可以通過?https://github.com/openzipkin/brave/blob/master/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/TracingFilter.java?了解其實現細節。
本文中涉及的例子可以從?https://github.com/dubbo/dubbo-samples?中的 "dubbo-samples-zipkin" 子模塊中獲取。另外,spring-cloud-sleth 2.0 中開始?正式支持 Dubbo,相關的文章和例子后續計劃提供。
原文鏈接
本文為云棲社區原創內容,未經允許不得轉載。
總結
以上是生活随笔為你收集整理的服务化改造实践(三) | Dubbo + Zipkin的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一幅GAN网络创造的肖像图卖了40万美金
- 下一篇: 2亿用户背后的Flutter应用框架Fi