1. 程式人生 > 實用技巧 >SpringCloud學習之七:使用Spring Cloud Sleuth實現微服務跟蹤

SpringCloud學習之七:使用Spring Cloud Sleuth實現微服務跟蹤

使用Spring Cloud Sleuth實現微服務跟蹤

Spring Cloud版本:Hoxton.SR5

1. 簡介

Spring Cloud Sleuth為Spring Cloud提供了分散式跟蹤的解決方案,它大量借用了Google Dapper、Twitter Zipkin和Apache HTrace的設計。

Sleuth借用了Dapper的術語:

  • span(跨度):基本工作單元。span用一個64位的id唯一標識。除ID外,span還包含了其他資料,例如描述、時間戳事件、鍵值對的註解(標籤),spanID、span父ID等。span被啟動和停止時,記錄了時間資訊。初始化span被稱為“root span”,該span的id和trace的ID相等。

  • trace(跟蹤):一組共享“root span”的span組成的樹狀結構稱為trace。trace也用一個64位的ID唯一標識,trace中的所有span都共享該trace的ID。

  • annotation(標註):annotaion用來記錄事件的存在,其中,核心annotaion用來定義請求的開始和結束。

    • CS(Client Server客戶端傳送):客戶端發起一個請求,該annotaion描述了span的開始

    • SR(Server Received伺服器端接收):伺服器端獲得請求並準備處理它。如果用SR減去CS時間戳,就能得到網路延遲

    • SS(Server Sent伺服器端傳送):該annotation表明如果完成請求處理(當響應發揮客戶端時)。如果用SS減去SR時間戳,就能得到伺服器端處理請求所需的時間

    • CR(Client Received客戶端接收):span結束的標識。客戶端成功接收到伺服器端的響應。如果CR減去CS時間戳,就能得到客戶端傳送請求到伺服器響應所需的時間

2. 整合Spring Cloud Sleuth

  • 在scl-eureka-client-consumer和scl-eureka-client-provider中新增如下依賴

    <dependency> 
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-sleuth</artifactId>
    </dependency>
    
  • 在配置檔案application.yml中新增如下配置

    logging:
      level:
        root: INFO
        org.springframework.web.servlet.DispatcherServlet: DEBUG
        org.springframework.cloud.sleuth: DEBUG
    
  • 重啟兩個專案,訪問http://localhost:8090/consumer/info,可發現兩個專案的日誌內容中已包含span和trace的一些資訊

3. 整合ELK

ELK是一款非常流行的日誌分析系統。搭建過程請參考:https://blog.csdn.net/u012575432/article/details/107252814

  • 在scl-eureka-client-consumer和scl-eureka-client-provider中新增如下依賴

    <dependency>
        <groupId>net.logstash.logback</groupId>
        <artifactId>logstash-logback-encoder</artifactId>
        <version>6.4</version>
    </dependency>
    
  • 因為日誌需要輸出給logstash進行分析,因此需要在src/main/resources目錄下新建檔案logback-spring.xml,內容如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
        
        <springProperty scope="context" name="springAppName" source="spring.application.name"/>
        <!-- Example for logging into the build folder of your project -->
        <property name="LOG_FILE" value="${BUILD_FOLDER:-log}/${springAppName}"/>​
    
        <!-- You can override this to have a custom pattern -->
        <property name="CONSOLE_LOG_PATTERN"
                  value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
    
        <!-- Appender to log to console -->
        <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <!-- Minimum logging level to be presented in the console logs-->
                <level>DEBUG</level>
            </filter>
            <encoder>
                <pattern>${CONSOLE_LOG_PATTERN}</pattern>
                <charset>utf8</charset>
            </encoder>
        </appender>
    
        <!-- Appender to log to file -->
        <appender name="flatfile" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_FILE}.log</file>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.gz</fileNamePattern>
                <maxHistory>7</maxHistory>
            </rollingPolicy>
            <encoder>
                <pattern>${CONSOLE_LOG_PATTERN}</pattern>
                <charset>utf8</charset>
            </encoder>
        </appender>
        
        <!-- Appender to log to file in a JSON format -->
        <appender name="logstash" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_FILE}.json</file>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${LOG_FILE}.json.%d{yyyy-MM-dd}.gz</fileNamePattern>
                <maxHistory>7</maxHistory>
            </rollingPolicy>
            <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
                <providers>
                    <timestamp>
                        <timeZone>UTC</timeZone>
                    </timestamp>
                    <pattern>
                        <pattern>
                            {
                            "severity": "%level",
                            "service": "${springAppName:-}",
                            "trace": "%X{traceId:-}",
                            "span": "%X{spanId:-}",
                            "baggage": "%X{key:-}",
                            "pid": "${PID:-}",
                            "thread": "%thread",
                            "class": "%logger{40}",
                            "rest": "%message"
                            }
                        </pattern>
                    </pattern>
                </providers>
            </encoder>
        </appender>
        
        <root level="INFO">
            <appender-ref ref="console"/>
            <appender-ref ref="logstash"/>
            <!-- <appender-ref ref="flatfile"/> -->
        </root>
    </configuration>
    
  • 因為logback-spring.xml中使用了spring.application.name屬性,所以需要將spring.application.name移動到bootstrap.yml下。

    因為logback-spring.xml配置先於application.yml載入,所以若仍將spring.application.name放在application.yml裡,將無法正確讀取屬性

  • 編寫Logstash配置檔案,命名為logstash.conf,放在config目錄下,然後重啟Logstash。內容如下

    input {
      file {
        codec => json
        path => "/home/sc/eureka-client/log/*.json" # logback輸出的json格式日誌檔案
      }
    }
    
    filter {
      grok {
        match => { "message" => "%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}\s+---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" }
      }
      date {
        match => ["timestamp", "ISO8601"]
      }
      mutate {
        remove_field => ["timestamp"]
      }
    }
    
    output {
      elasticsearch {
        hosts => ["http://127.0.0.1:9000"] # ES地址
      }
    }
    
  • 重啟兩個專案,多次訪問http://localhost:8090/consumer/info,產生日誌

  • 然後訪問Kibana首頁http://localhost:5601,找到Elasticsearch下的Index Management,發現列表中已存在logstash開頭的index

  • 然後找到Kibana下的Index Patterns,並按照如下步驟建立Index Pattern


  • 建立完成後,點選Kibana下的Discover,即可看見如下日誌。一個完整的http請求過程,展開可發現traceID都是一致的

