SpringCloud+Sleuth+Zipkin+Mysql链路追踪实现

前言

Spring Cloud Sleuth 在Spring Boot 1.x时代,是对Zipkin做一个完全整合,不仅实现了以 HTTP 的方式收集跟踪信息,还实现了通过消息中间件来对跟踪信息进行异步收集的封装。就连Zipkin服务端也做了一层封装。而到了 Spring Boot 2.0 之后 Zipkin 不再推荐我们再自定义 Server 端了,Sleuth专注于对Dapper 中的算法进行封装,spring-cloud-starter-zipkin 只是对Zipkin客户端的封装,对于Sleuth在工程中如何使用,Spring官网上是这样写的:

Only Sleuth (log correlation):如果只想使用Sleuth功能,而不想与Zipkin做集成的话,那么你只需要引入spring-cloud-starter-sleuth就可以了,普通开发人员可能没有机会用到这种方式。
Sleuth with Zipkin via HTTP :使用Sleuth并通过HTTP方式集成ZipkinServer,你只需要引入spring-cloud-starter-zipkin就可以了。starter中本身就已经依赖了sleuth和sleuth+zipkin。
在这里插入图片描述
Sleuth with Zipkin over RabbitMQ or Kafka :通过RabbitMQ或者Kafka这些消息中间件进行异步的消息处理,此时需要引入spring-cloud-starter-zipkin,还需要引入spring-rabbit(以Rabbit为例),且Zipkin的服务端也需要做rabbitMq的相应配置。
使用 RabbitMQ 异步发送 span 信息#
为什么选择 RabbitMQ 消息中间件发送 span 信息

1.sleuth 默认采用 http 通信方式,将数据传给 zipkin 作页面渲染,但是 http 本身存在握手链接等延时损耗,且传输过程中如果由于不可抗因素导致通信中断,那么此次通信的数据将会丢失。
2.而使用中间件的话,RabbitMQ 消息队列可以积压千万级别的消息,下次重连之后可以继续消费。且随着线程增多,并发量提升之后,RabbitMQ 异步发送数据明显更具有优势。
3.RabbitMQ 支持消息、队列持久化,可以通过消息状态落库、重回队列、镜像队列等技术手段保证其高可用。

  rabbitmq:
    host: 192.168.200.128
    port: 5672
    username: guest
    password: guest

本文本次仅实现**Sleuth with Zipkin via HTTP**方式。

1.eureka-server注册中心搭建

1.1pom.xml


       <dependencies>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
        dependency>
    dependencies>

1.2application.yml

server:
  port: 6868
spring:
  application:
    name: eureka   # 服务名称
eureka:
  instance:
    hostname: localhost
  client:
    register-with-eureka: false # 不要注册自己到eureka服务中
    fetch-registry: false # 是否从eureka中获取信息
    service-url: # 配置注册中心访问地址
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

