天天看點

SpringCloud認識四之配置中心及服務鍊路追蹤

看到這裡必須看過我前面的部落格才能繼續進行下去。之前,我們已經學習了 SpringCloud 的很多元件,每個元件都建立了一個工程,而每個工程都會有一個配置檔案,并且有些配置是一樣的。例如:在實際項目中,我們建立了使用者和訂單兩個服務,這兩個服務是同一個資料庫,那麼我們在這兩個服務的配置檔案都會配置相同的資料源,一旦我們的資料庫位址發生改變(隻是一種情況),使用者和訂單兩個服務的配置檔案都需要改,這還是隻是兩個服務,在一個大型系統(比如淘寶),将會有成千上萬個服務,按照這種方式代價無疑是巨大的。

不過無需擔心,正所謂上有政策,下有對策,既然有這個問題,就一定會有解決方案,那就是建立一個配置中心,專門用于管理系統的所有配置,也就是我們将所有配置檔案放到統一的地方進行管理。

我們知道,SpringCloud 就是為了簡化開發而生的,是以 SpringCloud 為我們內建了配置中心——Spring Cloud Config 元件。

Spring Cloud Config 簡介

Spring Cloud Config 是一個高可用的分布式配置中心,它支援将配置存放到記憶體(本地),也支援将其放到 Git 倉庫進行統一管理(本文主要探讨和 Git 的融合)。

建立配置中心

建立配置中心一般分為以下幾個步驟:

1.建立 Git 倉庫。

請自行百度謝謝。

2.建立配置中心。

在原有工程建立一個 moudle,命名為 config,在 pom.xml 加入配置中心的依賴:

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
           

建立啟動類 Application.java:

@SpringBootApplication
@EnableEurekaClient
@EnableConfigServer
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}
           
注意,要加入 

@EnableConfigServer

 注解,否則配置中心是無法開啟的。

建立 application.yml 并增加如下内容:

server:
  port: 8888
spring:
  application:
    name: config
  profiles:
    active: dev
  cloud:
    config:
      server:
        git:
          uri: https://github.com/lynnlovemin/SpringCloudLesson.git #配置git倉庫位址
          searchPaths: 第09課/config #配置倉庫路徑
          username: ****** #通路git倉庫的使用者名
          password: ****** #通路git倉庫的使用者密碼
      label: master #配置倉庫的分支
eureka:
  instance:
    hostname: ${spring.cloud.client.ipAddress}
    instanceId: ${spring.cloud.client.ipAddress}:${server.port}
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
           

注意這裡出現了前面課程沒有出現過的新配置: 

eureka.instance.hostname

 和 

eureka.instance.instanceId

,我們可以通過一個測試來看這兩個配置的作用。

首先分别啟動注冊中心 eurekaserver 和配置中心 config,浏覽器通路:http://localhost:8761,我們可以看到如下界面:

SpringCloud認識四之配置中心及服務鍊路追蹤

可以看到箭頭所指向的位置是以 IP:端口形式呈現的,現在我們去掉這兩個配置重新啟動配置中心 config,再次通路:http://localhost:8761,可以看到:

SpringCloud認識四之配置中心及服務鍊路追蹤

由此可見,它預設是以 ip:application_name:端口呈現的。

在實際項目中,建議大家都寫成上述配置,否則如果通過 K8S 或 Docker 部署系統,可能會出現問題,具體原因将在第16課提到。

通過上述過程,配置服務中心已經建立完成,啟動它并且通路位址:http://localhost:8888/config/dev,即可看到:

SpringCloud認識四之配置中心及服務鍊路追蹤

3.修改各個服務配置。

我們建立配置中心的目的就是為了友善其他服務進行統一的配置管理,是以,還需要修改各個服務。

以服務提供者 eurekaclient 為例,按照以下步驟進行操作。

在 pom.xml 加入配置中心依賴:

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
           

在 resources 下建立 bootstrap.yml 并删除 application.yml(注意:這裡不是 application.yml,而是 bootstrap.yml):

