天天看點

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

目  錄

     1. 摘要

    2. 全鍊路日志追蹤架構與服務搭建

        2.1 日志鍊路架構圖解

        2.2 微服務劃分與搭建

    3. 分布式服務全鍊路日志追蹤實踐

        3.1 服務的注冊與發現

        3.2 案例一:前端請求不經過網關層

            3.2.1 REST  服務-A + REST 服務-B

            3.2.2 基于 RestTemplate 追蹤

             3.2.3 基于 Feign 的日志追蹤

        3.3 案例二:前端請求統一經過網關層

             3.3.1 整合 zuul 網關

            3.3.2 全鍊路日志追蹤實作

     4. 小結

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

(圖檔來源于 Google Dapper 的一篇論文,這是鍊路追蹤理論基礎的鼻祖)這張圖看上去感覺很高大上的樣子 

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

,但精髓在于日志追蹤架構設計思維。即設計思維很重要!設計思維很重要!設計思維很重要!設計思維很重要![重要的話說四遍 

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

] —— 思路和方案設計指導可落地的開發實作

1. 摘  要:

由于早期微服務項目趕進度,對日志這塊沒有進行詳細的設計和規範,每個微服務都是自己管自己的服務内部日志資訊。這也對後面的線上問題排查定位帶來了很大的困難,特别是微服務之間的互相調用,問題定位特别的困難。現在我們想實作從請求開始,到請求結束的全鍊路日志追蹤。需求很簡單,實作思路也不難,隻需要在請求過程中添加一個全局唯一的 traceId 即可。

本文通過建構三個 Spring Boot 輕量級微服務系統,一個網關服務和兩個下遊接口服務,Step By Step 模拟實作分布式系統跨服務調用全鍊路日志追蹤。

2. 全鍊路日志追蹤架構與服務搭建

2.1 日志鍊路追蹤架構圖解

前後端分離模式下,前端直接通路對應的接口服務,微服務架構中很少見這種,第一種架構(簡化)圖示如下所示:

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

前後端分離模式下,前端直接通路網關(接口服務統一入口),由網關路由到具體的下遊服務接口,這種微服務架構較常見。第二種架構(簡化)圖示如下所示:

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

2.2 微服務劃分與搭建

可直接參考這篇文章【小白都能看得懂的服務調用鍊路追蹤設計與實作(未實作跨服務鍊路追蹤)】,複制成 3 份 log-track 工程,分别将各個工程命名為logtrack-1(Service-A)、logtrack-2(Service-B)、log-zuul(網關),工程結構如下圖所示:

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

3. 分布式服務全鍊路日志追蹤實踐

3.1 服務的注冊與發現

3.1.1 環境準備:

  • MacOS + IDEA 2019.3 + SpringBoot 2.1.1 + SpringCloud Greenwich.SR1 + Zookeeper 3.4.13

其中Spring Cloud 和 Spring Boot 版本的對應關系見下圖所示(Spring 官網 https://spring.io/projects/spring-cloud#overview):

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

3.1.2 選擇 Zookeeper 作為分布式服務注冊中心:

  • 關于 MacOS 軟體安裝 Zookeeper 小結(Windows 略過,也可自行百度):
Mac 使用 brew 包管理器安裝軟體// 安裝 Homebrew 在終端輸入:/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)”// 解除安裝 Homebrew 在終端輸入:/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/uninstall)"brew 常用指令:brew install    // 安裝brew uninstall  // 解除安裝brew list       // 列出已安裝的軟體brew update     // 更新brewbrew home       // 用浏覽器打開brew的官方網站brew info       // 顯示軟體資訊brew deps       // 顯示包依賴brew --version  // 顯示版本号安裝 Zookeeper 注冊中心// 檢視zookeeper可用版本資訊:brew info zookeeper// 利用brew 安裝指令:brew install zookeeper// 利用brew 解除安裝指令:brew uninstall zookeeper// 安裝成功後檢視 zookeeper 資訊:brew info zookeeper// 進入安裝目錄:cd /usr/local/etc/zookeeper/// 啟動指令:zkServer start// 檢視服務狀态:zkServer status// 停止服務:zkServer stop// zookeeper 背景用戶端:zkCli// 退出用戶端:quit
           

檢視本機安裝的 Zookeeper(以下簡稱 zk)版本号指令如下:

echo stat|nc localhost 2181
           
  • 本地 zk 版本号是 3.4.13,如下圖所示:
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign
  • 本地 zk 服務啟動指令如下所示:
# 啟動 zkzkServer start# 停止 zkzkServer stop
           

下圖表示本地 zk 服務啟動成功:

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign
  • zk 管理系統之 zkui 安裝與使用

通過網址下載下傳zkui(https://github.com/DeemOpen/zkui)

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

下載下傳後解壓 zkui-master.zip,如下圖所示:

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

解壓後進入zkui-master 檔案夾,檔案夾中内容展示如下所示:

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

檢視 config.cfg 配置檔案内容說明如下:

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

啟動 zkui 服務,即啟動 zkui.sh 檔案,啟動、停止指令如下:

# 啟動sh zkui.sh start# 停止sh zkui.sh stop
           
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

或者

# 解壓從github下載下傳的zkui-master.zipunzip zkui-master.zip# 進入zkui-master目錄cd zkui-master# 編譯注冊mvn clean install# 複制config.cfg檔案到target目錄下cp config.cfg target/# 進入target目錄cd target/# 背景啟動zkui指令nohup java -jar zkui-2.0-SNAPSHOT-jar-with-dependencies.jar &
           

在浏覽器中輸入網址:http://localhost:9090/,通路 zkui 管理界面,如下所示:

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

登入的使用者名和密碼預設是:admin、manager 登入 zkui home 首頁之後,可以看到如下所示界面,zk 根節點還沒有其他節點資訊:

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

如果啟動我們的 logtrack-1 和 logtrack-2 服務,可以觀察到 zkui-home頁面出現了新的 logservices 服務節點,該節點下注冊了兩個服務,服務的名稱正好對應正在啟動兩個應用服務名。

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

點選其中一個服務名,進去可以看到該服務在zk注冊的詳細資訊,如下圖所示:

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

修改 logtrack-1 工程的 pom.xml 檔案,新增 zk 注冊中心服務發現依賴,如下:

org.springframework.cloud      spring-cloud-starter-zookeeper-discovery                        org.apache.zookeeper          zookeeper                            org.apache.zookeeper      zookeeper            3.4.13                        org.slf4j          slf4j-log4j12                  
           

與 Spring Cloud 整合,在 pom.xml 檔案新增依賴如下所示:

org.springframework.cloud        spring-cloud-dependencies        Greenwich.SR1        pom        import            
           

完整的 pom.xml 檔案截圖如下:

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

修改服務A(logtrack-1)的 application.properties 為 application.yml 檔案,檔案内容如下所示:

spring:  application:    # 應用服務名    name: logtrack-1  cloud:    zookeeper:      # zk服務位址      connect-string: localhost:2181      discovery:        enabled: true        register: true        # 以ip形式注冊到zk服務上        instance-host: ${spring.cloud.client.ip-address}        # 在zk根節點下建立服務注冊路徑        root: /logservicesserver:  # 應用端口号  port: 9001
           

在 logtrack-1 服務的啟動類上加上注解 @EnableDiscoveryClient,然後啟動該服務,就能将該服務注冊到 zk 上,登入 zkui 管理系統即可檢視相應服務注冊情況,如下圖所示:

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

同上,修改 logtrack-2 工程(參考這篇文章搭建工程即可:小白都能看得懂的服務調用鍊路追蹤設計與實作),與 logtrack-1 工程的 pom.xml 檔案相同,application.yml 檔案内容修改如下:

spring:  application:    # 應用服務名    name: logtrack-2  cloud:    zookeeper:      # zk服務位址      connect-string: localhost:2181      discovery:        enabled: true        register: true        # 以ip形式注冊到zk服務上        instance-host: ${spring.cloud.client.ip-address}        # 在zk根節點下建立服務注冊路徑        root: /logservicesserver:  # 應用端口号  port: 9002
           

3.2 日志追蹤案例一 3.2.1 以不經過網關直接請求 logtrack-1 和 logtrack-2 服務為例 日志鍊路圖解如下所示:

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

① RestTemplate 用戶端實作日志鍊路追蹤

1)RestTemplate 常被用作 REST API 接口請求的用戶端,而實際發起每次請求之前都把請求頭 Header 相關資訊添加到 HttpEntity / RequestEntity 中,這樣實作的編碼會顯得十分備援。 正好,Spring 為我們提供了 ClientHttpRequestInterceptor 接口,可以對請求進行攔截,并在其被發送至被調用方接口服務之前修改請求或是增強相應的資訊。比如在 Header 中增加自定義字段 trace-id 等。 2)建立自定義的 RestTrackInterceptor.class 攔截器類 實作 ClientHttpRequestInterceptor 接口,然後将自定義攔截器類加到 RestTemplate 用戶端中。

  • RestTrackInterceptor.class 的編碼實作如下所示:
package com.smart4j.core.logtrack.interceptor;import org.springframework.http.HttpHeaders;import org.springframework.http.HttpRequest;import org.springframework.http.client.ClientHttpRequestExecution;import org.springframework.http.client.ClientHttpRequestInterceptor;import org.springframework.http.client.ClientHttpResponse;import org.springframework.stereotype.Component;import java.io.IOException;import java.util.Optional;import java.util.UUID;/** * @Description:  自定義請求攔截器 * @Param: * @return: * @Author: Mr.Zhang * @Date: 2020/4/15 */@Componentpublic class RestTrackInterceptor implements ClientHttpRequestInterceptor {    @Override    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {        HttpHeaders headers = request.getHeaders();        // 從線程局部共享變量 TRACE_ID_THREAD_LOCAL 中擷取traceId,如無則生成UUID替換        String traceId = Optional.ofNullable(LogTrackInterceptor.getTraceId()).orElse(UUID.randomUUID().toString().replaceAll("-",""));        // 請求頭傳遞參數:trace-id        headers.add("trace-id", traceId);        // 保證請求繼續被執行        return execution.execute(request, body);    }}
           

該類對應的包位置如下圖所示:

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

3)建立自己的 RestTemplate 用戶端 Bean,然後将前面建立的自定義請求攔截器類(RestTrackInterceptor.class)添加到 RestTemplate 中,并為了使其發送請求時具有負載均衡的能力,即加上@LoadBalanced 注解。 RestTemplateConfig 配置類代碼如下:

package com.smart4j.core.logtrack.config;import com.smart4j.core.logtrack.interceptor.RestTrackInterceptor;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cloud.client.loadbalancer.LoadBalanced;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.client.RestTemplate;import java.util.Collections;/** * @Description:  RestTemplate配置 * @Param: * @return: * @Author: Mr.Zhang * @Date: 2020/4/15 */@Configurationpublic class RestTemplateConfig {    @Autowired    private RestTrackInterceptor restTrackInterceptor;    @Bean    @LoadBalanced    public RestTemplate restTemplate() {        RestTemplate restTemplate = new RestTemplate();        // 把自定義的RestTrackInterceptor添加到RestTemplate,這裡可添加多個        restTemplate.setInterceptors(Collections.singletonList(restTrackInterceptor));        return restTemplate;    }}
           

該類對應的包位置下圖所示:

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

4)建立接口測試類 RestTemplateController.class,其中調用 logtrack-2 服務時,通過 zk 注冊的服務名進行調用即可,代碼如下:

package com.smart4j.core.logtrack.controller;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.client.RestTemplate;/** * @Description:  跨服務日志追蹤測試 * @Param: * @return: * @Author: Mr.Zhang * @Date: 2020/4/15 */@[email protected]@RequestMapping("/rest")public class RestTemplateController {    @Autowired    private RestTemplate restTemplate;    @GetMapping("/log")    public String logTrack(){        log.info("-----> Hi, info        log.warn("-----> Hi, warn         log.error("-----> Hi, error         String reqUrl = "http://logtrack-2/test/log";        log.info("-----> 調服務接口{}開始         ResponseEntity entity = restTemplate.getForEntity(reqUrl, String.class);        log.info("-----> 調服務接口{}結束         return null;    }}
           

5)基于 RestTemplate 用戶端的日志鍊路追蹤基本功能就已經實作了。然後在 logtrack-2 工程中也新增自定義攔截器和建立自定義的 RestTemplate 的Bean,和 logtrack-1 工程代碼及結構一樣,最後的工程結構如下圖所示:

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

logtrack-2 工程的接口測試類 RestTemplateController.class 代碼如下:

package com.smart4j.core.logtrack.controller;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.client.RestTemplate;/** * @Description:  跨服務日志追蹤測試 * @Param: * @return: * @Author: Mr.Zhang * @Date: 2020/4/15 */@[email protected]@RequestMapping("/rest")public class RestTemplateController {    @Autowired    private RestTemplate restTemplate;    @GetMapping("/log")    public String logTrack(){        log.info("-----> Hi, info        log.warn("-----> Hi, warn         log.error("-----> Hi, error         String reqUrl = "http://logtrack-1/test/log";        log.info("-----> 調服務接口{}開始         ResponseEntity entity = restTemplate.getForEntity(reqUrl, String.class);        log.info("-----> 調服務接口{}結束         return null;    }}
           

6)分别啟動 logtrack-1 和 logtrack-2 服務,啟動及注冊成功見下面 3 張圖:

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

7)通過 Postman 模拟浏覽器請求

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

點選 Send 後請求的日志如下圖所示:

  • 在 logtrack-1 服務中列印的日志資訊
[[[2020-04-19 16:21:11 | INFO  | 1234567890 | http-nio-9001-exec-3 | RestTemplateController.java:27 | com.smart4j.core.logtrack.controller.RestTemplateController : -----> Hi, info[[[2020-04-19 16:21:11 | WARN  | 1234567890 | http-nio-9001-exec-3 | RestTemplateController.java:28 | com.smart4j.core.logtrack.controller.RestTemplateController : -----> Hi, warn [[[2020-04-19 16:21:11 | ERROR | 1234567890 | http-nio-9001-exec-3 | RestTemplateController.java:29 | com.smart4j.core.logtrack.controller.RestTemplateController : -----> Hi, error [[[2020-04-19 16:21:11 | INFO  | 1234567890 | http-nio-9001-exec-3 | RestTemplateController.java:31 | com.smart4j.core.logtrack.controller.RestTemplateController : -----> 調服務接口http://logtrack-2/test/log開始 [[[2020-04-19 16:21:11 | INFO  | 1234567890 | http-nio-9001-exec-3 | RestTemplateController.java:33 | com.smart4j.core.logtrack.controller.RestTemplateController : -----> 調服務接口http://logtrack-2/test/log結束 
           
  • 在 logtrack-2 服務中列印的日志資訊
[[[2020-04-19 16:21:11 | INFO  | 1234567890 | http-nio-9002-exec-7 | LogTrackController.java:22 | com.smart4j.core.logtrack.controller.LogTrackController : -----> Nice meeting you, too, info [[[2020-04-19 16:21:11 | WARN  | 1234567890 | http-nio-9002-exec-7 | LogTrackController.java:23 | com.smart4j.core.logtrack.controller.LogTrackController : -----> Nice meeting you, too, warn [[[2020-04-19 16:21:11 | ERROR | 1234567890 | http-nio-9002-exec-7 | LogTrackController.java:24 | com.smart4j.core.logtrack.controller.LogTrackController : -----> Nice meeting you, too, error 
           
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

從上面的兩個服務的日志列印資訊可以看出,實作了跨服務的日志鍊路追蹤效果。 ② SpringCloud Feign — 申明式服務調用日志鍊路追蹤實作 1)雖然 RestTemplate 用戶端已經可以将請求攔截來實作對依賴服務的接口調用,并對 Http 請求進行封裝處理,形成一套模闆化的調用方法,但是對服務依賴的調用可能不隻一處,一個接口都會被多次調用,是以我們會像前面那樣針對各個微服務字形封裝一些用戶端接口調用類來包裝這些依賴服務的調用。 由于 RestTemplate 的封裝,幾乎每一個調用都是簡單的模闆化内容,Feign 在此基礎上做了進一步的封裝,由它來幫助我們定義和實作依賴服務接口的定義。 在服務消費者建立服務調用接口,通過 @FeignClient 注解指定服務名來綁定服務,然後再使用 SpringMVC 的注解來綁定具體該服務提供的 REST API 接口。 2)為了支援 feign 聲明式調用,在 pom.xml 檔案中加入相應的依賴如下:

org.springframework.cloud     spring-cloud-starter-openfeign 
           

3)這裡以 logtrack-1 工程作為 feign 的消費者,logtrack-2 工程作為 feign 服務的提供者。

  • 在 logtrack-2 工程下建立 FeignController 類作為 feign 服務提供者,代碼如下:
package com.smart4j.core.logtrack.controller;import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/** * @Description:  feign-跨服務日志追蹤測試 * @Param: * @return: * @Author: Mr.Zhang * @Date: 2020/4/15 */@[email protected]@RequestMapping("/feign")public class FeignController {    @GetMapping("/log")    public String logTrack(){        log.info("-----> Hi,feign, info        log.warn("-----> Hi,feign, warn         log.error("-----> Hi,feign, error         return "hello world";    }}
           
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign
  • SpringCloud 應用中,通過 feign 的方式實作 http 的調用,可以通過實作 feign.RequestInterceptor 接口在 feign 執行後進行攔截,對請求頭等資訊進行修改。在 logtrack-1 工程中新增 feign 聲明式調用的自定義攔截器(FeignTrackInterceptor.class),代碼如下所示:
package com.smart4j.core.logtrack.interceptor;import feign.RequestInterceptor;import feign.RequestTemplate;import java.util.Optional;import java.util.UUID;/** * @Description:  feign請求攔截器 * @Param: * @return: * @Author: Mr.Zhang * @Date: 2020/4/15 */public class FeignTrackInterceptor implements RequestInterceptor {    @Override    public void apply(RequestTemplate requestTemplate) {        // 從TRACE_ID_THREAD_LOCAL中擷取traceId,如無則生成UUID替換        String traceId = Optional.ofNullable(LogTrackInterceptor.getTraceId()).orElse(UUID.randomUUID().toString().replaceAll("-",""));        requestTemplate.header("trace-id", traceId);    }}
           
  • 在 logtrack-1 工程的 com.smart4j.core.logtrack.service 包下建立 FeignClient 請求的用戶端(FeignService.class),代碼如下所示:
package com.smart4j.core.logtrack.service;import com.smart4j.core.logtrack.interceptor.FeignTrackInterceptor;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.GetMapping;/** * @Description:  FeignClient用戶端 * @Param: * @return: * @Author: Mr.Zhang * @Date: 2020/4/15 */@FeignClient(value = "logtrack-2", configuration = FeignTrackInterceptor.class)public interface FeignService {    @GetMapping("/feign/log")    String logTrack();}
           

其中 @FeignClient 注解的 configuration = FeignTrackInterceptor.class 表示使用自定義的 FeignTrackInterceptor 攔截器。

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign
  • 最後在 logtrack-1 工程的啟動類上加上 @EnableFeignClients 注解,開啟聲明式調用,截圖如下:
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign
  • 重新啟動 logtrack-1 和 logtrack-2 服務,通過 Postman 模拟發送請求驗證 feign 用戶端請求的日志鍊路追蹤,見下圖:
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

備注:postman請求時沒有傳參數 trace-id 也可跟蹤請求

點選 Send 後發送 http 請求的日志如下圖所示:

  • 在 logtrack-1 服務中列印的日志資訊
[[[2020-04-19 16:47:11 | INFO  | d333d549b9264ac68c1fa72910d25087 | http-nio-9001-exec-4 | FeignController.java:26 | com.smart4j.core.logtrack.controller.FeignController : -----> Hi, info[[[2020-04-19 16:47:11 | WARN  | d333d549b9264ac68c1fa72910d25087 | http-nio-9001-exec-4 | FeignController.java:27 | com.smart4j.core.logtrack.controller.FeignController : -----> Hi, warn [[[2020-04-19 16:47:11 | ERROR | d333d549b9264ac68c1fa72910d25087 | http-nio-9001-exec-4 | FeignController.java:28 | com.smart4j.core.logtrack.controller.FeignController : -----> Hi, error [[[2020-04-19 16:47:11 | INFO  | d333d549b9264ac68c1fa72910d25087 | http-nio-9001-exec-4 | FeignController.java:29 | com.smart4j.core.logtrack.controller.FeignController : -----> 調logtrack-2 feign服務接口開始 [[[2020-04-19 16:47:11 | INFO  | d333d549b9264ac68c1fa72910d25087 | http-nio-9001-exec-4 | FeignController.java:31 | com.smart4j.core.logtrack.controller.FeignController : -----> 調logtrack-2 feign服務接口結束,結果為: hello world 
           
  • 在 logtrack-2 服務中列印的日志資訊
[[[2020-04-19 16:47:11 | INFO  | d333d549b9264ac68c1fa72910d25087 | http-nio-9002-exec-4 | FeignController.java:21 | com.smart4j.core.logtrack.controller.FeignController : -----> Hi,feign, info[[[2020-04-19 16:47:11 | WARN  | d333d549b9264ac68c1fa72910d25087 | http-nio-9002-exec-4 | FeignController.java:22 | com.smart4j.core.logtrack.controller.FeignController : -----> Hi,feign, warn [[[2020-04-19 16:47:11 | ERROR | d333d549b9264ac68c1fa72910d25087 | http-nio-9002-exec-4 | FeignController.java:23 | com.smart4j.core.logtrack.controller.FeignController : -----> Hi,feign, error 
           
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

3.2 日志追蹤案例二 3.2.1 經過網關路由通路 logtrack-1 和 logtrack-2 服務為例 1)日志鍊路圖解如下所示:

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

2)網關服務的搭建,整合 zuul 網關,在 pom.xml 檔案中添加 zuul 依賴:

org.springframework.cloud   spring-cloud-starter-netflix-zuul
           

完整的 pom.xml 檔案内容見下圖所示:

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

修改 application.yml 檔案内容如下:

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

log-zuul 工程和 logtrack-1、logtrack-2 工程結構内容,見下圖:

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

其中多了兩個過濾器類 TrackFilter 和 PostFilter,都繼承了 ZuulFilter;一個 CorsConfig 類,是請求跨域配置類;一個常量類 TrackConstants,即 TRACE_ID;一個擷取去除“-”的 UUID 工具類 CommonUtils.class;去除了攔截器 LogTrackInterceptor 和 CustomInterceptorConfig 相關設定。 新增的各個類的編碼如下:

  • 過濾器 TrackFilter.class,該過濾器是為了擷取前端應用的請求頭參數 trace-id,如無則生成 UUID 替換,具體編碼實作見截圖:
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate & Feign

(這裡使用過濾器代替了之前攔截器實作 traceId 日志追蹤功能)

  • 代碼清單如下: 
package com.smart4j.core.zuul.filter;import com.netflix.zuul.ZuulFilter;import com.netflix.zuul.context.RequestContext;import com.netflix.zuul.exception.ZuulException;import com.smart4j.core.zuul.constants.TrackConstants;import com.smart4j.core.zuul.util.CommonUtils;import lombok.extern.slf4j.Slf4j;import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;import java.util.Optional;/** * @Description: traceId過濾器 * @Param: * @return: * @Author: Mr.Zhang * @Date: 2020/4/15 */@[email protected] class TrackFilter extends ZuulFilter {    /**     * 存儲 traceId 的線程局部共享變量     */    private static final ThreadLocal TRACE_ID_THREAD_LOCAL = new ThreadLocal<>();    /**     * 設定過濾類型     * 四種不同生命周期的過濾器類型:pre,routing,error,post     * pre:主要用在路由映射的階段是尋找路由映射表的。     * routing:具體的路由轉發過濾器是在routing路由器,具體的請求轉發的時候會調用。     * error:一旦前面的過濾器出錯了,會調用error過濾器。     * post:當routing,error運作完後才會調用該過濾器,是在最後階段的。     * @return     */    @Override    public String filterType() {        // 聲明過濾器的類型為Pre        return FilterConstants.PRE_TYPE;    }    @Override    public int filterOrder() {        // 自定義過濾器執行的順序,數值越大越靠後執行,越小就越先執行        return -1;    }    /**     * 控制過濾器生效不生效,可以在裡控制是否執行過濾的具體邏輯     * @return     */    @Override    public boolean shouldFilter() {        // 傳回true,則執行下面的run()方法;反之,則不執行過濾具體邏輯        return true;    }    /**     * 執行過濾的具體邏輯:擷取請求頭中的trace-id(沒有則UUID生成)向下遊應用服務透傳     * @return     */    @Override    public Object run() throws ZuulException {        RequestContext ctx = RequestContext.getCurrentContext();        // 從上下文中拿到請求對象        HttpServletRequest request = ctx.getRequest();        // 擷取請求頭header中傳遞的trace-id        String traceId = request.getHeader(TrackConstants.TRACE_ID);        log.info("擷取請求頭header中傳遞的trace-id:{}", traceId);        // 若沒有traceId,則UUID代替        traceId = Optional.ofNullable(traceId).orElse(CommonUtils.getUUID());        // 請求前設定        TRACE_ID_THREAD_LOCAL.set(traceId);        // 通過上下文設定請求頭trace-id        ctx.addZuulRequestHeader(TrackConstants.TRACE_ID, traceId);        log.info("通過上下文設定請求頭trace-id:{}", traceId);        return null;    }    /**     * 擷取 TRACE_ID_THREAD_LOCAL 中存儲的 traceId     * @return     */    public static String getTraceId() {        return TRACE_ID_THREAD_LOCAL.get();    }    /**     * 清理 TRACE_ID_THREAD_LOCAL     * @return     */    public static void removeTraceId() {        TRACE_ID_THREAD_LOCAL.remove();    }}
           
  • PostFilter 過濾器類中手動調用 ThreadLocal 的 remove() 方法,防止記憶體洩漏,代碼如下:
package com.smart4j.core.zuul.filter;import com.netflix.zuul.ZuulFilter;import com.netflix.zuul.exception.ZuulException;import lombok.extern.slf4j.Slf4j;import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;import org.springframework.stereotype.Component;/** * @Description: Post過濾器 * @Param: * @return: * @Author: Mr.Zhang * @Date: 2020/4/15 */@[email protected] class PostFilter extends ZuulFilter {    @Override    public String filterType() {        return FilterConstants.POST_TYPE;    }    @Override    public int filterOrder() {        return -1;    }    @Override    public boolean shouldFilter() {        return true;    }    @Override    public Object run() throws ZuulException {        // 移除,防止記憶體洩漏        TrackFilter.removeTraceId();        return null;    }}
           
  • 自定義 TraceIdPatternConverter 日志轉換器類(traceId 改成從過濾器 TrackFilter 中擷取),代碼修改如下:
package com.smart4j.core.zuul.config;import ch.qos.logback.classic.pattern.ClassicConverter;import ch.qos.logback.classic.spi.ILoggingEvent;import com.smart4j.core.zuul.filter.TrackFilter;import org.springframework.util.StringUtils;/*** @Description:  自定義日志格式化* @Param:* @return:* @Author: Mr.Zhang* @Date: 2020/4/14*/public class TraceIdPatternConverter extends ClassicConverter {  @Override  public String convert(ILoggingEvent iLoggingEvent) {    // 從過濾器中擷取TRACE_ID_THREAD_LOCAL存儲的 traceId    String traceId = TrackFilter.getTraceId();    return StringUtils.isEmpty(traceId) ? "traceId" : traceId;  }}
           
  • 跨域請求配置類 CorsConfig.class(為了統一在網關入口解決前端應用 Ajax 請求浏覽器同源政策導緻的跨域問題)代碼清單如下:
package com.smart4j.core.zuul.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.cors.CorsConfiguration;import org.springframework.web.cors.UrlBasedCorsConfigurationSource;import org.springframework.web.filter.CorsFilter;/** * @Description: 跨域配置類 * @Param: * @return: * @Author: Mr.Zhang * @Date: 2020/4/15 */@Configurationpublic class CorsConfig {    private CorsConfiguration buildConfig() {        CorsConfiguration corsConfiguration = new CorsConfiguration();        // 允許cookie跨域        corsConfiguration.setAllowCredentials(true);        // 允許任何域名使用        corsConfiguration.addAllowedOrigin("*");        // 允許任何頭        corsConfiguration.addAllowedHeader("*");        // 允許任何方法(post、get等)        corsConfiguration.addAllowedMethod("*");        // 設定跨域緩存時間,機關為秒        corsConfiguration.setMaxAge(300L);        return corsConfiguration;    }    @Bean    public CorsFilter corsFilter() {        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();        // 對接口配置跨域設定        source.registerCorsConfiguration("/**", buildConfig());        return new CorsFilter(source);    }}
           
  • 常量類 TrackConstants.class,請求頭參數變量設定,代碼清單如下:
package com.smart4j.core.zuul.constants;/** * @Description: 常量類 * @Param: * @return: * @Author: Mr.Zhang * @Date: 2020/4/15 */public class TrackConstants {    /**     * 請求頭參數 trace-id     */    public static final String TRACE_ID = "trace-id";}
           
  • 工具類 CommonUtils.class,代碼如下:
package com.smart4j.core.zuul.util;import java.util.UUID;/** * @Description: 公共工具類 * @Param: * @return: * @Author: Mr.Zhang * @Date: 2020/4/15 */public class CommonUtils {    /**     * 擷取除去“-”的UUID     * @return     */    public static String getUUID() {        return UUID.randomUUID().toString().replaceAll("-","");    }}
           
  • log-zuul 工程啟動類上添加注解 @EnableZuulProxy,開啟 zuul 網關的功能,如下圖所示:
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate &amp;amp; Feign
  • log-zuul 工程:網關層實作了基本的跨域請求、路由及過濾器處理的相關功能,這裡不細說 zuul 網關是什麼,怎麼用的,這個百度有很多博文介紹。
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate &amp;amp; Feign

上面是成功啟動 log-zuul 服務的截圖,通路 zkui 管理系統,如下:

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate &amp;amp; Feign

說明在  zk 上成功注冊了 log-zuul、logtrack-1、logtrack-2 三個服務。

現在開始用 Postman 模拟請求測試,通過網關 log-zuul 請求路由到下遊服務 logtrack-1、logtrack-2上。

  • Postman 中請求頭設定了 trace-id=11223344556677889900,發送GET請求。
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate &amp;amp; Feign

請求結果如下:

log-zuul 服務列印的日志如下:

[[[2020-04-19 22:31:06 | INFO  | traceId | http-nio-9000-exec-7 | TrackFilter.java:72 | com.smart4j.core.zuul.filter.TrackFilter : 擷取請求頭header中傳遞的trace-id:11223344556677889900]]][[[2020-04-19 22:31:06 | INFO  | 11223344556677889900 | http-nio-9000-exec-7 | TrackFilter.java:80 | com.smart4j.core.zuul.filter.TrackFilter : 通過上下文設定請求頭trace-id:11223344556677889900]]]
           

logtrack-1 服務列印的日志如下:

[[[2020-04-19 22:31:06 | INFO  | 11223344556677889900 | http-nio-9001-exec-1 | RestTemplateController.java:27 | com.smart4j.core.logtrack.controller.RestTemplateController : -----> Hi, info[[[2020-04-19 22:31:06 | WARN  | 11223344556677889900 | http-nio-9001-exec-1 | RestTemplateController.java:28 | com.smart4j.core.logtrack.controller.RestTemplateController : -----> Hi, warn [[[2020-04-19 22:31:06 | ERROR | 11223344556677889900 | http-nio-9001-exec-1 | RestTemplateController.java:29 | com.smart4j.core.logtrack.controller.RestTemplateController : -----> Hi, error [[[2020-04-19 22:31:06 | INFO  | 11223344556677889900 | http-nio-9001-exec-1 | RestTemplateController.java:31 | com.smart4j.core.logtrack.controller.RestTemplateController : -----> 調服務接口http://logtrack-2/test/log開始 [[[2020-04-19 22:31:06 | INFO  | 11223344556677889900 | http-nio-9001-exec-1 | RestTemplateController.java:33 | com.smart4j.core.logtrack.controller.RestTemplateController : -----> 調服務接口http://logtrack-2/test/log結束 
           

logtrack-2 服務列印的日志如下:

[[[2020-04-19 22:31:06 | INFO  | 11223344556677889900 | http-nio-9002-exec-6 | LogTrackController.java:22 | com.smart4j.core.logtrack.controller.LogTrackController : -----> Nice meeting you, too, info [[[2020-04-19 22:31:06 | WARN  | 11223344556677889900 | http-nio-9002-exec-6 | LogTrackController.java:23 | com.smart4j.core.logtrack.controller.LogTrackController : -----> Nice meeting you, too, warn [[[2020-04-19 22:31:06 | ERROR | 11223344556677889900 | http-nio-9002-exec-6 | LogTrackController.java:24 | com.smart4j.core.logtrack.controller.LogTrackController : -----> Nice meeting you, too, error 
           

3 個服務的日志截圖如下:

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate &amp;amp; Feign
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate &amp;amp; Feign
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate &amp;amp; Feign

從以上 3 個服務的結果來看,從前端發送請求到結束,traceId 由網關 log-zuul 服務、到 logtrack-1 服務、再到 logtrack-2 服務,完成了日志鍊路追蹤的功能。

  • Postman 中請求頭沒有設定 trace-id 參數,發送GET請求。結果如下:
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate &amp;amp; Feign
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate &amp;amp; Feign
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate &amp;amp; Feign
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate &amp;amp; Feign

通過以上的結果可知,前端請求頭中沒有傳遞 trace-id,背景自動生成 UUID 替換,實作網關到下遊服務的全鍊路追蹤。

到這裡全鍊路日志追蹤實作和測試案例已經介紹完了,是不是很簡單呢 

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate &amp;amp; Feign

4. 小結

本文建構了三個 Spring Boot 輕量級微服務系統,一個網關服務和兩個下遊接口服務,Step By Step 模拟實作了兩種鍊路追蹤案例,一種直接請求接口服務提供方,另一種是通過網關轉發到接口服務提供方(推薦)。這兩種方式實作的分布式系統跨服務調用全鍊路日志追蹤的思路差不多。思路很重要!思路很重要!思路很重要!思路很重要!(重要的話說四遍 

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate &amp;amp; Feign

)

小夥伴們會了嗎?趕緊動手實踐一下吧 

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate &amp;amp; Feign

加油

# 精彩推薦 #

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate &amp;amp; Feign

小白都能看得懂的服務調用鍊路追蹤設計與實作

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate &amp;amp; Feign

  事務看完這篇你隻能算入門

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate &amp;amp; Feign

  微服務架構中你必須了解的 CAP 原理

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate &amp;amp; Feign

 趣談微服務之點-線-面關系

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate &amp;amp; Feign

  [三步法] 可視化分析定位線上 JVM 問題

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate &amp;amp; Feign

  從 Java 代碼如何運作聊到 JVM 和對象的建立-配置設定-定位-布局-垃圾回收

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate &amp;amp; Feign

  記一次生産頻繁出現 Full GC 的 GC日志圖文詳解

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate &amp;amp; Feign

點個"在看"在走吧,分享和收藏是最大的支援

resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate &amp;amp; Feign
resttemplate 設定請求頭_分布式服務全鍊路日志追蹤實戰之 RestTemplate &amp;amp; Feign