微服务监控之分布式链路追踪技术 Sleuth + Zipkin

本文介绍了微服务架构下分布式链路追踪的重要性,详细阐述了Spring Cloud Sleuth与Zipkin的整合,包括Sleuth的引入、Zipkin Server和Client的构建,以及追踪数据在MySql中的持久化,以实现调用链路的监控和性能优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 分布式链路追踪技术适用场景

1.1 场景描述

为了支撑日益增长的庞大业务量,我们会使用微服务架构设计系统,使系统不仅能够通过集群部署抵挡流量的冲击,又能根据业务进行灵活的扩展。

那么,在微服务架构下,一次请求少则经过三四次服务调用完成,多则跨越几十个甚至是上百个服务节点。那么问题接踵而来:

  1. 如何动态展示服务的调用链路?(比如:A服务调用了哪些其他的服务)
  2. 如何分析服务调用链路中的瓶颈节点并对其进行调优?(比如:A > B > C,C服务处理时间特别长)
  3. 如何快速进行服务链路的故障发现?

这就是分布式链路追踪技术存在的目的和意义

1.2 分布式链路追踪技术

如果在一个请求的调用处理过程中,在各个链路节点都能够记录下日志,并最终将日志进行集中可视化展示,那么想监控调用链路中的一些指标就有希望了。比如,请求到达哪个服务实例?请求被处理的状态怎样?处理耗时怎样?这些都能够分析出来了…

分布式环境下基于这种想法实现的监控技术就是就是分布式链路追踪(全链路追踪)。

1.3 市场上的分布式链路追踪方案

分布式链路追踪技术已然成熟,产品也不少,国内外都有,比如

  1. Spring Cloud Sleuth + Twitter Zipkin
  2. 阿里巴巴的 “鹰眼”
  3. 大众点评的 “CAT”
  4. 美团的 “Mtrace”
  5. 京东的 “Hydra”
  6. 新浪的 “Watchman”

另外还有最近也被提到很多的 Apache Skywalking。

2. 分布式链路追踪技术核心思想

本质:记录日志,作为一个完整的技术,分布式链路追踪也有自己的理论和概念微服务架构中,针对请求处理的调用链可以展现为一棵树,示意如下:
在这里插入图片描述
上图描述了一个常见的调用场景,一个请求通过网关服务路由到下游的 微服务-1,然后微服务-1调用微服务-2,拿到结果后再调用微服务-3,最后组合微服务-2微服务-3的结果,通过网关返回给用户

为了追踪整个调用链路,肯定需要记录日志,日志记录是基础,在此之上肯定有一些理论概念,当下主流的的分布式链路追踪技术/系统所基于的理念都来自于Google的一篇论文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》,我们来看下,这里面涉及到的核心理念是什么
在这里插入图片描述
上图表示一条请求链路,一条链路 通过 TraceId 唯一标识,通过 Span 表示发起的请求信息,各 Span 通过 parrentId 关联起来

Trace:服务追踪的追踪单元是从客户发起请求(request)抵达被追踪系统的边界开始,到被追踪系统向客户返回响应(response)为止的过程

Trace ID:为了实现请求跟踪,当请求发送到分布式系统的入口端点时,只需要服务跟踪框架为该请求创建一个唯一的跟踪标识 Trace ID,同时在分布式系统内部流转的时候,框架始终保持该唯一标识,直到返回给请求方

一 个 Trace 由一个或者多个 Span 组成,每一个 Span 都有一个 SpanId,Span 中会记录 TraceId,同时还有一个叫做 ParentId,指向了另外一个 Span 的 SpanId,表明父子关系,其实本质表达了依赖关系

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

每一个 Span 都会有一个唯一跟踪标识 Span ID,若干个有序的 Span 就组成了一个 Trace。

Span 可以认为是一个日志数据结构,在一些特殊的时机点会记录了一些日志信息,比如有时间戳SpanIdTraceIdparentId等,Span 中也抽象出了另外一个概念,叫做事件,核心事件如下:

  • CS :client send/start 客户端/消费者发出一个请求,描述的是一个Span开始
  • SR: server received/start 服务端/生产者接收请求 SR-CS属于请求发送的网络延迟
  • SS: server send/finish 服务端/生产者发送应答 SS-SR属于服务端消耗时间
  • CR:client received/finished 客户端/消费者接收应答 CR-SS表示回复需要的时间(响应的网络延迟)