spring:
  application:
    name: eurekaclient
  profiles:
    active: dev
  cloud:
    config:
      profile: dev #指定配置環境,配置檔案如果是多環境則取名類似:config-dev.yml
      name: eurekaclient #指定配置檔案名字(多個配置檔案以英文逗号隔開)
      label: master #git倉庫分支名
      discovery:
        enabled: true
        serviceId: config #連接配接的配置中心名字(applicaiton.name)
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
           

在配置中心配置的 Git 倉庫相應路徑下建立配置檔案 eurekaclient.yml(本執行個體為第09課/config):

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
server:
  port: 8763
spring:
  application:
    name: eurekaclient
           

我們依次啟動注冊中心、配置中心和服務提供者 eurekaclient,可以看到 eurekaclient 的監聽端口為8763,然後修改 eurekaclient.yml 的 server.port 為8764,重新啟動 eurekaclient,可以看到其監聽端口為8764,說明 eurekaclient 成功從 Git 上拉取了配置。

配置自動重新整理

我們注意到,每次修改配置都需要重新啟動服務,配置才會生效,這種做法也比較麻煩,是以我們需要一個機制,每次修改了配置檔案,各個服務配置自動生效,Spring Cloud 給我們提供了解決方案。

手動重新整理配置

我們先來看看如何通過手動方式重新整理配置。

1.在 eurekaclient 工程的 pom.xml 添加依賴:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
           

2.修改遠端 Git 倉庫的配置檔案 eurekaclient.yml:

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
server:
  port: 8764
spring:
  application:
    name: eurekaclient
management:
  security:
    #關閉安全驗證,否則通路refresh端點時會提示權限不足
    enabled: false
           

3.在 HelloController 類加入 

@RefeshScope

 依賴:

@RestController
@RefreshScope
public class HelloController {

    @Value("${server.port}")
    private int port;

    @RequestMapping("index")
    public String index(){
        return "Hello World!,端口:"+port;
    }
}
           

以上步驟就內建了手動重新整理配置。下面開始進行測試。

  1. 依次啟動注冊中心,配置中心,用戶端;
  2. 通路位址:http://localhost:8763/index,即可看到:
    SpringCloud認識四之配置中心及服務鍊路追蹤
  3. 修改 Git 倉庫遠端配置檔案 eurekaclient.yml 的端口為8764;
  4. 重新通路2的位址,我們發現端口未發生改變;
  5. POST 方式請求位址:http://localhost:8763/refresh,如:

    curl -X POST http://localhost:8763/refresh

    ,可以的用戶端控制台看到如下日志資訊:
    SpringCloud認識四之配置中心及服務鍊路追蹤
    說明 refresh 端點已請求配置中心重新整理配置。 6.再次通路2的位址,可以看到:
    SpringCloud認識四之配置中心及服務鍊路追蹤
    我們發現端口已發生改變,說明重新整理成功!

自動重新整理配置

前面我們講了通過 

/refresh

 端點手動重新整理配置,如果每個微服務的配置都需要我們手動重新整理,代價無疑是巨大的。不僅如此,随着系統的不斷擴張,維護也越來越麻煩。是以,我們有必要實作自動重新整理配置。

自動重新整理配置原理

  1. 利用 Git 倉庫的 WebHook,可以設定當有内容 Push 上去後,則通過 HTTP 的 POST 遠端請求指定位址。
  2. 利用消息隊列如 RabbitMQ、Kafka 等自動通知到每個微服務(本文以 RabbitMQ 為例講解)。

實作步驟

下面我們就來實作自動重新整理配置。

1.安裝 RabbitMQ(安裝步驟省略,請自行百度)。

2.在 eurekaclient 加入如下依賴:

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>
           

3.在 bootstrap.yml 添加以下内容:

spring:
    rabbitmq:
        host: localhost
        port: 5672
        username: guest
        password: guest
           

4.啟動注冊中心、配置中心和用戶端;

5.POST 方式請求:http://localhost:8763/bus/refresh,可以看到配置已被重新整理,實際項目中,我們會單獨建立一個工程用以重新整理配置,請求這個位址後,可以發現所有加入了 RefreshScope 和 actuator 依賴的工程都會被重新整理配置。

6.利用 Git 的 WebHook,實作自動重新整理,如圖:

SpringCloud認識四之配置中心及服務鍊路追蹤

設定好重新整理 URL 後,點選送出。以後每次有新的内容被送出後,會自動請求該 URL 實作配置的自動重新整理。

其實在上一課我們已經接觸過了消息總線,那就是 Spring Cloud Bus,這一課我們将繼續深入研究 Spring Cloud Bus 的一些特性。

局部重新整理

Spring Cloud Bus 用于實作在叢集中傳播一些狀态變化(例如:配置變化),它常常與 Spring Cloud Config 聯合實作熱部署。上面我們體驗了配置的自動重新整理,但每次都會重新整理所有微服務,有些時候我們隻想重新整理部分微服務的配置,這時就需要通過 

/bus/refresh

 斷點的 destination 參數來定位要重新整理的應用程式。

它的基本用法如下:

/bus/refresh?destination=application:port

其中,application 為各微服務指定的名字,port 為端口,如果我們要重新整理所有指定微服務名字下的配置,則 destination 可以設定為 application:例如:

/bus/refresh/destination=eurekaclient:

,代表重新整理所有名字為 EurekaClient 的微服務配置。

改進架構

在前面的示例中,我們是通過某一個微服務的 

/bus/refesh

 斷點來實作配置重新整理,但是這種方式并不優雅,它有以下弊端:

  1. 破壞了微服務的單一職責原則,微服務用戶端理論上應隻關注自身業務,而不應該負責配置重新整理。
  2. 破壞了微服務各節點的對等性。
  3. 有一定的局限性。在微服務遷移時,網絡位址時常會發生改變,這時若想自動重新整理配置,就不得不修改 Git 倉庫的 WebHook 配置。

是以,我們應考慮改進架構,将 ConfigServer 也加入到消息總線來,将其 

/bus/refresh

 用于實作配置的自動重新整理。這樣,各個微服務節點就隻需關注自身業務,無需再承擔配置自動重新整理的任務。

我們來看看此時的架構圖:

SpringCloud認識四之配置中心及服務鍊路追蹤

注意: 所有需要重新整理配置的服務都需要添加以下依賴。

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>
           

并且需要在配置檔案設定 rabbitmq 資訊:

spring:
    rabbitmq:
        host: localhost
        port: 5672
        username: guest
        password: guest
           

消息總線事件

在某些場景下,我們需要知道 Spring Cloud Bus 的事件傳播細節,這時就需要跟蹤消息總線事件。

要實作跟蹤消息總線事件是一件很容易的事情,隻需要修改配置檔案,如下所示:

server:
  port: 8888
spring:
  application:
    name: config
  profiles:
    active: dev
  cloud:
    bus:
      trace:
        enable: true
    config:
      server:
        git:
          uri: https://github.com/lynnlovemin/SpringCloudLesson.git #配置git倉庫位址
          searchPaths: 第09課/config #配置倉庫路徑
          username: lynnlovemin #通路git倉庫的使用者名
          password: liyi880301 #通路git倉庫的使用者密碼
      label: master #配置倉庫的分支
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
eureka:
  instance:
    hostname: ${spring.cloud.client.ipAddress}
    instanceId: ${spring.cloud.client.ipAddress}:${server.port}
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
management:
  security:
    enabled: false
           

我們将 spring.cloud.trace.enabled 設定為 true 即可,這樣我們在 POST 請求 

/bus/refresh

 後,浏覽器通路通路 

/trace

 端點即可看到如下資料:

[{
    "timestamp": 1527299528556,
    "info": {
        "method": "GET",
        "path": "/eurekaclient/dev/master",
        "headers": {
            "request": {
                "accept": "application/json, application/*+json",
                "user-agent": "Java/1.8.0_40",
                "host": "192.168.31.218:8888",
                "connection": "keep-alive"
            },
            "response": {
                "X-Application-Context": "config:dev:8888",
                "Content-Type": "application/json;charset=UTF-8",
                "Transfer-Encoding": "chunked",
                "Date": "Sat, 26 May 2018 01:52:08 GMT",
                "status": "200"
            }
        },
        "timeTaken": "4200"
    }
}, {
    "timestamp": 1527299524802,
    "info": {
        "method": "POST",
        "path": "/bus/refresh",
        "headers": {
            "request": {
                "host": "localhost:8888",
                "user-agent": "curl/7.54.0",
                "accept": "*/*"
            },
            "response": {
                "X-Application-Context": "config:dev:8888",
                "status": "200"
            }
        },
        "timeTaken": "1081"
    }
}, {
    "timestamp": 1527299497470,
    "info": {
        "method": "GET",
        "path": "/eurekaclient/dev/master",
        "headers": {
            "request": {
                "accept": "application/json, application/*+json",
                "user-agent": "Java/1.8.0_40",
                "host": "192.168.31.218:8888",
                "connection": "keep-alive"
            },
            "response": {
                "X-Application-Context": "config:dev:8888",
                "Content-Type": "application/json;charset=UTF-8",
                "Transfer-Encoding": "chunked",
                "Date": "Sat, 26 May 2018 01:51:37 GMT",
                "status": "200"
            }
        },
        "timeTaken": "2103"
    }
}, {
    "timestamp": 1527299490374,
    "info": {
        "method": "GET",
        "path": "/eurekaclient/dev/master",
        "headers": {
            "request": {
                "accept": "application/json, application/*+json",
                "user-agent": "Java/1.8.0_40",
                "host": "192.168.31.218:8888",
                "connection": "keep-alive"
            },
            "response": {
                "X-Application-Context": "config:dev:8888",
                "Content-Type": "application/json;charset=UTF-8",
                "Transfer-Encoding": "chunked",
                "Date": "Sat, 26 May 2018 01:51:30 GMT",
                "status": "200"
            }
        },
        "timeTaken": "6691"
    }
}]
           

這樣就可以清晰的看到傳播細節了。

之前的部落格已經教大家使用 Actuator 監控微服務,使用 Hystrix 監控 Hystrix Command。現在,我們來研究微服務鍊路追蹤。

我們知道,微服務之間通過網絡進行通信。在我們提供服務的同時,我們不能保證網絡一定是暢通的,相反,網絡是很脆弱的,網絡資源也有限。是以,我們有必要追蹤每個網絡請求,了解其經過了哪些微服務,延遲多少,每個請求所耗費的時間等。隻有這樣,我們才能更好的分析系統拼勁,解決系統問題。

下面,我們主要探讨服務追蹤元件 Zipkin,SpringCloudSleuth 內建了 Zipkin。

Zipkin 簡介

Zipkin 是 Twitter 開源的分布式跟蹤系統,基于 Dapper 的論文設計而來。它的主要功能是收集系統的時序資料,進而追蹤微服務架構的系統延時等問題。Zipkin 還提供了一個非常友好的界面,便于我們分析追蹤資料。

SpringCloudSleuth 簡介

通過 SpringCloud 來建構微服務架構,我們可以通過 SpringCloudSleuth 實作分布式追蹤,它內建了 Zipkin。

Sleuth 術語

  • span(跨度):基本工作單元。例如,在一個建立的 span 中發送一個 RPC 等同于發送一個回應請求給 RPC,span 通過一個64位 ID 唯一辨別,trace 以另一個64位 ID 表示,span 還有其他資料資訊,比如摘要、時間戳事件、關鍵值注釋(tags)、span 的 ID,以及進度 ID(通常是 IP 位址)。span 在不斷的啟動和停止,同時記錄了時間資訊,當你建立了一個 span,你必須在未來的某個時刻停止它。
  • trace(追蹤):一組共享“root span”的 span 組成的樹狀結構成為 trace。trace 也用一個64位的 ID 唯一辨別,trace中的所有 span 都共享該 trace 的 ID。
  • annotation(标注):用來及時記錄一個事件的存在,一些核心 annotations 用來定義一個請求的開始和結束。
    • cs,即 Client Sent,用戶端發起一個請求,這個 annotion 描述了這個 span 的開始。
    • sr,即 Server Received,服務端獲得請求并準備開始處理它,如果将其 sr 減去 cs 時間戳便可得到網絡延遲。
    • ss,即 Server Sent,注解表明請求處理的完成(當請求傳回用戶端),如果 ss 減去 sr 時間戳便可得到服務端需要的處理請求時間。
    • cr,即 Client Received,表明 span 的結束,用戶端成功接收到服務端的回複,如果 cr 減去 cs 時間戳便可得到用戶端從服務端擷取回複的所有所需時間。