1.3启动引导类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {

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

2.zipkin-server服务端搭建

2.1pom.xml关键设置

    <dependencies>
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-netflix-eureka-clientartifactId>
            <version>2.1.1.RELEASEversion>
        dependency>
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-zipkinartifactId>
        dependency>
        
        <dependency>
            <groupId>io.zipkin.javagroupId>
            <artifactId>zipkin-serverartifactId>
            <version>2.12.3version>
            
            <exclusions>
                <exclusion>
                    <artifactId>log4j-slf4j-implartifactId>
                    <groupId>org.apache.logging.log4jgroupId>
                exclusion>
            exclusions>
        dependency>
        
        <dependency>
            <groupId>io.zipkin.javagroupId>
            <artifactId>zipkin-autoconfigure-uiartifactId>
            <version>2.12.3version>
        dependency>
        
        <dependency>
            <groupId>io.zipkin.javagroupId>
            <artifactId>zipkin-autoconfigure-storage-mysqlartifactId>
            <version>2.12.3version>
        dependency>
        
	    <dependency>
	        <groupId>mysqlgroupId>
	        <artifactId>mysql-connector-javaartifactId>
	    dependency>
	    <dependency>
	        <groupId>org.springframework.bootgroupId>
	        <artifactId>spring-boot-starter-jdbcartifactId>
	    dependency>
	    <dependency>
	        <groupId>org.springframework.amqpgroupId>
	        <artifactId>spring-rabbitartifactId>
	    dependency>
    dependencies>

2.2application.yml

server:
  port: 8082
management:
  metrics:
    web:
      server:
        auto-time-requests: false
spring:
  zipkin:
#    关闭本工程的推送到zipkin服务的功能
    enabled: false
    discovery-client-enabled: false
	storage:
	  type: mysql
  rabbitmq:
    host: 192.168.200.128
    port: 5672
    username: guest
    password: guest
    
  # 配置mysql
  datasource:
    schema: classpath:/mysql.sql
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/atest?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: 123456
    # Switch this on to create the schema on startup:
#    initialize: true
#    continueOnError: true

  sleuth:
    enabled: false
#    sampler:
#      probability: 1.0


##数据库脚本创建地址,当有多个是可使用[x]表示集合第几个元素
#spring.datasource.schema[0]=classpath:/mysql.sql

# 关闭自动配置启用所有请求得检测
# 加入下面的配置可以防止报错:java.lang.IllegalArgumentException: Prometheus requires that all meters with the same name have the same set of tag keys.
management:
  metrics:
    web:
      server:
        # 避免访问 zipkin 页面报 java.lang.IllegalArgumentException
        auto-time-requests: false

2.3启动引导类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import zipkin2.server.internal.EnableZipkinServer;

@SpringBootApplication
@EnableEurekaClient
@EnableZipkinServer
public class QddFreightZipkinApplication {

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

3.建表sql

手动建数据库表,sql如下:

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,
  `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'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
 
ALTER TABLE zipkin_spans ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `id`) COMMENT 'ignore insert on duplicate';
ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`, `id`) COMMENT 'for joining with zipkin_annotations';
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(`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';
ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces';
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
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
 
ALTER TABLE zipkin_dependencies ADD UNIQUE KEY(`day`, `parent`, `child`);

运行Zipkin服务端,并访问http:..laocalhost:8082j就可以看到zipkin的界面。如下:
SpringCloud+Sleuth+Zipkin+Mysql链路追踪实现_第1张图片

3.service-provider

3.1pom.xml

添加zipkin依赖

 		
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
        dependency>
      
       <dependency>
           <groupId>org.springframework.cloudgroupId>
           <artifactId>spring-cloud-starter-zipkinartifactId>
       dependency>

3.2application.yml

spring:
  zipkin:
    # 不使用rabbitmq需要配置Zipkin服务器地址
    base-url: http://localhost:8082
  sleuth:
    sampler:
      # 配置采样比例,生产环境应调小,否则影响系统执行效率,或者改为rabbitMq方式
      probability: 1.0

但在测试的过程中我们会发现,有时候,程序刚刚启动后,刷新几次,并不能看到任何数据,原因就是我们的spring-cloud-sleuth收集信息是有一定的比率的,默认的采样率是0.1,该值的大小与系统响应速度成反比。建议生产环境下不要设置过大。

可能是版本更新问题,一些文章中设置的方式是spring.sleuth.sampler.percentage=1,但我查看了源码后发现,现在已经改为probability了。如果按照原来的写应该会报黄。是否生效,我没再进行确认。但如果已经没有这个字段属性了,应该不会生效。

3.3启动引导类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
//import org.springframework.context.annotation.Bean;
import zipkin2.server.internal.EnableZipkinServer;
//import zipkin2.storage.mysql.v1.MySQLStorage;

//import javax.sql.DataSource;

@SpringBootApplication
@EnableEurekaClient
@EnableZipkinServer
public class QddFreightZipkinApplication {

    public static void main(String[] args) {
        SpringApplication.run(QddFreightZipkinApplication.class, args);
    }
//    不使用该bean同样可以完成数据库写操作
//    @Bean
//    public MySQLStorage mySQLStorage(DataSource datasource) {
//        return MySQLStorage.newBuilder().datasource(datasource).executor(Runnable::run).build();
//    }
}

有些文章上说需要在引导类中注册MySQLStorage的Bean,但是本人测试过按照我文章中所写的方法,并不需要该Bean。而且,大部分文章里给的Bean的注册方式都是MySQLStorage.builder()....;。但我用的版本中方法为MySQLStorage.newBuilder()....。因为没有用到这个Bean,所以我不知道是否正确,但至少不报错。
注意DataSource的包名是javax.sql.DataSource。

4.发起请求

因为是测试,所以没有搭建服务consumer,只是使用postman做了请求测试。
如下请求,响应成功。
SpringCloud+Sleuth+Zipkin+Mysql链路追踪实现_第2张图片
查看zipkin界面上显示的追踪到的数据:
SpringCloud+Sleuth+Zipkin+Mysql链路追踪实现_第3张图片
可以点击查看详细的请求数据。
SpringCloud+Sleuth+Zipkin+Mysql链路追踪实现_第4张图片
数据库中相应的数据也已经写入成功。
SpringCloud+Sleuth+Zipkin+Mysql链路追踪实现_第5张图片

总结:

缺陷1:zipkin客户端向zipkin-server程序发送数据使用的是http的方式通信,每次发送的时候涉及到连接和发送过程。如果设置采样率过高,会拉低系统性能,所以不建议生产环境设置过高采样率。
缺陷2:当我们的zipkin-server程序关闭或者重启过程中,因为客户端收集信息的发送采用http的方式会被丢失。

改进方案:

1、通信采用socket或者其他效率更高的通信方式。
2、客户端数据的发送尽量减少业务线程的时间消耗,采用异步等方式发送收集信息。
3、客户端与zipkin-server之间增加缓存类的中间件,例如redis、MQ等,在zipkin-server程序挂掉或重启过程中,客户端依旧可以正常的发送自己收集的信息。

采用以上三种方式会提高系统的执行和响应效率和可靠性。而且spring-cloud也已经为我们提供了采用redis或Es等内存数据存储的方式,以及利用消息中间件进行数据分阶段存储的实现方式。

你可能感兴趣的:(SpringCloud+Sleuth+Zipkin+Mysql链路追踪实现)