文章目录

  • 分布式链路追踪 Sleuth+Zipkin
  • 适用场景
  • 市场上分布式链路追踪方案
  • 分布式链路追踪技术核心思想
  • Sleuth+Zipkin
  • Zipkin server
  • Zipkin client
  • 追踪数据Zipkin持久化到MySQL


分布式链路追踪 Sleuth+Zipkin

适用场景

在微服务架构下,一次请求要调用多次其他的服务,那么问题就来了,

1)如何动态展示服务的调用链路?

2)如何分析服务调用链路中的瓶颈节点在哪及如何进行调优?

3)如何快速进行服务链路的故障发现?

这就是分布式链路追踪技术存在的意义和要解决的问题。

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

  • Spring cloud Sleuth + Twitter Zipkin
  • 阿里巴巴的“鹰眼”
  • 大众点评的“CAT”
  • 美团的“Mtrace”
  • 京东的“Hydra”
  • 新浪的“Watchman”
  • Apache Skywalking

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

分布式链路追踪的基础时日志,在请求调用的各个节点记录调用的日志,然后将日志进行集中分析展示。

java 分布式日志跟踪_链路

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

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

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

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

Span可以认为是一个日志数据结构,在一些特殊的时间点记录了一些日志信息,Span中抽象出了一个概念,叫事件,核心事件如下:

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

Spring Cloud Sleuth 追踪服务间的调用,记录一个服务请求经过哪些服务,服务处理时长等,根据这些,我们能够服务的调用关系进行问题追踪分析

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

通常把Spring Cloud Sleuth和Zipkin 一起使用,把Sleuth的数据信息发送给Zipkin进行聚合,利用Zipkin存储并展示数据。

java 分布式日志跟踪_mysql_02

Sleuth+Zipkin

由上图可知,zipkin分客户端和服务端,先看服务端

Zipkin server

  1. 新建项目cloud-zipkin-server,引入依赖
<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>io.zipkin.java</groupId>
        <artifactId>zipkin-server</artifactId>
        <version>2.12.3</version>
        <exclusions>
            <!--排除log42的传递依赖,避免与spring boot的日志框架冲突-->
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-log4j2</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!--zipkin ui-->
    <dependency>
        <groupId>io.zipkin.java</groupId>
        <artifactId>zipkin-autoconfigure-ui</artifactId>
        <version>2.12.3</version>
    </dependency>
</dependencies>
  1. application.yml配置文件
spring:
  application:
    name: cloud-zipkin-server
eureka:
  instance:
    prefer-ip-address: true
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka,http://localhost:8762/eureka
management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: always
  # zipkin sever 增加
  metrics:
    web:
      server:
        auto-time-requests: false  # 关闭自动检测请求
  1. 启动类
@SpringBootApplication
@EnableZipkinServer // 开启Zipkin Server
@EnableDiscoveryClient
public class ZipkinApp7980 {

    public static void main(String[] args) {
        SpringApplication.run(ZipkinApp7980.class, args);
    }
}

Zipkin client

  1. 引入依赖
<!-- 服务链路追踪 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>
  1. 在配置文件中配置zipkin 信息
spring:
  zipkin:
    sender:
      # web 客户端将追踪日志数据通过网络请求方式传输到服务端
      # Kafka/rabbit 客户端将追踪日志数据传递到mq进行中转。
      type: web
    locator:
      discovery:
        enabled: true # 能够根据服务发现定位服务名称 base-url:http://服务名称
    base-url: http://cloud-zipkin-server
    # base-url: http://localhost:7980 指定服务地址
  sleuth:
    sampler:
      # 采样率1 代表100%全部采集,默认0.1 代表10%的请求追踪数据会被采集
      probability: 1

运行服务,访问zipkin服务

java 分布式日志跟踪_mysql_03

追踪数据Zipkin持久化到MySQL

MySQL中创建zipkin的数据库,并执行如下SQL(官方提供):https://github.com/openzipkin/zipkin/blob/master/zipkin-storage/mysql-v1/src/main/resources/mysql.sql

--
-- Copyright 2015-2019 The OpenZipkin Authors
--
-- Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
-- in compliance with the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software distributed under the License
-- is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
-- or implied. See the License for the specific language governing permissions and limitations under
-- the License.
--

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;

Zipkin Server 项目引入依赖:

<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>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
</dependency>

修改配置文件,添加zipkin 持久化的配置和数据源配置

server:
  port: 7980

spring:
  application:
    name: cloud-zipkin-server
  datasource:
    url: jdbc:mysql://localhost:3306/zipkin?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    druid:
      initial-size: 10
      min-idle: 10
      max-active: 30
      max-wait: 50000
# 指定zipkin持久化介质为mysql 
zipkin:
  storage:
    type: mysql # 没有代码提示

启动类中添加事务管理器

@Autowired
private DataSource dataSource;

@Bean
public DataSourceTransactionManager transactionManager() {
    return new DataSourceTransactionManager(dataSource);
}