1. 程式人生 > >微服務之分散式跟蹤系統(springboot+zipkin)

微服務之分散式跟蹤系統(springboot+zipkin)

          微服務之分散式跟蹤系統(springboot+zipkin)

一、zipkin是什麼

        zipkin是一個開放原始碼分散式的跟蹤系統,由Twitter公司開源,它致力於收集服務的定時資料,以解決微服務架構中的延遲問題,包括資料的收集、儲存、查詢和展現。它的理論模型來自於Google Dapper 論文。

        每個服務向zipkin報告計時資料,zipkin會根據呼叫關係通過Zipkin UI生成依賴關係圖,顯示了多少跟蹤請求通過每個服務,該系統讓開發者可通過一個 Web 前端輕鬆的收集和分析資料,例如使用者每次請求服務的處理時間等,可方便的監測系統中存在的瓶頸。

二、什麼需要分散式跟蹤系統(zipkin)

        當代的網際網路的服務,通常都是用複雜的、大規模分散式叢集來實現的。特別是隨著微服務架構和容器技術的興起(加速企業敏捷,快速適應業務變化,滿足架構的高可用和高擴充套件),網際網路應用往往構建在不同的服務之上,這些服務,有可能是由不同的團隊開發、可能使用不同的程式語言來實現、有可能布在了幾千臺伺服器,橫跨多個不同的資料中心。因此,就需要一些可以幫助理解系統行為、用於快速分析效能問題的工具。先是Google開發其分散式跟蹤系統並且發表了Dapper 論文,然後由Twitter參照Dapper論文設計思想開發zipkin分散式跟蹤系統,同時開源出來。

         zipkin通過採集跟蹤資料可以幫助開發者深入瞭解在分散式系統中某一個特定的請求時如何執行的。假如說,我們現在有一個使用者請求超時,我們就可以將這個超時的請求呼叫鏈展示在UI當中。我們可以很快度的定位到導致響應很慢的服務究竟是什麼。如果對這個服務細節也很很清晰,那麼我們還可以定位是服務中的哪個問題導致超時。同時,通過服務呼叫鏈路能夠快速定位系統的效能瓶頸。

三、zipkin下載與啟動

       在本節中,我們將介紹下載和啟動zipkin例項,以便在本地檢查zipkin。有三種安裝方法:使用官網自己打包好的Jar執行,Docker方式或下載原始碼自己打包Jar執行(因為zipkin使用了springboot,內建了伺服器,所以可以直接使用jar執行)。zipkin推薦使用docker方式執行,我後面會專門寫一遍關於docker的執行方式,而原始碼執行方式好處是有機會體驗到最新的功能特性,但是可能也會帶來一些比較詭異的坑,所以不做講解,下面我直接是使用官網打包的jar執行過程:

(1)    下載jar檔案


         wget -O zipkin.jar  'https://search.maven.org/remote_content?g=io.zipkin.java&a=zipkin-server&v=LATEST&c=exec'
      不過,我在執行的過程中,發現無法下載。然後我通過翻牆軟體,下載了其最新的jar檔案(zipkin-server-1.17.1-exec.jar),我這裡也提供其下載地址。

(2)    啟動例項

         java-jar zipkin-server-1.17.1-exec.jar或者java -jar zipkin.jar(注意需要安轉JDK8或者以上的版本),啟動成功如下圖所示:

(3)    檢視執行效果

        通過上圖,我們發現zipkin使用springboot,並且啟動的埠為9411,然後我們通過瀏覽器訪問,效果如下:

四、zipkin的架構與核心概念

        將資料傳送到zipkin的已檢測應用程式中的元件稱為Reporter。它通過幾種傳輸方式之一將跟蹤資料傳送到zipkin收集器,zipkin收集器將跟蹤資料儲存到儲存器。稍後,儲存由API查詢以向UI提供資料。為了保證服務的呼叫鏈路跟蹤,zipkin使用傳輸ID,例如,當正在進行跟蹤操作並且它需要發出傳出http請求時,會新增一些headers資訊以傳播ID,但它不能用於傳送詳細資訊(操作名稱、資料等)。其架構圖如下所示:

A、Span

        基本工作單元,一次鏈路呼叫建立一個span,通過一個64位ID標識它,span通過還有其他的資料,例如描述資訊,時間戳,key-value對的(Annotation)tag資訊,parent-id等,其中parent-id 可以表示span呼叫鏈路來源,通俗的理解span就是一次請求資訊。

B、 Trace

      類似於樹結構的Span集合,表示一條呼叫鏈路,存在唯一標識。

C、 Annotation

      註解,用來記錄請求特定事件相關資訊(例如時間),通常包含四個註解資訊:

(1)cs - ClientStart,表示客戶端發起請求

(2)sr - Server Receive,表示服務端收到請求

(3)ss - Server Send,表示服務端完成處理,並將結果傳送給客戶端

(4)cr - Client Received,表示客戶端獲取到服務端返回資訊

D、 Transport

        收集被trace的services的spans,並且傳輸給zipkin的collector,有三個主要傳輸:HTTP,Kafka和Scribe。

E、 Collector

       zipkincollector會對一個到來的被trace的資料(span)進行驗證、儲存並設定索引。

F、 Storage

        儲存,zipkin預設的儲存方式為in-memory,即不會進行持久化操作。如果想進行收集資料的持久化,可以儲存資料在Cassandra,因為Cassandra是可擴充套件的,有一個靈活的模式,並且在Twitter中被大量使用,我們使這個元件可插入。除了Cassandra,我們原生支援ElasticSearch和MySQL。其他後端可能作為第三方擴充套件提供。

G、QueryService

        一旦資料被儲存和索引,我們需要一種方法來提取它。查詢守護程式提供了一個用於查詢和檢索跟蹤的簡單JSON API,此API的主要使用者是WebUI。

H、 WebUI

        展示頁面,提供了一個漂亮的介面來檢視痕跡。 Web UI提供了一種基於服務,時間和註釋檢視trace的方法(通過Query Service)。注意:在UI中沒有內建的身份驗證。

五、分散式跟蹤系統實踐(springboot+zipkin)

  5.1場景設定與分析

      現在有一個服務A呼叫服務B,服務B又分別呼叫服務C和D,整個鏈路過程的關係圖如下所示:

其呼叫工作流程呼叫鏈路詳細圖:

上圖表示一請求鏈路,一條鏈路通過TraceId唯一標識,Span標識發起的請求資訊,各span通過parent id 關聯起來,parentId==null,表示該span就是root span,如下圖所示:

 

zipkin提供了各種語言的客戶端(Java、Go、Scala、Ruby、JavaScript),使用它們想zipkin彙報資料。

5.2 程式碼編寫

     下面我以Java的客戶端Brave為例完成上面四個服務呼叫程式碼編寫,原始碼下載地址:https://github.com/dreamerkr/mircoservice.git資料夾springboot+zipkin下面,具體如下:

(1)    serivce1

a、 springboot啟動類

@SpringBootApplication
@EnableAutoConfiguration
public class Application {
 
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
b、zipkin收集與配置類


/**
 * 
 * TODO zipkin配置
 *
 * @author wangzhao (mailto:[email protected])
 */
@Configuration
public class ZipkinConfig {
    
    //span(一次請求資訊或者一次鏈路呼叫)資訊收集器
    @Bean
    public SpanCollector spanCollector() {
        Config config = HttpSpanCollector.Config.builder()
                .compressionEnabled(false)// 預設false,span在transport之前是否會被gzipped
                .connectTimeout(5000)
                .flushInterval(1)
                .readTimeout(6000)
                .build();
        return HttpSpanCollector.create("http://localhost:9411", config, new EmptySpanCollectorMetricsHandler());
    }
    
    //作為各呼叫鏈路,只需要負責將指定格式的資料傳送給zipkin
    @Bean
    public Brave brave(SpanCollector spanCollector){
        Builder builder = new Builder("service1");//指定serviceName
        builder.spanCollector(spanCollector);
        builder.traceSampler(Sampler.create(1));//採集率
        return builder.build();
    }
 
 
    //設定server的(服務端收到請求和服務端完成處理,並將結果傳送給客戶端)過濾器
    @Bean
    public BraveServletFilter braveServletFilter(Brave brave) {
        BraveServletFilter filter = new BraveServletFilter(brave.serverRequestInterceptor(),
                brave.serverResponseInterceptor(), new DefaultSpanNameProvider());
        return filter;
    }
    
    //設定client的(發起請求和獲取到服務端返回資訊)攔截器
    @Bean
    public OkHttpClient okHttpClient(Brave brave){
        OkHttpClient httpClient = new OkHttpClient.Builder()
                .addInterceptor(new BraveOkHttpRequestResponseInterceptor(
                        brave.clientRequestInterceptor(),
                        brave.clientResponseInterceptor(), 
                        new DefaultSpanNameProvider())).build();
        return httpClient;
    }
}
c、 服務1業務程式碼


@Api("service的API介面")
@RestController
@RequestMapping("/service1")
public class ZipkinBraveController {
 
 
    @Autowired
    private OkHttpClient client;
    
