1. 程式人生 > 程式設計 >ApiBoot Logging使用SpringCloud Openfeign透傳鏈路資訊

ApiBoot Logging使用SpringCloud Openfeign透傳鏈路資訊

ApiBoot Logging可以無縫整合SpringCloud來採集請求日誌,目前支援RestTemplateOpenfeign兩種方式,我們本章來講解下在使用Openfeign完成服務之間請求相互呼叫的一條鏈路請求日誌是否可以都採集到。

部落格原文地址:blog.yuqiyu.com/apiboot-log…

搭建Eureka Server

我們先來搭建一個Eureka Server,請訪問【搭建服務註冊中心Eureka Server】文章內容檢視具體搭建流程。

搭建Logging Admin

我們需要搭建一個Logging Admin用於接收Logging Client上報的請求日誌,請訪問【

ApiBoot Logging整合SpringCloud Eureka負載均衡上報日誌】檢視具體的搭建流程。

我們本章來模擬提交訂單的業務邏輯,涉及到兩個服務,分別是:商品服務訂單服務,接下來我們需要來建立這兩個服務。

本章原始碼採用Maven多模組的形式進行編寫,請拉至文章末尾檢視下載本章原始碼。

新增ApiBoot & SpringCloud統一版本

由於是採用Maven 多模組專案,存在繼承關係,我們只需要在root模組新增版本依賴即可,其他子模組就可以直接使用,如下所示:

<properties>
  <java.version>1.8</java.version
