簡體   English   中英

定義 RestTemplateBuilder bean 會導致跨服務邊界的 traceId 傳播不再有效

[英]Defining a RestTemplateBuilder bean causes traceId propagation across service boundaries to no longer work

以下代碼演示了通過將RestTemplateBuilder bean 包含到項目中, micrometer-tracing不會跨服務邊界正確傳播traceId

我在這里發布示例代碼,因為我的客戶禁止我通過 GitHub 共享代碼。

創建兩個 Spring Maven 項目,demo-client 和 demo-server,參考下面的代碼,然后運行這兩個腳本讓微服務運行起來。 然后,向http://localhost:2048/tracer-response發送 GET 請求,您將在演示客戶端日志中看到traceId匹配。 接下來,取消注釋DemoClientApplication中的RestTemplateBuilder bean 並重新運行腳本。 重新測試,您會發現traceId不再匹配。

請告知為什么此配置不再有效。 它之前使用 Spring Boot 2.7.7 和 Spring Cloud 2021.0.5。 如果 Spring 團隊的意圖是不再使用此配置並且在文檔中指出了這一點,那么我為錯過它而道歉。 如果沒有指出,那么在我看來,這應該被強調為警告,因為它清楚地否定了使用分布式跟蹤背后的全部目的。

演示客戶端代碼

package com.example.democlient;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@Slf4j
public class DemoClientApplication {

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

  @Bean
  public RestTemplate restTemplate(RestTemplateBuilder builder) {
    return builder.build();
  }

  // Uncomment the bean definition below, and traceId propagation will no longer work, meaning the traceId
  // values logged by demo-client and demo-server will not be the same value.
/*  @Bean
  public RestTemplateBuilder restTemplateBuilder() {
    return new RestTemplateBuilder();
  }*/
}
package com.example.democlient;

import io.micrometer.tracing.Tracer;
import jakarta.annotation.security.PermitAll;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@Slf4j
public class DemoClientController {

  @Autowired RestTemplate restTemplate;
  @Autowired Tracer tracer;

  @PermitAll
  @GetMapping(value = "/tracer-response", produces = MediaType.APPLICATION_JSON_VALUE)
  public TracerResponse getClientResponse() {
    log.info("Getting the tracer response from demo server...");
    var clientTraceId = tracer.currentSpan().context().traceId();
    var clientSpanId = tracer.currentSpan().context().spanId();
    log.info(
        "Here's the traceId and spanId before the call: {} and {}", clientTraceId, clientSpanId);
    var serverTracerResponse =
        restTemplate
            .getForEntity("http://localhost:2049/tracer-response", TracerResponse.class)
            .getBody();
    var serverTraceId = serverTracerResponse.getTraceId();
    var serverSpanId = serverTracerResponse.getSpanId();
    log.info(
        "Here's the traceId and spanId from the server: {} and {}", serverTraceId, serverSpanId);
    log.info("Do the client and server traceIds match? {}", clientTraceId.equals(serverTraceId));
    return serverTracerResponse;
  }
}

package com.example.democlient;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;

@JsonInclude(JsonInclude.Include.NON_NULL)
@Data
public class TracerResponse {
  private String spanId;
  private String traceId;
}

src/main/resources/application.yml

server:
  port: 2048
spring:
  application:
   name: demo-client

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"/>
    <property name="LOG_PATTERN"
              value="%d{yyyy-MM-dd, America/New_York} | %d{HH:mm:ss.SSS, America/New_York} | %-16.16thread | %-5.5p | %-40.40logger{40} | %-39.39(Trace: %X{traceId}) | %-22.22(Span: %X{spanId}) | %m%n"/>

    <appender name="console"
              class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <logger name="com.example" level="DEBUG" additivity="false">
        <appender-ref ref="console"/>
    </logger>

    <logger name="org.springframework" level="WARN"
            additivity="false">
        <appender-ref ref="console"/>
    </logger>

    <root level="WARN">
        <appender-ref ref="console"/>
    </root>