Spring Cloud Sleuth (追踪服务框架)可以追踪服务之间的调用,Sleuth 可以记录一个服务请求经过哪些服务、服务处理时长等,根据这些,我们能够理清各微服务间的调用关系及进行问题追踪分析。

  • 耗时分析:通过 Sleuth 了解采样请求的耗时,分析服务性能问题(哪些服务调用比较耗时)
  • 链路优化:发现频繁调用的服务,针对性优化等

Sleuth 就是通过记录日志的方式来记录踪迹数据的

注意:我们往往把 Spring Cloud Sleuth 和 Zipkin 一起使用,把 Sleuth 的数据信息发送给 Zipkin 进行聚合,利用 Zipkin 存储并展示数据。
在这里插入图片描述

3. Sleuth + Zipkin

3.1 引入 Sleuth

  1. 每⼀个需要被追踪踪迹的微服务⼯程都引⼊依赖坐标
<!--链路追踪-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
  1. 每⼀个微服务都修改application.yml配置⽂件,添加⽇志级别
# 分布式链路追踪
logging:
  level:
    org.springframework.web.servlet.DispatcherServlet: debug
    org.springframework.cloud.sleuth: debug

修改完成后,当请求到来时,在控制台可以观察到 Sleuth 输出的⽇志(全局TraceId、SpanId 等)。

这样的⽇志⾸先不容易阅读观察,另外⽇志分散在各个微服务服务器上,接下来我们使⽤Zipkin统⼀聚合轨迹⽇志并进⾏存储展示。

  1. 结合 Zipkin 展示追踪数据

Zipkin 包括 Zipkin Server 和 Zipkin Client 两部分,Zipkin Server 是⼀个单独的服务,Zipkin Client 就是具体的微服务

3.2 Zipkin Server 构建

  • pom.xml
<dependencies>
	<!--zipkin-server的依赖坐标-->
	<dependency>
		<groupId>io.zipkin.java</groupId>
		<artifactId>zipkin-server</artifactId>
		<version>2.12.3</version>
		<exclusions>
			<!--排除掉log4j2的传递依赖,避免和springboot依赖的⽇志组件冲突-->
			<exclusion>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-starter-log4j2</artifactId>
			</exclusion>
		</exclusions>
	</dependency>
	<!--zipkin-server ui界⾯依赖坐标-->
	<dependency>
		<groupId>io.zipkin.java</groupId>
		<artifactId>zipkin-autoconfigure-ui</artifactId>
		<version>2.12.3</version>
	</dependency>
</dependencies>
  • 添加入口启动类
package com.study;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import zipkin2.server.internal.EnableZipkinServer;

/**
 * @author xiaosong
 * @version 1.0
 * @since 2020/12/31
 */
@SpringBootApplication
@EnableZipkinServer // 开启 Zipkin Server 功能
public class ZipkinApplication9411 {
    public static void main(String[] args) {
        SpringApplication.run(ZipkinApplication9411.class,args);
    }
}
  • application.yml
server:
  port: 9411
  
# 关闭自动检测请求
management:
  metrics:
    web:
      server:
        auto-time-requests: false 

3.3 Zipkin Client 构建

Zipkin Client 在具体微服务的项目中修改

  • pom.xml 中添加 Zipkin 依赖
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
  • application.yml 中添加对 Zipkin Server 的引用
spring:
  application:
    name: cloud-gateway
  zipkin:
    # zipkin server 的请求地址
    base-url: http://127.0.0.1:9411
    sender:
      # kafka/rabbit 客户端将踪迹日志数据传递到mq进行中转
      type: web
  sleuth:
    sampler:
      # 采样率 1 代表100%全部采集 ,默认0.1 代表10% 的请求踪迹数据会被采集
      # 生产环境下,请求量非常大,没有必要所有请求的踪迹数据都采集分析,对于网络包括server端压力都是比较大的,可以配置采样率采集一定比例的请求的踪迹数据进行分析即可
      probability: 1

另外,log 日志依然保持开启 debug 状态

Zipkin Server ⻚⾯⽅便我们查看服务调⽤依赖关系及⼀些性能指标和异常信息
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

3.4 追踪数据 Zipkin 持久化到 MySql

改造 Zipkin Server

  • 在 MySql 的库中创建相关表
