Spring Cloud Sleuth 分布式链路追踪入门

您好,我是今夜写代码,今天学习下分布式链路组件Spring Cloud Sleuth

本文内容

  • 介绍了分布式链路的思想

  • Sleuth 和 Zipkin 简单集成Demo,并不涉及 Sleuth原理。

为什么要用链路追踪?

微服务架构下,一个复杂的电商应用,完成下单可能依赖商品、库存、结算、风控等一系列服务,并且依赖的服务又依赖一堆服务,其中任何一个环节出错都可能导致服务调用失败。 一旦服务调用失败,对于问题排查的成本是非常高的。

究竟哪个环节出错了?请求日志在哪一个服务节点上? 在没有调用链路追踪情况下,相信这绝对是一大难题。

早在2010年一篇由谷歌发表的 《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure 》横空出世, 这篇论文详细介绍了谷歌的分布式系统追踪基础设施 Dapper, 当时这篇论文在国内应该是思想超前的,大学还在教JSP、Servlet。 如今链路追踪在大型分布式应用中,起到很重要的一席之地。

如何自己实现简单的链路追踪?

我们可以按照下面流程实现一个简单的链路追踪,当然不包含链路上报和检索功能。

以常见的 Dubbo 和 Spring MVC 请求举例,实现将请求Trace Id传递下去,并且满足如下特征

  • 对业务代码无入侵

  • 业务代码可以通过API方式操作Trace Id

Dubbo

Dubbo attachment和过滤器等等不熟悉的同学可以查看之前文章Dubbo学习合集

我们可以通过Dubbo的attachment 实现Trace Id 透传,实现对业务代码无入侵。 同时为了业务代码获取方便,我们将TraceId 放入ThreadLocal中,这样业务代码可以通过ThreadLocal获取,而不必依赖Dubbo 的 RpcContext 。

java 复制代码
  String traceId  = RpcContext.getContext().getAttachment("traceId");
  if(traceId == null){
      traceId = UUID.randomUUID().toString().replace("-","");
      RpcContext.getContext().setAttachment("traceId",traceId);
  }
  TraceUtil.setTraceId(traceId);
java 复制代码
/**
 * @ClassName TraceUtil
 * @Description
 * @Author codetonight 今夜写代码 https://blog.csdn.net/happycao123
 * @Date 2024/12/14 16:06
 */
public class TraceUtil {

    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static String getTraceId(){
        return threadLocal.get();
    }

    public static void setTraceId(String traceId){
        threadLocal.set(traceId);
    }

    public static void remove(){
        threadLocal.remove();
    }

}

Spring MVC

Spring MVC 有自己的拦截器,我们也可以直接使用 servlet-api 中Filter 。同样的,我们将Trace Id 放入 ThreadLocal ,这样后面业务就可以通过ThreadLocal 操作Trace Id。

java 复制代码
/**
 * @ClassName TraceIdFilter
 * @Description
 * @Author codetonight 今夜写代码 https://blog.csdn.net/happycao123
 * @Date 2024/12/14 15:52
 */
public class TraceIdFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String traceId = request.getHeader("traceId");
        if(traceId == null) {
           traceId = UUID.randomUUID().toString().replace("-","");
        }
        TraceUtil.setTraceId(traceId);
        try{
            filterChain.doFilter(servletRequest,servletResponse);
        }finally {
            TraceUtil.remove();
        }
    }
}

上面实现比较粗糙,信息也不够全,还存在异步方法链路无法透传问题,当然更重要的缺乏链路查询的支持。

Spring Cloud Sleuth

Spring Cloud Sleuth 是一个分布式追踪的组件,我们无需再重复造轮子。

相关概念

  • Trace ID: 整个调用链全局唯一的Id,无论经过多少个服务,整个调用链中 TraceId都相同

  • Span ID: 标识一次具体的操作或服务调用,同样全局唯一。

  • Parent ID: 记录当前服务调用发起方的 Span Id。

一个复杂的微服务调用,可能出现服务的层层嵌套,通过Parent ID ,可以梳理整个调用链的上下游关系。

如图服务A 调用服务B ,服务B 又调用了服务C ,TraceId 对于整个调用链是不变的。 各种的服务都有自己的Span ID ,Parent ID记录了其直接调用方服务对应的Span ID 。

Zipkin

Sleuth可以实现服务调用的链路透传, 如果需要实现链路检索功能,可以使用Zipkin,Zipkin核心功能日志收集和链路检索,Zipkin 具有可视化页面。

Zipkin 安装

如果您使用的是 Java 17或者更高版本,可以通过编译源码方式生成 zipkin-server-*exec.jar

bash 复制代码
java -jar ./zipkin-server/target/zipkin-server-*exec.jar
Docker 安装方式

