1. 程式人生 > >使用 Zipkin 和 Brave 實現分布式系統追蹤

使用 Zipkin 和 Brave 實現分布式系統追蹤

文檔 route 接收 ip 地址 信息 打開 Nid ide spring

**簡介

一、Zipkin
1.1 Zipkin
是一款開源的分布式實時數據追蹤系統(Distributed Tracking System),基於 Google Dapper 的論文設計而來,由 Twitter 公司開發貢獻。其主要功能是聚集來自各個異構系統的實時監控數據,用來追蹤微服務架構下的系統延時問題。

應用系統需要進行裝備(instrument)以向 Zipkin 報告數據。Zipkin 的用戶界面可以呈現一幅關聯圖表,以顯示有多少被追蹤的請求通過了每一層應用。

Zipkin 以 Trace 結構表示對一次請求的追蹤,又把每個 Trace 拆分為若幹個有依賴關系的 Span。在微服務架構中,一次用戶請求可能會由後臺若幹個服務負責處理,那麽每個處理請求的服務就可以理解為一個 Span(可以包括 API 服務,緩存服務,數據庫服務以及報表服務等)。當然這個服務也可能繼續請求其他的服務,因此 Span 是一個樹形結構,以體現服務之間的調用關系。

Zipkin 的用戶界面除了可以查看 Span 的依賴關系之外,還以瀑布圖的形式顯示了每個 Span 的耗時情況,可以一目了然的看到各個服務的性能狀況。打開每個 Span,還有更詳細的數據以鍵值對的形式呈現,而且這些數據可以在裝備應用的時候自行添加。

整個調用鏈中有兩個微服務 service1 和 service2,在 10ms(相對時間點)的時候,service1 作為客戶端向 service2 發送了一個請求(Client Send),之後 service2 服務於 19ms 的時候收到請求(Server Receive),並用了 12ms 的時間來處理,並於 31ms 時刻將數據返回(Server Send),最後 service1 服務於 1ms 以後接收到此數據(Client Receive),因此整個過程共耗時 22ms。圖中還給出了 service1 訪問 service2 服務前後 Http Client 連接池的狀態信息。

1.2、架構

Zipkin 主要由四部分構成:收集器、數據存儲、查詢以及 Web 界面。Zipkin 的收集器負責將各系統報告過來的追蹤數據進行接收;而數據存儲默認使用 Cassandra,也可以替換為 MySQL;查詢服務用來向其他服務提供數據查詢的能力,而 Web 服務是官方默認提供的一個圖形用戶界面。

1.3、運行
使用 Docker 運行 Zipkin 最為簡單,其過程如下:

gitclone https://github.com/openzipkin/docker-zipkin
cd docker-zipkin
docker-composeup
這樣啟動,默認會使用 Cassandra 數據庫,如果想改用 MySQL,可以換做以下命令啟動:

docker-compose -f docker-compose.yml -f docker-compose-mysql.yml up
啟動成功以後,可以通過 http:// :8080 來訪問。具體獲取 IP 地址的方法請參閱 Docker 的相關文檔。

二、Brave

2.1、簡介
Brave 是用來裝備 Java 程序的類庫,提供了面向 Standard Servlet、Spring MVC、Http Client、JAX RS、Jersey、Resteasy 和 MySQL 等接口的裝備能力,可以通過編寫簡單的配置和代碼,讓基於這些框架構建的應用可以向 Zipkin 報告數據。同時 Brave 也提供了非常簡單且標準化的接口,在以上封裝無法滿足要求的時候可以方便擴展與定制。

2.2、初始化
Brave 的初始化就是要構建 Brave 類的實例,該庫提供了 Builder 類用來完成這件事情。

註:下文中約定,大寫的 Brave 指該 Java 類庫,而 Brave 類指 com.github.kristofa.brave.Brave 類型,而小寫的 brave 指該類型的實例。

Brave.Builderbuilder = new Brave.Builder("serviceName");
Bravebrave = builder.build();
其中的 serviceName 是當前服務的名稱,這個名稱會出現在所有跟該服務有關的 Span 中。默認情況下,Brave 不會將收集到的監控數據發送給 Zipkin 服務器,而是會以日誌的形式打印到控制臺。如果需要將數據發送給服務器,就需要引入 HttpSpanCollector 類。當前版本(3.8.0)將這個類命名為 Collector,這個概念容易跟 Zipkin 自身的 Collector 相混淆,因此在 Issue #173 中官方建議將其更名為 Reporter,也就是說這個類是用來向 Zipkin 的 Collector 報告數據的。

使用 HttpSpanCollector 的方法如下:

Brave.Builderbuilder = new Brave.Builder("serviceName");
builder.spanCollector(HttpSpanCollector.create(
"http://localhost:9411",
new EmptySpanCollectorMetricsHandler()));
Bravebrave = builder.build();
使用 HttpSpanCollector.create 方法可以創建該類的一個對象,第一個參數就是 Zipkin 服務的地址(默認部署時的端口為 9411)。

如果使用 Spring 的話,為了方便擴展,建議添加一個名為 ZipkinBraveFactoryBean 的類,其內容大致如下:

package net.tangrui.example.brave;

// 省略所有的 import