    @ApiOperation("trace第一步")
    @RequestMapping("/test")
    public String service1() throws Exception {
        Thread.sleep(100);
        Request request = new Request.Builder().url("http://localhost:8082/service2/test").build();
        Response response = client.newCall(request).execute();
        return response.body().string();
    }
    
}
d、pom檔案


  <dependencies>
          <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>1.3.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>1.3.5.RELEASE</version>
        </dependency>
       <dependency>
             <groupId>io.zipkin.brave</groupId>
              <artifactId>brave-core</artifactId>
              <version>3.9.0</version>
          </dependency>
<!--      <dependency>
             <groupId>io.zipkin.reporter</groupId>
             <artifactId>zipkin-reporter-urlconnection</artifactId>
             <version>0.2.0</version>
          </dependency>
          -->
          <dependency>
              <groupId>io.zipkin.brave</groupId>
              <artifactId>brave-spancollector-http</artifactId>
             <version>3.9.0</version>
         </dependency>
         <dependency>
             <groupId>io.zipkin.brave</groupId>
              <artifactId>brave-web-servlet-filter</artifactId>
              <version>3.9.0</version>
         </dependency>
         <dependency>
             <groupId>io.zipkin.brave</groupId>
             <artifactId>brave-okhttp</artifactId>
             <version>3.9.0</version>
         </dependency>
 
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.6.1</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.6.1</version>
        </dependency>
  </dependencies>
e、application.properties

application.name: service1
 
server.port: 8081
(2)    serivce2

            pom檔案和啟動類與service1是一樣的,配置類ZipkinConfig把service1改成service2,application.properties改name為service2、改埠為8082,服務2業務程式碼如下:


@Api("service的API介面")
@RestController
@RequestMapping("/service2")
public class ZipkinBraveController {
 
    @Autowired
    private OkHttpClient client;
    
    @ApiOperation("trace第二步")
    @RequestMapping("/test")
    public String service1() throws Exception {
        Thread.sleep(200);
        Request request3 = new Request.Builder().url("http://localhost:8083/service3/test").build();
        Response response3 = client.newCall(request3).execute();
        
        Request request4 = new Request.Builder().url("http://localhost:8084/service4/test").build();
        Response response4 = client.newCall(request4).execute();
        return response3.toString()+":"+response4.toString();
    }
    
}
(3)    serivce3


          pom檔案和啟動類與service1是一樣的,配置類ZipkinConfig把service1改成service3,application.properties改name為service3、改埠為8083,服務3業務程式碼如下:

@Api("service的API介面")
@RestController
@RequestMapping("/service3")
public class ZipkinBraveController {
    
    @ApiOperation("trace第三步")
    @RequestMapping("/test")
    public String service1() throws Exception {
        Thread.sleep(300);
        return "service3";
    }
    
}
}
(4)    serivce4


           pom檔案和啟動類與service1是一樣的,配置類ZipkinConfig把service1改成service4,application.properties改name為service4、改埠為8084,服務4業務程式碼如下:

@Api("service的API介面")
@RestController
@RequestMapping("/service4")
public class ZipkinBraveController {
    
    @ApiOperation("trace第四步")
    @RequestMapping("/test")
    public String service1() throws Exception {
        Thread.sleep(300);
        return "service4";
    }
    
}
5.3執行效果

 (1)分別啟動每個服務,然後訪問服務1,瀏覽器訪問(http://localhost:8081/service1/test)

(2)輸入zipkin地址,每次trace的列表

點選其中的trace,可以看trace的樹形結構,包括每個服務所消耗的時間:

點選每個span可以獲取延遲資訊:

同時可以檢視服務之間的依賴關係:

        本篇主要與大家分享zipkin進行簡單的服務分散式跟蹤,但是收集的資料都在記憶體裡面,重新啟動後資料丟失,但是zipkin還提供了Cassandra、mysql、ElasticSearch等多種儲存方式,下面章節做講解《微服務之分散式跟蹤系統(springboot+zipkin+mysql)》
--------------------- 
作者:追夢客 
來源:CSDN 
原文:https://blog.csdn.net/qq_21387171/article/details/53787019 
版權宣告:本文為博主原創文章,轉載請附上博文連結!