docker 方式一个命令即可,启动成功输入 http://{ip}:9411/,即可进入Web 页面

复制代码
docker run -d -p 9411:9411 openzipkin/zipkin

Spring Cloud Sleuth 简单Demo

Server 作为SpringBoot 启动类,同时提供了/hello http 接口。

Client 作为SpringBoot 启动类,提供/callServer接口,其内部通过restTemplate调用 Server/hello接口。

java 复制代码
/**
 * @ClassName Server
 * @Description
 * @Author codetonight 今夜写代码 https://blog.csdn.net/happycao123
 * @Date 2024/12/15 19:00
 */
@EnableAutoConfiguration
@RestController
@Slf4j
public class Server {


    private  final Tracer tracer;

    public Server(Tracer tracer) {
        this.tracer = tracer;
    }

    @RequestMapping("/hello")
    public String hello() {
        log.info("Server hello is called ");
//        Span currentSpan = tracer.currentSpan();
//        if (currentSpan != null) {
//            currentSpan.tag("custom-tag", "value");
//            currentSpan.annotate("Custom Event");
//        }
        return new Date().toString();
    }

    public static void main(String[] args) {
        SpringApplication.run(Server.class,
                "--spring.application.name=server",
                "--server.port=9000"
        );
    }
}
java 复制代码
/**
 * @ClassName Client
 * @Description
 * @Author codetonight 今夜写代码 https://blog.csdn.net/happycao123
 * @Date 2024/12/15 19:04
 */
@EnableAutoConfiguration
@RestController
@Slf4j
public class Client {

    @Autowired
    RestTemplate restTemplate;

    String backendBaseUrl = "http://localhost:9000";

    @RequestMapping("/callServer") public String callServer() {
        log.info("callServer {}",new Date());
        return restTemplate.getForObject(backendBaseUrl + "/hello", String.class);
    }

    @Bean
    RestTemplate restTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(Client.class,
                "--spring.application.name=client",
                "--server.port=8081"
        );
    }
}
bash 复制代码
logging.level.org.springframework.web=DEBUG
spring.sleuth.traceId128=true
spring.sleuth.sampler.probability=1.0
# Adds trace and span IDs to logs (when a trace is in progress)
logging.pattern.level=[%X{traceId}/%X{spanId}] %-5p [%t] %C{2} - %m%n


spring.application.name=sleuth-service


spring.zipkin.base-url=http://localhost:9411

效果图

zipkin 控制台可以看到相关日志,我们自己打印的日志并没有TraceId 相关信息

业务日志增加TraceId 信息

很自然想到的方式通过API方式获取Trace ID相关信息,但这种相对比繁琐,业务代码有一定入侵,其实我们可以配置logback-spring.xml 控制日志格式,配置后再看看效果,我们自己的业务日志也包含Trace ID 等信息了。

API 获取链路信息简单例子
复制代码
        Span currentSpan = tracer.currentSpan();
        System.out.println(currentSpan.context().traceIdString());
        System.out.println(currentSpan.context().spanIdString());
logback-spring.xml
复制代码
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{36} - traceId=%X{traceId:-}, spanId=%X{spanId:-} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

总结

本文主要介绍了分布式链路追踪主要思想,以及Spring Cloud Slueth + Zipkin集成实现分布式链路追踪,当然本文只是入门级教程,不涉及Slueth原理相关,文中给的例子仅仅只是Demo,实际项目中,启动类与Controller 类职责要严格区分,不能混在一起,更多Demo 可以查看官网。

相关推荐
Rain的Java大神之路14 小时前
如何快速上传10G文件
java·spring boot·redis·后端·mysql·spring cloud·面试
需要82617 小时前
MySQL 索引 B+ 树与“查询为什么变慢了
java·spring boot·spring·spring cloud·tomcat
需要8261 天前
Arthas 一个命令排查线上问题
java·jvm·spring·spring cloud
苏渡苇2 天前
Spring Insight 里如何把 Span 画成瀑布时间线
后端·spring·spring cloud·springboot·监控·apm
叶总没有会2 天前
Nacos—注册中心
spring cloud·微服务
苏渡苇3 天前
Spring Insight 里如何把 Span 收成一条链路
spring boot·spring·spring cloud·系统监控·apm
叶总没有会3 天前
微服务--分布式基础
java·spring·spring cloud·微服务
孙启超4 天前
【AI开发之Rust】第 7 课:错误处理 —— panic、Result 与 `?`
人工智能·分布式·后端·爬虫·spring cloud·架构·rust
行百里er5 天前
Spring Insight 里如何把 Span 收成一条链路
spring boot·spring cloud·监控
砍材农夫5 天前
spring|spring web|拦截器、过滤器、aop
java·spring boot·spring·spring cloud