分布式服务跟踪Sleuth

一、Sleuth前言

    随着业务的发展,系统规模也会变得越来越大,各微服务间的调用关系也变得越来越错综复杂。通常一个由客户端发起的请求在后端系统中会经过多个不同的微服务调用来协同产生最后的请求结果,在复杂的微服务架构系统中,几乎每一个前端请求都会形成一条复杂的分布式服务调用链路,在每条链路中任何一个依赖服务出现延迟过高或错误的时候都有可能引起请求最后的失败。这时候, 对于每个请求,全链路调用的跟踪就变得越来越重要,通过实现对请求调用的跟踪可以帮助我们快速发现错误根源以及监控分析每条请求链路上的性能瓶颈等。

    上面所述的分布式服务跟踪问题, Spring Cloud Sleuth提供了一套完整的解决方案,下面将介绍Spring Cloud Sleuth的应用

二、Sleuth快速入门

1、为了保持其他模块的整洁性,重新搭建一个消费者(springcloud-consumer-sleuth),提供者(springcloud-consumer-sleuth),消费者和提供者都是和前面所用的都一样没有什么区别,注册中心还是使用前面案例的注册中心(springcloud-eureka-server/8700),详细查看案例源码。

2、完成以上工作之后,我们为服务提供者和服务消费者添加跟踪功能,通过Spring Cloud Sleuth的封装,我们为应用增加服务跟踪能力的操作非常方便,只需要在服务提供者和服务消费者增加spring-cloud-starter-sleuth依赖即可

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

3、访问消费者接口,然后查看控制台日志显示

消费者(springcloud-consumer-sleuth)打印的日志

2019-12-05 12:30:20.178  INFO [springcloud-consumer-sleuth,f6fb983680aab32b,f6fb983680aab32b,false] 8992 --- [nio-9090-exec-1] c.s.controller.SleuthConsumerController  : === consumer hello ===

提供者(springcloud-provider-sleuth)打印的日志

2019-12-05 12:30:20.972  INFO [springcloud-provider-sleuth,f6fb983680aab32b,c70932279d3b3a54,false] 788 --- [nio-8080-exec-1] c.s.controller.SleuthProviderController  : === provider hello ===

    从上面的控制台输出内容中,我们可以看到多了一些形如 [springcloud-consumer-sleuth,f6fb983680aab32b,c70932279d3b3a54,false]的日志信息,而这些元素正是实现分布式服务跟踪的重要组成部分,每个值的含义如下所述:

  • 第一个值: springcloud-consumer-sleuth,它记录了应用的名称,也就是application properties 中spring.application.name参数配置的属性

  • 第二个值:f6fb983680aab32b, Spring Cloud Sleuth生成的一个ID,称为Trace ID, 它用来标识一条请求链路。一条请求链路中包含一个Trace ID,多个Span ID

  • 第三个值:c70932279d3b3a54, Spring Cloud Sleuth生成的另外一个ID,称为Span ID,它表示一个基本的工作单元,比如发送一个HTTP请求

  • 第四个值: false,表示是否要将该信息输出到Zipkin等服务中来收集和展示。上面四个值中的Trace ID和Span ID是Spring Cloud Sleuth实现分布式服务跟踪的核心,在一次服务请求链路的调用过程中,会保持并传递同一个Trace ID,从而将整个分布于不同微服务进程中的请求跟踪信息串联起来。以上面输出内容为例springcloud-consumer-sleuth和springcloud-provider-sleuth同属于一个前端服务请求资源,所以他们的Trace ID是相同的,处于同一条请求链路中。

三、跟踪原理

分布式系统中的服务跟踪在理论上并不复杂,主要包括下面两个关键点:

  1. 为了实现请求跟踪,当请求发送到分布式系统的入口端点时,只需要服务跟踪框架为该请求创建一个唯一的跟踪标识,同时在分布式系统内部流转的时候,框架始终保持传递该唯一标识,直到返回给请求方为止,这个唯一标识就是前文中提到的Trace ID。通过Trace ID的记录,我们就能将所有请求过程的日志关联起来

  2. 为了统计各处理单元的时间延迟,当请求到达各个服务组件时,或是处理逻辑到达某个状态时,也通过一个唯一标识来标记它的开始、具体过程以及结束,该标识就是前面提到的Span ID。对于每个Span来说,它必须有开始和结束两个节点,通过记录开始Span和结束Span的时间戳,就能统计出该Span的时间延迟,除了时间 戳记录之外,它还可以包含一些其他元数据,比如事件名称、请求信息等

    在【二、sleuth快速入门】示例中,我们轻松实现了日志级别的跟踪信息接入,这完全归功于spring-cloud-starter-sleuth组件的实现,在SpringBoot应用中通过在工程中引入spring-cloud-starter-sleuth依赖之后,他会自动为当前应用构建起各通信通道的跟踪机制,比如:

  • 通过RabbitMQ、Kafka(或者其他任何Spring Cloud Stream绑定器实现的消息中间件)传递的请求

  • 通过Zuul代理传递的请求

  • 通过RestTemplate发起的请求

    在【二、sleuth快速入门】示例中,由于springcloud-consumer-sleuth对springcloud-provider-sleuth发起的请求是通过RestTemplate实现的,所以spring-cloud-starter-sleuth组件会对该请求进行处理。在发送到springcloud-provider-sleuth之前,Sleuth会在该请求的Header中增加实现跟踪需要的重要信息,主要有下面这几个(更多关于头信息的定义可以通过查看org.springframework.cloud.sleuth.Span的源码获取)。

  • X-B3-TraceId:一条请求链路( Trace)的唯一标识,必需的值。

  • X-B3- SpanId:一个工作单元(Span)的唯一标识,必需的值。

  • X-B3- ParentSpanId:标识当前工作单元所属的上一个工作单元, Root Span(请求链路的第一个工作单元)的该值为空。

  • X-B3-Sampled:是否被抽样输出的标志,1表示需要被输出,0表示不需要被输出。

  • X-B3-Name:工作单元的名称