下圖示範了請求依次經過 SERVICE1 -> SERVICE2 -> SERVICE3 -> SERVICE4 時,span、trace、annotation 的變化:

SpringCloud認識四之配置中心及服務鍊路追蹤

簡單的鍊路追蹤實作

(1)在 parent 工程上建立一個子工程:zipkin,在 pom.xml 加入以下依賴:

<dependencies>
        <dependency>
            <groupId>io.zipkin.java</groupId>
            <artifactId>zipkin-autoconfigure-ui</artifactId>
        </dependency>
        <dependency>
            <groupId>io.zipkin.java</groupId>
            <artifactId>zipkin-server</artifactId>
        </dependency>
    </dependencies>
           

(2)編寫啟動類 Application.java:

@SpringBootApplication
@EnableZipkinServer
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}
           

(3)編寫配置檔案 application.yml:

server:
  port: 9411
           

(4)啟動 Application.java,并通路位址:http://localhost:9411,即可看到如下界面:

SpringCloud認識四之配置中心及服務鍊路追蹤

單純內建 zipkinServer 還達不到追蹤的目的,我們還必須使我們的微服務用戶端內建 Zipkin 才能跟蹤微服務,下面是內建步驟。

(1)在 EurekaClient 工程的 pom 檔案中添加以下依賴:

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-sleuth-zipkin</artifactId>
        </dependency>
           

(2)在 Git 倉庫的配置檔案 eurekaclient.yml 中添加以下内容:

spring:
    zipkin:
        base-url: http://localhost:9411
    sleuth:
        sampler:
            percentage: 1.0
           

其中,spring.zipkin.base-url 用來指定 zipkinServer 的位址。spring.sleutch.sampler.percentage 用來指定采樣請求的百分比(預設為0.1,即10%)。

(3)依次啟動注冊中心、配置中心、Zipkin、eurekaclient,依次通路 http://localhost:8763/index,http://localhost:9411,進入 Zipkin 界面後,點選 Find a trace 按鈕,可以看到 trace 清單:

SpringCloud認識四之配置中心及服務鍊路追蹤

通過消息中間件實作鍊路追蹤

在之前的執行個體中,我們使用 HTTP 來收集資料,如果 zipkinServer 的網絡位址發生了變化,每個微服務的 base-url 都需要改變,是以,我們還可以通過消息隊列來收集追蹤資料。

我以 RabbitMQ 作為消息中間件進行示範。

(1)改造 Zipkin 工程,将 pom.xml 依賴修改為:

<dependencies>
        <dependency>
            <groupId>io.zipkin.java</groupId>
            <artifactId>zipkin-autoconfigure-ui</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-sleuth-zipkin-stream</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
        </dependency>
    </dependencies>
           

(2)配置檔案加入 RabbitMQ 相關:

pring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
           

(3)改造 EurekaClient,将 pom.xml 依賴改為如下内容:

<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-sleuth-stream</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
        </dependency>
    </dependencies>
           

(4)Git 倉庫的配置檔案 EurekaClient 去掉 spring.zipkin.base-url 配置,并添加如下内容:

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
           

(5)依次啟動相應工程,我們發現依然可以正常跟蹤微服務。

存儲追蹤資料

前面的示例中,ZipkinServer 是預設将資料存儲在記憶體中,一旦 ZipkinServer 重新開機或發生故障,将會導緻曆史資料丢失,是以我們需要将跟蹤資料儲存到硬碟中。

ZipkinServer 支援多種後端資料存儲,比如:MySQL、ElasticSearch、Cassandra 等。

我以 MySQL 為例來示範如何将曆史資料存儲在 MySQL 中。

(1)首先建立一個名為 Zipkin 的資料庫,并執行以下腳本:

CREATE TABLE IF NOT EXISTS zipkin_spans (
  `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
  `trace_id` BIGINT NOT NULL,
  `id` BIGINT NOT NULL,
  `name` VARCHAR(255) NOT NULL,
  `parent_id` BIGINT,
  `debug` BIT(1),
  `start_ts` BIGINT COMMENT 'Span.timestamp(): epoch micros used for endTs query and to implement TTL',
  `duration` BIGINT COMMENT 'Span.duration(): micros used for minDuration and maxDuration query'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;

ALTER TABLE zipkin_spans ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `id`) COMMENT 'ignore insert on duplicate';
ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`, `id`) COMMENT 'for joining with zipkin_annotations';
ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTracesByIds';
ALTER TABLE zipkin_spans ADD INDEX(`name`) COMMENT 'for getTraces and getSpanNames';
ALTER TABLE zipkin_spans ADD INDEX(`start_ts`) COMMENT 'for getTraces ordering and range';

CREATE TABLE IF NOT EXISTS zipkin_annotations (
  `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
  `trace_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.trace_id',
  `span_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.id',
  `a_key` VARCHAR(255) NOT NULL COMMENT 'BinaryAnnotation.key or Annotation.value if type == -1',
  `a_value` BLOB COMMENT 'BinaryAnnotation.value(), which must be smaller than 64KB',
  `a_type` INT NOT NULL COMMENT 'BinaryAnnotation.type() or -1 if Annotation',
  `a_timestamp` BIGINT COMMENT 'Used to implement TTL; Annotation.timestamp or zipkin_spans.timestamp',
  `endpoint_ipv4` INT COMMENT 'Null when Binary/Annotation.endpoint is null',
  `endpoint_ipv6` BINARY(16) COMMENT 'Null when Binary/Annotation.endpoint is null, or no IPv6 address',
  `endpoint_port` SMALLINT COMMENT 'Null when Binary/Annotation.endpoint is null',
  `endpoint_service_name` VARCHAR(255) COMMENT 'Null when Binary/Annotation.endpoint is null'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;

ALTER TABLE zipkin_annotations ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `span_id`, `a_key`, `a_timestamp`) COMMENT 'Ignore insert on duplicate';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`, `span_id`) COMMENT 'for joining with zipkin_spans';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTraces/ByIds';
ALTER TABLE zipkin_annotations ADD INDEX(`endpoint_service_name`) COMMENT 'for getTraces and getServiceNames';
ALTER TABLE zipkin_annotations ADD INDEX(`a_type`) COMMENT 'for getTraces';
ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces';

CREATE TABLE IF NOT EXISTS zipkin_dependencies (
  `day` DATE NOT NULL,
  `parent` VARCHAR(255) NOT NULL,
  `child` VARCHAR(255) NOT NULL,
  `call_count` BIGINT
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;

ALTER TABLE zipkin_dependencies ADD UNIQUE KEY(`day`, `parent`, `child`);
           

(2)改造 Zipkin 工程并添加以下依賴:

<dependency>
            <groupId>io.zipkin.java</groupId>
            <artifactId>zipkin-storage-mysql</artifactId>
            <version>2.4.9</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
           

(3)在 application.yaml 增加如下配置:

zipkin:
  storage:
    type: mysql
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/zipkin?autoReconnect=true
    username: root
    password: ******
    driverClassName: com.mysql.jdbc.Driver
           

(4)修改 Application.java:

@SpringBootApplication
@EnableZipkinStreamServer
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }

    @Bean
    @Primary
    public MySQLStorage mySQLStorage(DataSource datasource) {
        return MySQLStorage.builder().datasource(datasource).executor(Runnable::run).build();
    }
}
           

(5)啟動測試,檢視 Zipkin 資料庫,發現已經生成了資料,并重新開機 Zipkin 工程,繼續查詢,發現仍可查詢曆史資料。

繼續閱讀