4. 整合Zipkin

ELK實現了業務日誌的跟蹤,Zipkin可實現服務鏈路日誌跟蹤

Spring官網已不推薦自己實現Zipkin Server端,建議使用原生的Zipkin Server

zipkin-server:https://search.maven.org/remote_content?g=io.zipkin&a=zipkin-server&v=LATEST&c=exec

zipkin-dependencies:https://search.maven.org/remote_content?g=io.zipkin.dependencies&a=zipkin-dependencies&v=LATEST

4.1 微服務整合Zipkin

  • 啟動zipkin服務端

    nohup java -jar zipkin-server-2.21.5-exec.jar >> /home/sc/zipkin-server/zipkin.log 2>&1 &
    
  • 在scl-eureka-client-consumer和scl-eureka-client-provider中新增如下依賴

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-zipkin</artifactId>
    </dependency>
    
  • 在application.yml裡新增zipkin相關配置

    spring:
      zipkin:
        base-url: http://127.0.0.1:9411
      sleuth:
        sampler:
          probability: 1.0
    
  • 重啟兩個專案,多次訪問http://localhost:8090/consumer/info,產生日誌,然後訪問http://localhost:9411,可看到請求日誌,點選其中某一條即可看見呼叫過程。

4.2 使用RabbitMQ收集資料

  • 使用如下命令啟動Zipkin服務端

    nohup java -jar zipkin-server-2.21.5-exec.jar --RABBIT_ADDRESSES=127.0.0.1:5672  >> /home/sc/zipkin-server/zipkin.log 2>&1 &
    
  • 在scl-eureka-client-consumer和scl-eureka-client-provider中新增如下依賴

    <dependency>
        <groupId>org.springframework.amqp</groupId>
        <artifactId>spring-rabbit</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.amqp</groupId>
        <artifactId>spring-rabbit</artifactId>
    </dependency>
    
  • 修改application.yml配置檔案,刪除spring.zipkin.base-url,新增RabbitMQ相關配置

    spring:
      zipkin:
        sender:
          type: rabbit
      sleuth:
        sampler:
          probability: 1.0
      rabbitmq:
        host: 127.0.0.1
        port: 5672
    
  • 按上一步的測試方法進行測試。依然可以正常顯示跟蹤日誌

4.3 使用Elasticsearch儲存跟蹤資料

​ Zipkin預設使用將資料儲存在記憶體中。若Zipkin Server重啟或崩潰都會導致資料丟失,不適合生產環境。zipkin Server支援多種後端儲存,MySQL、Elasticsearch、Cassandra等。本文使用Elasticsearch儲存跟蹤資料

  • 使用如下命令啟動Zipkin服務端

    * nohup java -jar zipkin-server-2.21.5-exec.jar --RABBIT_ADDRESSES=127.0.0.1:5672 --STORAGE_TYPE=elasticsearch --ES_HOSTS=http://127.0.0.1:9000 >> /home/sc/zipkin-server/zipkin.log 2>&1 &
    
  • 使用curl http://localhost:9411/health即可檢視

4.4 依賴介面無法顯示依賴關係

  • 使用如下命令啟動zipkin dependencies即可

    STORAGE_TYPE=elasticsearch ES_HOSTS=http://127.0.0.1:9000 java -Xms64m -Xmx512m -jar zipkin-dependencies-2.4.2.jar
    
  • 該jar包啟動後僅允許一次,分析當前依賴關係。若想定時分析,可配置定時任務

  • 重新訪問http://localhost:9411,點選依賴即可檢視