>
<!--ApiBoot版本號--> <api.boot.version>2.1.5.RELEASE</api.boot.version> <!--SpringCloud版本號--> <spring.cloud.version>Greenwich.SR3</spring.cloud.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId
>
org.minbox.framework</groupId> <artifactId>api-boot-dependencies</artifactId> <version>${api.boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring.cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> 複製程式碼

建立公共Openfeign介面定義

學習過Openfeign的同學應該都知道,Openfeign可以繼承實現,我們只需要建立一個公共的服務介面定義,在實現該介面的服務進行業務實現,在呼叫該介面的地方直接注入即可。 下面我們建立一個名為common-openfeign的公共依賴專案,pom.xml新增依賴如下所示:

<dependencies>
  <!--SpringBoot Web-->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <optional>true</optional>
  </dependency>
  <!--Openfeign-->
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    <optional>true</optional>
  </dependency>
</dependencies>
複製程式碼

在提交訂單時我們簡單模擬需要獲取商品的單價,所以在common-openfeign專案內我們要提供一個查詢商品單價的服務介面,建立一個名為GoodClient的介面如下所示:

package org.minbox.chapter.common.openfeign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * 商品服務介面定義
 *
 * @author 恆宇少年
 */
@FeignClient(name = "good-service")
@RequestMapping(value = "/good")
public interface GoodClient {
    /**
     * 獲取商品價格
     *
     * @param goodId 商品編號
     * @return
     */
    @GetMapping(value = "/{goodId}")
    Double getGoodPrice(@PathVariable("goodId") Integer goodId);
}
複製程式碼

註解解釋:

  • @FeignClientSpringCloud Openfeign提供的介面客戶端定義註解,通過value或者name來指定GoodClient訪問的具體ServiceID,這裡我們配置的value值為good-service專案spring.application.name配置引數(ServiceID = spring.application.name)。

這樣當我們通過注入GoodClient介面呼叫getGoodPrice方法時,底層通過OpenfeignHttp代理訪問good-service的對應介面。

建立商品服務

下面我們再來建立一個名為good-serviceSpringBoot專案。

新增相關依賴

pom.xml專案配置檔案內新增如下依賴:

<dependencies>
  <!--ApiBoot Logging Client-->
  <dependency>
    <groupId>org.minbox.framework</groupId>
    <artifactId>api-boot-starter-logging</artifactId>
  </dependency>

  <!--SpringBoot Web-->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <!--Eureka Client-->
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  </dependency>

  <!--公共Openfeign介面定義依賴-->
  <dependency>
    <groupId>org.minbox.chapter</groupId>
    <artifactId>common-openfeign</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </dependency>
</dependencies>
複製程式碼

可以看到我們在good-service專案依賴內添加了我們在上面建立的common-openfeign依賴模組,因為GoodClient服務介面的實現是在good-service專案內,我們需要新增common-openfeign依賴後建立對應的XxxController並實現GoodClient介面完成對應的業務邏輯實現。

商品業務實現

這裡我們簡單做個示例,將價格固定返回,實現GoodClient的控制器如下所示:

package org.minbox.chapter.good.service;

import org.minbox.chapter.common.openfeign.GoodClient;
import org.springframework.web.bind.annotation.RestController;

/**
 * 商品服務介面實現
 *
 * @author 恆宇少年
 * @see GoodClient
 */
@RestController
public class GoodController implements GoodClient {
    @Override
    public Double getGoodPrice(Integer goodId) {
        if (goodId == 1) {
            return 15.6;
        }
        return 0D;
    }
}
複製程式碼

註冊到Eureka Server

我們需要將good-service註冊到Eureka Server,修改application.yml配置檔案如下所示:

# ServiceID
spring:
  application:
    name: good-service
# 埠號
server:
  port: 8082
# Eureka Config
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10000/eureka/
  instance:
    prefer-ip-address: true
複製程式碼

配置上報的Logging Admin

我們需要將good-service的請求日誌上報到Logging Admin,採用SpringCloud ServiceID的方式配置,修改application.yml配置檔案如下所示:

api:
  boot:
    logging:
      # 控制檯列印日誌
      show-console-log: true
      # 美化列印日誌
      format-console-log-json: true
      # 配置Logging Admin 服務編號
      discovery:
        service-id: logging-admin
複製程式碼

啟用Eureka Client & Logging

最後我們在XxxApplication入口類新增註解來啟用Eureka Client以及Logging Client,如下所示:

/**
 * 商品服務
 *
 * @author 恆宇少年
 */
@SpringBootApplication
@EnableLoggingClient
@EnableDiscoveryClient
public class GoodServiceApplication {
    /**
     * logger instance
     */
    static Logger logger = LoggerFactory.getLogger(GoodServiceApplication.class);

    public static void main(String[] args) {
        SpringApplication.run(GoodServiceApplication.class,args);
        logger.info("{}服務啟動成功.","商品");
    }
}
複製程式碼

至此我們的商品服務已經準備完成.

建立訂單服務

建立一個名為order-serviceSpringBoot專案(建議參考原始碼,本章採用Maven多模組建立)。

新增相關依賴

修改pom.xml新增相關依賴如下所示:

<dependencies>
  <!--ApiBoot Logging Client-->
  <dependency>
    <groupId>org.minbox.framework</groupId>
    <artifactId>api-boot-starter-logging</artifactId>
  </dependency>

  <!--SpringBoot Web-->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <!--Eureka Client-->
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  </dependency>

  <!--Openfeign-->
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
  </dependency>

  <!--公共Openfeign介面定義依賴-->
  <dependency>
    <groupId>org.minbox.chapter</groupId>
    <artifactId>common-openfeign</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </dependency>
</dependencies>
複製程式碼

訂單業務實現

我們來模擬一個提交訂單的場景,建立一個名為OrderController的控制器,如下所示:

/**
 * 訂單控制器
 *
 * @author 恆宇少年
 */
@RestController
@RequestMapping(value = "/order")
public class OrderController {
    /**
     * 商品介面定義注入
     * {@link GoodClient}
     */
    @Autowired
    private GoodClient goodClient;

    @PostMapping
    public String submit(Integer goodId,Integer buyCount) {
        Double goodPrice = goodClient.getGoodPrice(goodId);
        Double orderAmount = goodPrice * buyCount;
        //...
        return "訂單提交成功,訂單總金額:" + orderAmount;
    }
}
複製程式碼

註冊到Eureka Server

將我們建立的order-service註冊到Eureka Server,修改application.yml配置檔案如下所示:

spring:
  application:
    name: order-service
server:
  port: 8081
# Eureka Config
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10000/eureka/
  instance:
    prefer-ip-address: true
複製程式碼

配置上報的Logging Admin

我們需要將order-service的請求日誌上報到Logging Admin,採用SpringCloud ServiceID的方式配置,修改application.yml配置檔案如下所示:

api:
  boot:
    logging:
      # 控制檯列印日誌
      show-console-log: true
      # 美化列印日誌
      format-console-log-json: true
      # 配置Logging Admin 服務編號
      discovery:
        service-id: logging-admin
複製程式碼

啟用Eureka Client & Logging

修改order-service入口類OrderServiceApplication,新增啟用Eureka ClientLogging Client的註解,如下所示:

/**
 * 訂單服務
 *
 * @author 恆宇少年
 */
@SpringBootApplication
@EnableDiscoveryClient
@EnableLoggingClient
@EnableFeignClients(basePackages = "org.minbox.chapter.common.openfeign")
public class OrderServiceApplication {
    /**
     * logger instance
     */
    static Logger logger = LoggerFactory.getLogger(OrderServiceApplication.class);

    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class,"");
    }
}
複製程式碼

註解解釋:

  • @EnableFeignClients:該註解是Openfeign提供的啟用自動掃描Client的配置,我們通過basePackages(基礎包名)的方式進行配置掃描包下配置@FeignClient註解的介面,併為每個介面生成對應的代理實現並新增到Spring IOC容器。

    org.minbox.chapter.common.openfeign包名在common-openfeign專案內。

執行測試

依次啟動專案,eureka-server > logging-admin > good-service > order-service

通過curl命令訪問order-service內的提交訂單地址:/order,如下所示:

➜ ~ curl -X POST http://localhost:8081/order\?goodId\=1\&buyCount\=3
訂單提交成功,訂單總金額:46.8
複製程式碼

可以看到我們已經可以成功的獲取訂單的總金額,我們在/order請求方法內呼叫good-service獲取商品的單價後計算得到訂單總金額。

測試點:鏈路資訊傳遞

我們通過控制檯輸出的日誌資訊來確認下鏈路資訊(traceId、spanId)的透傳是否正確。

收到order-service上報的日誌

Receiving Service: 【order-service -> 127.0.0.1】,Request Log Report,Logging Content:[
	{
		"endTime":1573009439840,"httpStatus":200,"requestBody":"","requestHeaders":{
			"host":"localhost:8081","user-agent":"curl/7.64.1","accept":"*/*"
		},"requestIp":"0:0:0:0:0:0:0:1","requestMethod":"POST","requestParam":"{\"buyCount\":\"3\",\"goodId\":\"1\"}","requestUri":"/order","responseBody":"訂單提交成功,訂單總金額:46.8","responseHeaders":{},"serviceId":"order-service","serviceIp":"127.0.0.1","servicePort":"8081","spanId":"241ef717-b0b3-4fcc-adae-b63ffd3dbbe4","startTime":1573009439301,"timeConsuming":539,"traceId":"3e20cc72-c880-4575-90ed-d54a6b4fe555"
	}
]
複製程式碼

收到good-service上報的日誌

Receiving Service: 【good-service -> 127.0.0.1】,Request Log Report,Logging Content:[
	{
		"endTime":1573009439810,"parentSpanId":"241ef717-b0b3-4fcc-adae-b63ffd3dbbe4","requestHeaders":{
			"minbox-logging-x-parent-span-id":"241ef717-b0b3-4fcc-adae-b63ffd3dbbe4","minbox-logging-x-trace-id":"3e20cc72-c880-4575-90ed-d54a6b4fe555","host":"10.180.98.156:8082","connection":"keep-alive","accept":"*/*","user-agent":"Java/1.8.0_211"
		},"requestIp":"10.180.98.156","requestMethod":"GET","requestParam":"{}","requestUri":"/good/1","responseBody":"15.6","serviceId":"good-service","servicePort":"8082","spanId":"6339664e-097d-4a01-a734-935de52a7d44","startTime":1573009439787,"timeConsuming":23,"traceId":"3e20cc72-c880-4575-90ed-d54a6b4fe555"
	}
]
複製程式碼

結果分析:

  • 請求日誌的入口為order-service所以並不存在parentSpanId(上級單元編號),而spanId(單元編號)、traceId(鏈路編號)也是新生成的。

  • 本次請求會經過good-service服務,因此parentSpanId則是order-service生成的spanIdtraceId同樣也是order-service生成的,透傳HttpHeader方式進行傳遞,表示在同一條請求鏈路上。

敲黑板,劃重點

ApiBoot Logging支援使用Openfeign傳遞鏈路資訊,內部通過Openfeign攔截器實現,原始碼詳見:org.minbox.framework.logging.client.http.openfeign.LoggingOpenFeignInterceptor

traceId(鏈路編號)、parentSpanId(單元編號)通過HttpHeader的形式傳遞到目標訪問服務,服務通過請求日誌攔截器進行提取並設定鏈路繫結關係。

  • traceId傳遞時HttpHeader名稱為:minbox-logging-x-trace-id.
  • parentSpanId傳遞時HttpHeader名稱為:minbox-logging-x-parent-span-id

程式碼示例

如果您喜歡本篇文章請為原始碼倉庫點個Star,謝謝!!! 本篇文章示例原始碼可以通過以下途徑獲取,目錄為SpringBoot2.x/apiboot-logging-using-openfeign-transparent-traceid

作者個人 部落格 使用開源框架 ApiBoot 助你成為Api介面服務架構師