分布式进阶-链路追踪SpringCloudSleuth、Zipkin【实战篇】

开发 架构
Spring Cloud Sleuth 是 Spring Cloud 生态系统的一部分,它是一个分布式追踪解决方案,用于监视微服务架构中的请求流程,并帮助开发者跟踪请求在不同微服务之间的传播路径。

一、前言

我们在使用微服务的时候,往往涉及到各个微服务之间的调用,肯定会存在深度的调用链路,如果出现BUG或者异常,就会让问题定位和处理效率非常低。有了Sleuth ,就可以帮助我们记录、跟踪应用程序中的请求和操作。通常与 Zipkin 配合使用,从而提供更全面的可视化应用程序跟踪和分析功能。

就像ElasticSearch和Kibana一样!

复杂的链路调用如下图所示:

在继续往下看的同时,需要你具备Springboot整合Nacos构建一个聚合项目的能力。

当然如果不想自己来,小编也给大家准备好了。大家可以下载运行一下,开始下面的实战!

防止Github访问不了,这里把代码提交到了Gitee。

cloud-sleuth-zipkin-demo代码下载地址:https://gitee.com/wang-zhenjun/cloud-sleuth-zipkin-demo

二、Spring Cloud Sleuth 介绍

1、简介

Spring Cloud Sleuth 是 Spring Cloud 生态系统的一部分,它是一个分布式追踪解决方案,用于监视微服务架构中的请求流程,并帮助开发者跟踪请求在不同微服务之间的传播路径。

Sleuth主要用于解决微服务架构中的分布式系统跟踪和调试问题。

官网文档:https://docs.spring.io/spring-cloud-sleuth/docs/2.2.8.RELEASE/reference/html/。

2、核心概念

我们可以看一下官网的图片:

简单名词介绍:

cs:「客户端发送」,客户已提出请求。该注释指示跨度的开始。sr:「服务器已接收」,服务器端收到请求并开始处理。从此时间戳中减去cs时间戳即可得出网络延迟。ss:「服务器发送」,在请求处理完成时(当响应发送回客户端时)进行注释。从这个时间戳中减去sr时间戳就可以得出服务器端处理请求所需的时间。cr:「客户端已收到」,表示跨度的结束。客户端已成功收到服务器端的响应。从此时间戳中减去cs时间戳即可得出客户端从服务器接收响应所需的整个时间。

详细信息可以看官网介绍,总结一下:

名词

翻译

解释

Trace

追踪

Trace 是一个请求的整体追踪。它代表了从请求的起始点到结束点的完整路径,经过多个微服务。每个 Trace 都有一个唯一的 Trace ID。

Span

跨度

Span 是 Trace 中的一个小段,它代表了请求在某个特定微服务上的处理过程。Spans 之间有父子关系,它们可以形成一个层次结构,以表示请求的处理路径。

Trace ID

追踪标识

Trace ID 是唯一标识一个 Trace 的标识符。它在整个 Trace 中保持不变,用于将不同的 Span 关联到同一个 Trace 上。

Span ID

跨度标识

Span ID 是唯一标识一个 Span 的标识符。它用于在不同 Span 之间建立父子关系。

Parent Span ID

父 Span 标识

父 Span ID 是标识一个 Span 的父 Span 的标识符。它用于建立 Span 之间的关系。

Annotations

注解

Annotations 是关于 Span 的额外信息,通常用于记录 Span 的开始和结束时间、操作名称、以及其他相关信息。Annotations 可以帮助你更好地理解请求的处理过程。

Binary Annotations

二进制注解

Binary Annotations 是键值对形式的信息,用于记录与 Span 相关的自定义信息,例如请求的状态、错误信息等。

Collector

收集器

Collector 是用于收集追踪信息的组件,它将追踪数据发送到后端存储或可视化工具(如Zipkin或Jaeger)。Collector 可以将 Span 数据持久化,以供分析和监视使用。

Sampler

采样器

Sampler 用于确定是否对一个请求进行追踪。它决定是否为请求创建一个 Trace。Sampler 可以根据策略决定是否记录某个请求的 Trace 数据,以避免记录过多的追踪信息,从而降低性能开销。

「官网的图,每一个代表一个组件,他们之间进行调用,画的少了Trace Id,加上就好了。」

每个组件都会生成一个 Trace Id(全局唯一),还会有 Span Id、Parent Id 三部分组成。链路上的所有组件组成一个完整的 Trace。

「注意:」

「头链路Parent Id = null,其余的都指向上一个组件的Span Id,从而形成链路。」

「一次链路调用所有的组件Trace Id都是一样的。」

「这里说的组件就是一个个的微服务!」

三、 Zipkin介绍和搭建

1、定义

Zipkin 是一个分布式追踪系统。它有助于收集解决服务架构中的延迟问题所需的计时数据。功能包括该数据的收集和查找。

Zipkin官网地址:https://zipkin.io/。

2、核心概念

名词

翻译

解释

Trace

追踪

Trace 代表整个请求的追踪路径,跨越不同的服务。

Span

跨度

Span 是基本工作单位,代表了请求在单个服务中的处理过程。

Trace ID

追踪标识

Trace ID 是唯一标识一个 Trace 的标识符,用于将不同的 Span 关联到同一个 Trace 上。

Annotations

注解

Annotations 用于记录 Span 的关键事件,通常包括开始和结束时间、操作名称等。

Binary Annotations

二进制注解

Binary Annotations 用于记录额外的自定义信息,例如请求状态、错误信息等。

Collector

收集器

Collector 负责接收和存储从不同服务发送的 Span 数据,以便后续的检查和分析。

Query and Visualization

查询和可视化

提供了查询和可视化界面,允许用户查看和分析跟踪数据,以帮助故障排查和性能优化。

尽管Sleuth 和 Zipkin有些术语和概念中有相似之处,但它们是两个不同的工具,各自有自己的实现和用途。

「Spring Cloud Sleuth 用于生成和传播跟踪信息,而 Zipkin 用于收集、存储、查询和可视化这些信息。它们可以协同工作,但也可以独立使用。」

3、docker搭建

官方有三种方式搭建,推荐使用:「如果您熟悉 Docker,这是首选的启动方法。」

Docker Zipkin项目能够构建 docker 镜像、提供脚本和docker-compose.yml 用于启动预构建镜像的脚本。

https://github.com/openzipkin/docker-zipkin/blob/master/docker-compose.yml。

最快的启动方式是直接运行最新的镜像:

docker run -d -p 9411:9411 openzipkin/zipkin

我们启动成功,在Windows下访问看是否成功!

http://192.168.239.130:9411/zipkin/

「注意:」

「Zipkin默认将追踪数据信息保存到内存,重启服务后追踪数据丢失,Zipkin支持将追踪数据持久化到MySQL或ES。」

可以直接使用docker-componse运行:

docker-componse运行脚本:https://github.com/openzipkin/zipkin/tree/master/docker/examples

可以自行试一下,这里就不带大家演示了!

四、Springboot整合

今天我们来进行简单的链路模拟:

「service-order模块调用service-stock模块调用service-message模块」

「通信我们使用openFeign来进行调用,三个模块统一使用nacos进行注册」

大家可以下载一下项目体验一下,可以自己搭建,就是一个聚合项目!

结构如下:

1、导入依赖

这是父依赖。

<properties>
    <spring.boot.version>2.7.3</spring.boot.version>
    <spring.cloud.dependencies.version>2021.0.1</spring.cloud.dependencies.version>
    <spring.cloud.alibaba.version>2021.0.1.0</spring.cloud.alibaba.version>
    <java.version>1.8</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <org.projectlombok.lombok>1.18.26</org.projectlombok.lombok>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring.cloud.dependencies.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring.boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>${spring.cloud.alibaba.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${org.projectlombok.lombok}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

spring-cloud-dependencies里包含了sleuth、zipkin的依赖,父不需要再定义管理版本。

子依赖要比父依赖多了sleuth、zipkin两个,还有openFeign的包!

<!-- Sleuth依赖项 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

<!--Zipkin 依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>

<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>

2、yml配置

三份自己改一下端口和应用名称:service-order、service-stock、service-message;端口分别为:9000、9001、9002

server:
  port: 9000

spring:
  application:
    # 应用名称
    name: service-order
  cloud:
    nacos:
      discovery:
        # 服务注册地址
        server-addr: localhost:8848
  zipkin:
    base-url: http://192.168.239.130:9411
    sender:
      type: web # 设置使用 http 的方式传输数据

3、详细代码

记得在启动类上添加注解:@EnableFeignClients,表示开启feign调用。

完整的结构如下:

下面把具体代码给大家:OrderController :

/**
 * @author wangzhenjun
 * @date 2023/10/31 14:25
 */
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/order")
@RestController
public class OrderController {

    private final RemoteStockFeignService remoteStockFeignService;
    /**
     * 模拟下单流程
     * @param userId
     * @param productId
     * @return
     */
    @GetMapping("/createOrder")
    public String createOrder(@RequestParam("userId") Integer userId, @RequestParam("productId") Integer productId) {
        log.info("====>订单模块<========");
        // 调用库存服务进行库存扣减
        String stockResult = remoteStockFeignService.subtractStock(userId,productId,1);
        log.info("扣减库存结果:{}", stockResult);
        // 还有其他。。。。。

        return "下单成功!";
    }
}

RemoteStockFeignService :

/**
 * @author wangzhenjun
 * @date 2023/10/31 14:29
 */
@FeignClient(value = "service-stock")
public interface RemoteStockFeignService {

    @GetMapping(value = "/stock/subtractStock")
    String subtractStock(@RequestParam(value = "userId") Integer userId,@RequestParam(value = "productId") Integer productId,@RequestParam(value = "num") Integer num);
}

StockController :

/**
 * @author wangzhenjun
 * @date 2023/10/31 14:40
 */
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/stock")
@RestController
public class StockController {

    private final RemoteMessageFeignService remoteMessageFeignService;

    @GetMapping(value = "/subtractStock")
    public String subtractStock(@RequestParam(value = "userId") Integer userId,@RequestParam(value = "productId") Integer productId, @RequestParam(value = "num") Integer num) {
        log.info("====>库存模块<========");
        if (productId < 1) {
            throw new RuntimeException("商品不存在,请重新请求!");
        }
        // 调用短信模块给用户发下单成功短信
        String messageResult = remoteMessageFeignService.sendMessage(userId);
        log.info("发送短信结果:{}", messageResult);
        return "扣减库存成功!";
    }
}

RemoteMessageFeignService:

/**
 * @author wangzhenjun
 * @date 2023/10/31 14:29
 */
@FeignClient(value = "service-message")
public interface RemoteMessageFeignService {

    @GetMapping(value = "/message/sendMessage/{userId}")
    String sendMessage(@PathVariable(value = "userId") Integer userId);
}

MessageController :

/**
 * @author wangzhenjun
 * @date 2023/10/31 14:40
 */
@Slf4j
@RequestMapping("/message")
@RestController
public class MessageController {


    @GetMapping(value = "/sendMessage/{userId}")
    public String sendMessage(@PathVariable(value = "userId") Integer userId) {
        log.info("====>短信模块<========");
        if (userId < 1 || userId > 999999) {
            throw new RuntimeException("用户不存在,请重新请求!");
        }
        return "发送短信成功!";
    }
}

4、启动nacos

Windows下启动nacos,找到nacos下的bin目录执行命令:

startup.cmd -m standalone

找到地址,访问。用户名密码都是nacos。可以在配置文件中修改!

5、注册成功

我们把三个模块进行启动!nacos上已经可以看到我们的服务注册上了,可以通过服务名进行调用了!

五、调试

我们以订单模块为入口进行链路调用:

http://localhost:9000/order/createOrder?userId=2&productId=89。

1、查看日志

我们看一下订单模块的日志。

可以总结出Sleuth 日志格式:

[service-order,e36ebe859a7473e7,e36ebe859a7473e7]

[服务名称,Trace ID,Span ID]

在最开始的链路上,Trace ID 和 Span ID 的值通常是相同的,这是因为它们都代表了整个请求的追踪。

「一条链路使用一个相同的Trace ID。」

在日志没有体现出Parent Span ID,不过不应该,我们可以通过Zipkin来看链路!

2、Zipkin查看

前面我们已经进入了Zipkin页面了,只需要刷新一下就可以看到每次链路的记录了!

点击SHOW按钮,可以看到详细链路信息:

耗时,深度,Trance ID 还是挺好的。

3、模拟异常

我们来把商品修改一下:

http://localhost:9000/order/createOrder?userId=2&productId=-89。

此时是库存服务出现的问题,就不会展示下一个消息模块,自然而然的找到了出现问题的链路和根源!

在模拟一个三级错误的,就会看到链路的最后一级!

下面还可以查询依赖关系:

点击节点,可以查看汇总,调用次数和失败次数的统计分析!

六、总结

分布式链路追踪已经成为现代微服务架构中不可或缺的工具之一。

通过它,我们可以清晰地跟踪请求的调用路径,了解系统的性能,诊断潜在问题,并不断优化我们的应用程序。

Spring Cloud Sleuth让我们轻松生成和传播跟踪信息,使我们的微服务能够协同工作,无缝地捕捉每个请求的处理路径。

Zipkin作为一个流行的分布式追踪系统,为我们提供了可视化界面,使我们能够以图形化的方式查看和分析跟踪数据。

「当然简单系统上这个大材小用,但是我们可以在项目中试试,加了也不会影响程序的正常运行,做一个简单的知识储备!」

责任编辑:姜华 来源: 小王博客基地
相关推荐

2020-12-16 09:24:18

Skywalking分布式链路追踪

2024-01-26 07:49:49

Go分布式链路

2022-05-25 08:23:32

ZipKinTwitter开源项目

2021-02-22 07:58:51

分布式链路追踪

2020-09-11 09:44:04

微服务分布式链路

2021-11-08 14:10:37

分布式Spring链路

2022-11-26 09:49:07

分布式链路追踪技术

2022-08-05 10:03:17

分布式微服务

2022-05-23 08:23:24

链路追踪SleuthSpring

2023-10-26 00:00:00

分布式系统定位

2022-02-18 09:30:48

分布式Spring应用程序

2020-05-26 11:59:30

日志链路微服务架构

2011-09-13 14:21:00

IRF交换机基础分布式链路聚合

2022-09-25 22:19:24

Dapr分布式追踪

2022-11-24 10:01:10

架构分布式

2009-06-12 11:42:28

EJB分布式

2018-10-18 08:15:27

开源分布式追踪工具

2019-05-31 09:25:56

2023-10-16 23:43:52

云原生可观测性

2019-08-05 07:58:01

分布式架构系统
点赞
收藏

51CTO技术栈公众号