</configuration>

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>3.0.1</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    <groupId>com.example</groupId>
    <artifactId>demo-client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo-client</name>
    <description>Demo project for Spring Boot</description>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.logging.log4j</groupId>
                    <artifactId>log4j-to-slf4j</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-tracing-bridge-otel</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

演示服務器代碼

package com.example.demoserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoServerApplication {

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

}
package com.example.demoserver;

import io.micrometer.tracing.Tracer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
public class DemoServerController {

    @Autowired
    Tracer tracer;

    @GetMapping(value = "/tracer-response", produces = MediaType.APPLICATION_JSON_VALUE)
    public TracerResponse getClientResponse() {
        log.info("Getting the serverResponse...");
        log.info("traceId: {} spanId: {}",tracer.currentSpan().context().traceId(),tracer.currentSpan().context().spanId());
        var response = new TracerResponse();
        response.setTraceId(tracer.currentSpan().context().traceId());
        response.setSpanId(tracer.currentSpan().context().spanId());
        return response;
    }
}
package com.example.demoserver;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;

@JsonInclude(JsonInclude.Include.NON_NULL)
@Data
public class TracerResponse {
    private String traceId;
    private String spanId;
}

src/main/resources/application.yml

server:
  port: 2049
spring:
  application:
    name: demo-server

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"/>
    <property name="LOG_PATTERN"
              value="%d{yyyy-MM-dd, America/New_York} | %d{HH:mm:ss.SSS, America/New_York} | %-16.16thread | %-5.5p | %-40.40logger{40} | %-39.39(Trace: %X{traceId}) | %-22.22(Span: %X{spanId}) | %m%n"/>

    <appender name="console"
              class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <logger name="com.example" level="DEBUG" additivity="false">
        <appender-ref ref="console"/>
    </logger>

    <logger name="org.springframework" level="WARN"
            additivity="false">
        <appender-ref ref="console"/>
    </logger>

    <root level="WARN">
        <appender-ref ref="console"/>
    </root>

</configuration>

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo-server</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.logging.log4j</groupId>
                    <artifactId>log4j-to-slf4j</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-properties-migrator</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-tracing-bridge-otel</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

管理和運行微服務的腳本

kill_demo_server.sh

#! /bin/bash
set -e
set -x

ps -ef | grep 'demo-server-0.0.1' | kill -9 $(awk 'NR==1{print $2}') 2>/dev/null;

build_deploy_demos.sh

#! /bin/bash
set -e
set -x

cd ~/repos/demo-server;
mvn clean install;
nohup java -jar target/demo-server-0.0.1-SNAPSHOT.jar &
cd ~/repos/demo-client;
mvn clean install;
java -jar target/demo-client-0.0.1-SNAPSHOT.jar

你的問題不清楚為什么你需要一個自定義的RestTemplateBuilder bean,但我相信無論如何都有一些用例。

因此,讓我們看看是否使該 bean 完全類似於 Spring 引導自動配置所做的有助於解決您的要求!

請參閱RestTemplateAutoConfiguration

@Bean
@Lazy
@ConditionalOnMissingBean
public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer restTemplateBuilderConfigurer) {
    RestTemplateBuilder builder = new RestTemplateBuilder();
    return restTemplateBuilderConfigurer.configure(builder);
}

因此,您的自定義 bean 應該類似:

  @Bean
  public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer restTemplateBuilderConfigurer) {
       RestTemplateBuilder builder = new RestTemplateBuilder();
       return restTemplateBuilderConfigurer.configure(builder);
  }

RestTemplateBuilderConfigurer委托給相應的定制器。 目前我在我的類路徑中看到這個 impls:

MockServerRestTemplateCustomizer
ObservationRestTemplateCustomizer
TraceRestTemplateCustomizer

其中ObservationRestTemplateCustomizer是使用測微計跟蹤檢測RestTempalte的工具。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM