整合Spring Cloud Sleuth编码实践

基于Spring Cloud Sleuth的链路追踪实现思路

笔者将创建三个微服务实例工程,这里直接使用第 8 章的源码 spring-cloud-alibaba-openfeign-demo 项目,该项目的调用链路是 order-service 通过 OpenFeign 组件分别调用 shopcart-servicegoods-service,如图 12-3 所示。

为了组件整合和演示需要,笔者将调用链路改为 order-service 通过 OpenFeign 组件调用 shopcart-serviceshopcart-service 通过 OpenFeign 组件调用 goods-service,如图 12-4 所示。

image 2025 04 18 13 30 38 684
Figure 1. 图12-3 原调用链路示意图
image 2025 04 18 13 31 36 860
Figure 2. 图12-4 本节代码所需的调用链路示意图

代码基础改造

  1. 修改项目内容。

    修改项目名称为 spring-cloud-alibaba-sleuth-zipkin-demo,之后把各个模块中 pom.xml 文件的 artifactId 修改为 spring-cloud-alibaba-sleuth-zipkin-demo

    修改项目的启动端口,分别为 820182048207,主要是为了做章节区分。

  2. 增加测试接口。

    打开 goods-service-demo 项目,在 NewBeeCloudGoodsAPI 类中新增测试接口,代码如下:

    @GetMapping("/goodsDetail2/{goodsId}")
    public String goodsDetail2(@PathVariable("goodsId") int goodsId) {
        // 根据id查询商品并返回给调用端
        if (goodsId < 1 || goodsId > 100000) {
            return "查询商品为空,当前服务的端口号为:" + applicationServerPort;
        }
        String goodsName = "商品" + goodsId;
        // 返回信息给调用端
        return goodsName + ", 当前服务的端口号为" + applicationServerPort;
    }
  3. 新增 OpenFeign 代码。

    打开 shopcart-service-demo 项目中的 pom.xml 文件,在 dependencies 标签下引入 OpenFeign 的依赖文件,新增代码如下:

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

    shopcart-service-demo 项目中新建 ltd.shopcart.cloud.newbee.openfeign 包,在 openfeign 包中新增 NewBeeGoodsDemoService 文件,用于创建对商品服务的 Feign 调用。

    NewBeeGoodsDemoService.java 代码如下:

    package ltd.shopcart.cloud.newbee.openfeign;
    
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    
    @FeignClient(value = "newbee-cloud-goods-service", path = "/goodsDetail2")
    public interface NewBeeGoodsDemoService {
    
        @GetMapping(value = "/{goodsId}")
        String getGoodsDetail2(@PathVariable(value = "goodsId") int goodsId);
    }
  4. 增加配置,启用 OpenFeign 并使 FeignClient 生效。

    shopcart-service-demo 项目的启动类上添加 @EnableFeignClients 注解,并配置相关的 FeignClient 类,代码如下:

    @SpringBootApplication
    @EnableFeignClients(clients={ltd.shopcart.cloud.newbee.openfeign.NewBeeGoodsDemoService.class})
    
    public class ShopCartServiceApplication {
        public static void main(String[] args) {
            SpringApplication.run(ShopCartServiceApplication.class, args);
        }
    }
  5. 使用 OpenFeign 声明的接口实现服务通信。

    由于已经使用 OpenFeign 声明了相关接口且配置完毕,因此这里修改 NewBeeCloudShopCartAPI 类中的代码并增加对 goods-service 项目中接口的远程调用就可以了。

    修改 NewBeeCloudShopCartAPI 类的代码如下:

    @RestController
    public class NewBeeCloudShopCartAPI {
    
        @Value("${server.port}")
        private String applicationServerPort; // 读取当前应用的启动端口
    
        @Resource
        private NewBeeGoodsDemoService newBeeGoodsDemoService;
    
        @GetMapping("/shop-cart/{cartId}")
        public String cartItemDetail(@PathVariable("cartId") int cartId) {
            String detail2Result = newBeeGoodsDemoService.getGoodsDetail2(2025);
            // 根据id查询商品并返回给调用端
    
            if (cartId < 0 || cartId > 100000) {
                return "查询购物项为空,当前服务的端口号为:" + applicationServerPort;
            }
            String cartItem = "购物项" + cartId;
            // 返回信息给调用端
            return cartItem + ",当前服务的端口号为:" + applicationServerPort;
        }
    }