public class ZipkinBraveFactoryBean implements FactoryBean<Brave> {

private final String serviceName;
private final String zipkinHost;

private Braveinstance;

public void setServiceName(final String serviceName) {
this.serviceName = serviceName;
}

public void setZipkinHost(final String zipkinHost) {
this.zipkinHost = zipkinHost;
}

private void createInstance() {
if (this.serviceName == null) {
throw new BeanInitializationException("Property serviceName
must be set.");
}

Brave.Builderbuilder = new Brave.Builder(this.serviceName);
if (this.zipkinHost != null && !"".equals(this.zipkinHost)) {
  builder.spanCollector(HttpSpanCollector.create(
    this.zipkinHost, new EmptySpanCollectorMetricsHandler()));
}
this.instance = builder.build();

}

@Override
public BravegetObject() throws Exception {
if (this.instance == null) {
this.createInstance();
}
return this.instance;
}

@Override
public Class<?> getObjectType() {
return Brave.class;
}

@Override
public boolean isSingleton() {
return true;
}
}
然後只需要在 application-context.xml 配置文件中使用該 FactoryBean 就可以了:

<beanid="brave"
class="net.tangrui.example.brave.ZipkinBraveFactoryBean"
p:serviceName="serviceName"
p:zipkinHost="http://localhost:9411"/>
2.3、裝備標準的 Servlet 應用
Brave 提供了 brave-web-servlet-filter 模塊,可以為標準的 Servlet 應用添加向 Zipkin 服務器報告數據的能力,需要做的就是在 web.xml 文件增加一個 BraveServletFilter。

不過這個 Filter 在初始化的時候需要傳入幾個參數,這些參數可以通過 brave 對象的對應方法獲得,但是註入這些構造參數,最簡單的辦法還是使用 Spring 提供的 DelegatingFilterProxy。

在 web.xml 中添加如下內容(最好配置為第一個 Filter,以便從請求最開始就記錄數據):

<filter>
<filter-name>braveFilter</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>braveFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
然後在配置文件中添加以下內容(創建 brave Bean 的有關代碼請參考上文):

最後一個類 com.github.kristofa.brave.http.DefaultSpanNameProvider 存在於 brave-http 模塊中。當使用 Maven 或 Gradle 來管理項目的話,brave-http 會隨著 brave-web-servlet-filter 的引入被自動關聯進來。 一切無誤的話就可以啟動服務。如果給定了 zipkinHost 參數,數據就會被發送到指定的 Zipkin 服務器上,然後可以在其 Web 界面上看到相關內容;否則會有類似如下的信息打印到系統控制臺(做了格式美化): { "traceId": "27bf14862307cd99", "name": "post", "id": "d79a683e2900c293", "parentId": "27bf14862307cd99", "timestamp": 1.463737111294e+15, "duration": 772000, "annotations": [ { "endpoint": { "serviceName": "service1", "ipv4": "172.20.13.41" }, "timestamp": 1.463737111294e+15, "value": "cs" }, { "endpoint": { "serviceName": "service1", "ipv4": "172.20.13.41" }, "timestamp": 1.463737112066e+15, "value": "cr" } ], "binaryAnnotations": [ { "key": "route.conn_manager_stats.after", "value": "[leased: 1; pending: 0; available: 0; max: 1000]", "endpoint": { "serviceName": "service1", "ipv4": "172.20.13.41" } }, { "key": "route.conn_manager_stats.before", "value": "[leased: 0; pending: 0; available: 0; max: 1000]", "endpoint": { "serviceName": "service1", "ipv4": "172.20.13.41" } }, { "key": "total.conn_manager_stats.after", "value": "[leased: 1; pending: 0; available: 0; max: 1000]", "endpoint": { "serviceName": "service1", "ipv4": "172.20.13.41" } }, { "key": "total.conn_manager_stats.before", "value": "[leased: 0; pending: 0; available: 0; max: 1000]", "endpoint": { "serviceName": "service1", "ipv4": "172.20.13.41" } } ] } **2.3、裝備 Spring MVC 應用** Brave 自帶了 brave-spring-web-servlet-interceptor 模塊,因此裝備 Spring MVC 項目變得非常容易,只需要在配置文件中添加一些 interceptor 就好了: **2.4、裝備 MySQL 服務** brave-mysql 模塊在 JDBC 驅動層面添加了一些攔截器,可以對 MySQL 的查詢進行監控。在使用之前也需要通過 Spring 進行一下配置。 該配置的目的是要給 MySQLStatementInterceptorManagementBean 類註入一個 ClientTracer 實例,這個實例會在後來的 MySQL JDBC 驅動的攔截器中被使用。初始化完成以後只需要在連接字符串中添加如下參數就可以了: ?statementInterceptors=com.github.kristofa.brave.mysql.MySQLStatementInterceptor&zipkinServiceName=myDatabaseService 其中的 zipkinServiceName 用來指定該 MySQL 服務的名稱,如果省略的話,會默認以 mysql-${databaseName} 的形式來呈現 這裏需要特別說明一點,因為 MySQL 服務是跟 Java 服務分離的,因此上文初始化 brave 對象時提供的服務名稱,並不適用於 MySQL 服務,所以才需要在這裏另外指定。 可以看出,添加了 statement interceptor 之後,可以看到 service2 請求 MySQL 查詢的起止時間,以及執行的 SQL 語句等信息。 # **三、總結** 本文主要介紹了 Zipkin 服務和其 Java 庫 Brave 的一些基本概念及原理,並且針對 Brave 開箱提供的一些裝備組件進行了詳細的使用說明。在後面進階篇的文章中,會對如何擴展 Brave 以實現自定義監控信息的內容進行介紹,敬請期待!

使用 Zipkin 和 Brave 實現分布式系統追蹤