CREATE TABLE IF NOT EXISTS zipkin_spans (
  `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
  `trace_id` BIGINT NOT NULL,
  `id` BIGINT NOT NULL,
  `name` VARCHAR(255) NOT NULL,
  `remote_service_name` VARCHAR(255),
  `parent_id` BIGINT,
  `debug` BIT(1),
  `start_ts` BIGINT COMMENT 'Span.timestamp(): epoch micros used for endTs query and to implement TTL',
  `duration` BIGINT COMMENT 'Span.duration(): micros used for minDuration and maxDuration query',
  PRIMARY KEY (`trace_id_high`, `trace_id`, `id`)
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;

ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTracesByIds';
ALTER TABLE zipkin_spans ADD INDEX(`name`) COMMENT 'for getTraces and getSpanNames';
ALTER TABLE zipkin_spans ADD INDEX(`remote_service_name`) COMMENT 'for getTraces and getRemoteServiceNames';
ALTER TABLE zipkin_spans ADD INDEX(`start_ts`) COMMENT 'for getTraces ordering and range';

CREATE TABLE IF NOT EXISTS zipkin_annotations (
  `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
  `trace_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.trace_id',
  `span_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.id',
  `a_key` VARCHAR(255) NOT NULL COMMENT 'BinaryAnnotation.key or Annotation.value if type == -1',
  `a_value` BLOB COMMENT 'BinaryAnnotation.value(), which must be smaller than 64KB',
  `a_type` INT NOT NULL COMMENT 'BinaryAnnotation.type() or -1 if Annotation',
  `a_timestamp` BIGINT COMMENT 'Used to implement TTL; Annotation.timestamp or zipkin_spans.timestamp',
  `endpoint_ipv4` INT COMMENT 'Null when Binary/Annotation.endpoint is null',
  `endpoint_ipv6` BINARY(16) COMMENT 'Null when Binary/Annotation.endpoint is null, or no IPv6 address',
  `endpoint_port` SMALLINT COMMENT 'Null when Binary/Annotation.endpoint is null',
  `endpoint_service_name` VARCHAR(255) COMMENT 'Null when Binary/Annotation.endpoint is null'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;

ALTER TABLE zipkin_annotations ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `span_id`, `a_key`, `a_timestamp`) COMMENT 'Ignore insert on duplicate';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`, `span_id`) COMMENT 'for joining with zipkin_spans';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTraces/ByIds';
ALTER TABLE zipkin_annotations ADD INDEX(`endpoint_service_name`) COMMENT 'for getTraces and getServiceNames';
ALTER TABLE zipkin_annotations ADD INDEX(`a_type`) COMMENT 'for getTraces and autocomplete values';
ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces and autocomplete values';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id`, `span_id`, `a_key`) COMMENT 'for dependencies job';

CREATE TABLE IF NOT EXISTS zipkin_dependencies (
  `day` DATE NOT NULL,
  `parent` VARCHAR(255) NOT NULL,
  `child` VARCHAR(255) NOT NULL,
  `call_count` BIGINT,
  `error_count` BIGINT,
  PRIMARY KEY (`day`, `parent`, `child`)
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
  • pom.xml
<!--zipkin针对mysql持久化的依赖-->
<dependency>
	<groupId>io.zipkin.java</groupId>
	<artifactId>zipkin-autoconfigure-storage-mysql</artifactId>
	<version>2.12.3</version>
</dependency>
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>druid-spring-boot-starter</artifactId>
	<version>1.1.18</version>
</dependency>
<!--操作数据库需要事务控制-->
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-tx</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-jdbc</artifactId>
</dependency>
  • 修改配置⽂件,添加如下内容
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql:///cloud?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=UTC
    username: root
    password: root
    druid:
      initial-size: 10
      min-idle: 10
      max-active: 30
      max-wait: 30000
# 指定 zipkin 持久化介质为 mysql
zipkin:
  storage:
    type: mysql
  • 启动类中注⼊事务管理器
package com.study;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import zipkin2.server.internal.EnableZipkinServer;

import javax.sql.DataSource;

/**
 * @author xiaosong
 * @version 1.0
 * @since 2020/12/31
 */
@SpringBootApplication
@EnableZipkinServer // 开启 Zipkin Server 功能
public class ZipkinApplication9411 {
    public static void main(String[] args) {
        SpringApplication.run(ZipkinApplication9411.class,args);
    }

    @Bean
    public PlatformTransactionManager txManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值