之后,尝试启动三个项目,并在浏览器中请求 order-service 的测试接口,如果一切正常就表示代码修改成功。如此一来,就将调用链路改成了 order-service→shopcart-service→goods-service。

整合 Spring Cloud Sleuth 编码

前面已经完成了对演示代码的基础修改,接下来主要讲解 Spring Cloud Sleuth 的整合过程。

  1. 开启 OpenFeign 的日志输出。

    为了后续功能演示的效果,需要打开 OpenFeign 组件的日志输出功能,这样就能够把 OpenFeign 远程调用接口的日志内容打印出来。

    order-service-demo 项目和 shopcart-service-demo 项目中分别新建 ltd.order.cloud.newbee.config 包和 ltd.shopcart.cloud.newbee.config 包,用于存放 OpenFeign 日志输出的配置类。之后在刚刚创建的两个 config 包下新建 OpenFeignConfiguration 类,用于设置 OpenFeign 的日志级别,代码如下:

    @Configuration
    public class OpenFeignConfiguration {
        @Bean
        public Logger.Level openFeignLogLevel() {
            // 设置 OpenFeign 的日志级别
            return Logger.Level.FULL;
        }
    }

    修改 order-service-demo 项目和 shopcart-service-demo 项目中的 application.properties 配置文件,分别新增如下配置项:

    # order-service-demo 项目
    # 演示需要,开启 OpenFeign debug 级别日志
    logging.level.ltd.order.cloud.newbee.openfeign=debug
    # shopcart-service-demo 项目
    # 演示需要,开启 OpenFeign debug 级别日志
    logging.level.ltd.shopcart.cloud.newbee.openfeign=debug

    完成后,一旦项目中有用到 OpenFeign 远程调用的情况,就会在控制台输出对应的日志信息。

    当然,开启 OpenFeign 日志输出后,读者也可以启动三个项目并调用测试接口,可以记录一下此时的日志信息,方便后续与整合 Sleuth 组件后的日志进行比对。笔者在功能测试时就记录了一下,获取的日志信息如下:

    2023-06-01 23:54:42.104 DEBUG 6860 --- [nio-8114-exec-2]
  2. 引入 Sleuth 依赖。

    依次打开 order-service-demogoods-service-demoshopcart-service-demo 项目中的 pom.xml 文件,在 dependencies 标签下引入 Sleuth 的依赖文件,新增代码如下:

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-sleuth</artifactId>
    </dependency>

    整合 Spring Cloud Sleuth 组件是很简单的,不需要做额外的配置,只要添加依赖文件即可。重启三个项目并调用测试接口,此时获取的日志信息就变了,内容如下:

    当然,不止 OpenFeign 调用时的日志会被打标,平时打印的一些日志信息也会被打标。为了给读者演示,笔者在 NewBeeCloudOrderAPI 类中新增如下测试代码:

    private static final Logger log = LoggerFactory.getLogger(NewBeeCloudOrderAPI.class);
    
    @GetMapping("/logTest")
    public String logTest() {
        // 平时会打印的日志
        log.info("test info log by sleuth");
        log.error("test error log by sleuth");
    
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            // 将异常信息通过日志输出
            log.error("test exception log by sleuth:", e);
        }
    
        return "logTest";
    }

    整合 Sleuth 后,打印的日志信息中也出现了打标信息,代码如下:

这样一来,在项目开发与维护时,一旦出现异常或错误信息都可以通过打标数据查找上下游的数据,对更快地定位错误有非常大的帮助。

打标的日志信息数据已经产生,但仅靠开发人员手工组织和串联这些不计其数的链路日志显然不现实,因此还需要部署链路追踪数据的分析工具 Zipkin 来完成这个工作。