可以通过对springcloud-provider-sleuth的实现做一些修改来输出这些头信息,具体如下:

private final Logger logger = Logger.getLogger(SleuthProviderController.class.getName());

    @RequestMapping("/hello")
    public String hello(HttpServletRequest request){
        logger.info("=== provider hello ===,Traced={"+request.getHeader("X-B3-TraceId")+"},SpanId={"+request.getHeader("X-B3- SpanId")+"}");
        return "Trace";
    }

通过上面的改造,再次重启案例,然后访问我们查看日志,可以看到提供者输出了正在处理的TraceId和SpanId信息。

消费者(springcloud-consumer-sleuth)打印的日志

2019-12-05 13:15:01.457  INFO [springcloud-consumer-sleuth,41697d7fa118c150,41697d7fa118c150,false] 10036 --- [nio-9090-exec-2] c.s.controller.SleuthConsumerController  : === consumer hello ===

提供者(springcloud-provider-sleuth)打印的日志

2019-12-05 13:15:01.865  INFO [springcloud-provider-sleuth,41697d7fa118c150,863a1245c86b580e,false] 11088 --- [nio-8080-exec-1] c.s.controller.SleuthProviderController  : === provider hello ===,Traced={41697d7fa118c150},SpanId={863a1245c86b580e}

四、抽样收集

    通过Trace ID和Span ID已经实现了对分布式系统中的请求跟踪,而记录的跟踪信息最终会被分析系统收集起来,并用来实现对分布式系统的监控和分析功能,比如,预警延迟过长的请求链路、查询请求链路的调用明细等。此时,我们在对接分析系统时就会碰到个问题:分析系统在收集跟踪信息的时候,需要收集多少跟踪信息才合适呢?

    理论上来说,我们收集的跟踪信息越多就可以越好地反映出系统的实际运行情况,并给出更精准的预警和分析。但是在高并发的分布式系统运行时,大量的请求调用会产生海量的跟踪日志信息,如果收集过多的跟踪信息将会对整个分布式系统的性能造成一定的影响,同时保存大量的日志信息也需要不少的存储开销。所以,在Sleuth中采用了抽象收集的方式来为跟踪信息打上收集标识,也就是我们之前在日志信息中看到的第4个布尔类型的值,他代表了该信息是否被后续的跟踪信息收集器获取和存储。

public abstract class Sampler {
  /**
   * Returns true if the trace ID should be measured.
   *
   * @param traceId The trace ID to be decided on, can be ignored
   */
  public abstract boolean isSampled(long traceId);
}

    通过实现isSampled方法, Spring Cloud Sleuth会在产生跟踪信息的时候调用它来为跟踪信息生成是否要被收集的标志。需要注意的是,即使isSampled返回了false,它仅代表该跟踪信息不被输出到后续对接的远程分析系统(比如Zipkin中,对于请求的跟踪活动依然会进行,所以我们在日志中还是能看到收集标识为fase的记录。

    默认情况下, Sleuth会使用SamplerProperties实现的抽样策略,以请求百分比的方式配置和收集跟踪信息。我们可以通过在application.yml中配置下面的参数对其百分比值进行设置,它的默认值为0.1,代表收集10%的请求跟踪信息。

spring:
  sleuth:
    sampler:
      probability: 0.1

    在开发调试期间,通常会收集全部跟踪信息并输出到远程仓库,我们可以将其值设置为1,或者也可以注入Sampler对象SamplerProperties策略,比如

@Bean
  public Sampler defaultSampler() {
    return Sampler.ALWAYS_SAMPLE;
  }

    由于跟踪日志信息数据的价值往往仅在最近一段时间内非常有用,比如一周。那么我们在设计抽样策略时,主要考虑在不对系统造成明显性能影响的情况下,以在日志保留时间窗内充分利用存储空间的原则来实现抽样策略。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值