简体   繁体   English

定义 RestTemplateBuilder bean 会导致跨服务边界的 traceId 传播不再有效

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

The following code demonstrates that by including a RestTemplateBuilder bean into a project, micrometer-tracing will not propagate traceId s correctly across service boundaries.以下代码演示了通过将RestTemplateBuilder bean 包含到项目中, micrometer-tracing不会跨服务边界正确传播traceId

I am posting the sample code here as my client prohibits me from sharing code via GitHub.我在这里发布示例代码,因为我的客户禁止我通过 GitHub 共享代码。

Create two Spring Maven projects, demo-client and demo-server, referencing the code below, then run the two scripts to get the microservices running.创建两个 Spring Maven 项目,demo-client 和 demo-server,参考下面的代码,然后运行这两个脚本让微服务运行起来。 Then, send a GET request to http://localhost:2048/tracer-response and you'll see in the demo-client logs that the traceId s match.然后,向http://localhost:2048/tracer-response发送 GET 请求,您将在演示客户端日志中看到traceId匹配。 Next, uncomment the RestTemplateBuilder bean in DemoClientApplication and re-run the scripts.接下来,取消注释DemoClientApplication中的RestTemplateBuilder bean 并重新运行脚本。 Retest, and you'll see that the traceId s no longer match.重新测试,您会发现traceId不再匹配。

Please advise as to why this configuration no longer works.请告知为什么此配置不再有效。 It previously worked with Spring Boot 2.7.7 and Spring Cloud 2021.0.5.它之前使用 Spring Boot 2.7.7 和 Spring Cloud 2021.0.5。 If the intent from the Spring team is for this configuration to no longer be used and this is pointed out in the documentation, then I apologize for missing it.如果 Spring 团队的意图是不再使用此配置并且在文档中指出了这一点,那么我为错过它而道歉。 If it isn't pointed out, then in my opinion this should be highlighted as a warning, as it clearly negates the whole purpose behind using distributed tracing.如果没有指出,那么在我看来,这应该被强调为警告,因为它清楚地否定了使用分布式跟踪背后的全部目的。

demo-client code演示客户端代码

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 src/main/resources/application.yml

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

src/main/resources/logback-spring.xml 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 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>

demo-server code演示服务器代码

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 src/main/resources/application.yml

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

src/main/resources/logback-spring.xml 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 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>

scripts to manage and run the microservices管理和运行微服务的脚本

kill_demo_server.sh 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 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

Not clear by your question why do you need a custom RestTemplateBuilder bean, but I believe there are some use-case anyway.你的问题不清楚为什么你需要一个自定义的RestTemplateBuilder bean,但我相信无论如何都有一些用例。

So, let's see if making that bean fully similar to what Spring Boot auto-configuration does helps to solve your requirements!因此,让我们看看是否使该 bean 完全类似于 Spring 引导自动配置所做的有助于解决您的要求!

See RestTemplateAutoConfiguration :请参阅RestTemplateAutoConfiguration

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

Therefore your custom bean should be similar:因此,您的自定义 bean 应该类似:

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

That RestTemplateBuilderConfigurer delegates to respective customizers. RestTemplateBuilderConfigurer委托给相应的定制器。 At the moment I see this impls in my classpath:目前我在我的类路径中看到这个 impls:

MockServerRestTemplateCustomizer
ObservationRestTemplateCustomizer
TraceRestTemplateCustomizer

Where ObservationRestTemplateCustomizer is the one which instruments a RestTempalte with micrometer-tracing.其中ObservationRestTemplateCustomizer是使用测微计跟踪检测RestTempalte的工具。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 RestTemplateBuilder bean - RestTemplateBuilder bean 如何使用 WebTestClient + Micrometer Tracer 测试 traceId 传播? - How to test traceId propagation with WebTestClient + Micrometer Tracer? 定义微服务边界 - Defining Microservice boundaries 跨项目自动装配时钟会导致“未找到此类豆”异常 - Autowiring clock across projects causes 'No Such Bean found' exception 考虑在您的配置中定义一个“服务”类型的 bean [Spring boot] - Consider defining a bean of type 'service' in your configuration [Spring boot] EnableEurekaClient无法与EnableOAuth2Sso一起使用。 它导致springSecurityFilterChain bean AlreadyBuiltException - EnableEurekaClient cannot work with EnableOAuth2Sso. It causes springSecurityFilterChain bean AlreadyBuiltException Spring 引导定义一个 Bean - Spring Boot Defining A Bean 定义类型 bean 的问题 - Issue in defining a bean of type 考虑在您的配置中定义“com.fsse2207.project_backend.service.ProductService”类型的 bean - Consider defining a bean of type 'com.fsse2207.project_backend.service.ProductService' in your configuration 考虑在您的配置中定义一个类型为 &#39;org.o7planning.sbshoppingcart.service.UserDetailsS​​erviceImpl&#39; 的 bean - Consider defining a bean of type 'org.o7planning.sbshoppingcart.service.UserDetailsServiceImpl' in your configuration
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM