服务追踪Zipkin+Spring Cloud Sleuth

为什么需要Spring Cloud Sleuth

微服务架构是一个分布式架构,它按业务划分服务单元,一个分布式系统往往有很多个服务单元。由于服务单元数量众多,业务的复杂性,如果出现了错误和异常,很难去定位。主要体现在,一个请求可能需要调用很多个服务,而内部服务的调用复杂性,决定了问题难以定位。所以微服务架构中,必须实现分布式链路追踪,去跟进一个请求到底有哪些服务参与,参与的顺序又是怎样的,从而达到每个请求的步骤清晰可见,出了问题,很快定位。

举个例子,在微服务系统中,一个来自用户的请求,请求先达到前端A(如前端界面),然后通过远程调用,达到系统的中间件B、C(如负载均衡、网关等),最后达到后端服务D、E,后端经过一系列的业务逻辑计算最后将数据返回给用户。对于这样一个请求,经历了这么多个服务,怎么样将它的请求过程的数据记录下来呢?这就需要用到服务链路追踪。

Google开源的 Dapper链路追踪组件,并在2010年发表了论文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》,这篇文章是业内实现链路追踪的标杆和理论基础,具有非常大的参考价值。
目前,链路追踪组件有Google的Dapper,Twitter 的Zipkin,以及阿里的Eagleeye (鹰眼)等,它们都是非常优秀的链路追踪开源组件。

本文主要讲述如何在Spring Cloud Sleuth中集成Zipkin。在Spring Cloud Sleuth中集成Zipkin非常的简单,只需要引入相应的依赖和做相关的配置即可。

基本术语

Spring Cloud Sleuth采用的是Google的开源项目Dapper的专业术语。

Span:基本工作单元,发送一个远程调度任务 就会产生一个Span,Span是一个64位ID唯一标识的,Trace是用另一个64位ID唯一标识的,Span还有其他数据信息,比如摘要、时间戳事件、Span的ID、以及进度ID。
Trace:一系列Span组成的一个树状结构。请求一个微服务系统的API接口,这个API接口,需要调用多个微服务,调用每个微服务都会产生一个新的Span,所有由这个请求产生的Span组成了这个Trace。
Annotation:用来及时记录一个事件的,一些核心注解用来定义一个请求的开始和结束 。这些注解包括以下:
cs - Client Sent -客户端发送一个请求,这个注解描述了这个Span的开始
sr - Server Received -服务端获得请求并准备开始处理它,如果将其sr减去cs时间戳便可得到网络传输的时间。
ss - Server Sent (服务端发送响应)–该注解表明请求处理的完成(当请求返回客户端),如果ss的时间戳减去sr时间戳,就可以得到服务器请求的时间。
cr - Client Received (客户端接收响应)-此时Span的结束,如果cr的时间戳减去cs时间戳便可以得到整个请求所消耗的时间。

Zipkin
Zipkin 是 Twitter 的一个开源项目,它基于 Google Dapper 实现,它致力于收集服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集、存储、查找和展现。
我们可以使用它来收集各个服务器上请求链路的跟踪数据,并通过它提供的 REST API 接口来辅助我们查询跟踪数据以实现对分布式系统的监控程序,从而及时地发现系统中出现的延迟升高问题并找出系统性能瓶颈的根源。除了面向开发的 API 接口之外,它也提供了方便的 UI 组件来帮助我们直观的搜索跟踪信息和分析请求链路明细,比如:可以查询某段时间内各用户请求的处理时间等。
Zipkin 提供了可插拔数据存储方式:In-Memory、MySql、Cassandra 以及 Elasticsearch。接下来的测试为方便直接采用 In-Memory 方式进行存储,生产推荐 Elasticsearch。

上图展示了 Zipkin 的基础架构,它主要由 4 个核心组件构成:

Collector:收集器组件,它主要用于处理从外部系统发送过来的跟踪信息,将这些信息转换为 Zipkin 内部处理的 Span 格式,以支持后续的存储、分析、展示等功能。
Storage:存储组件,它主要对处理收集器接收到的跟踪信息,默认会将这些信息存储在内存中,我们也可以修改此存储策略,通过使用其他存储组件将跟踪信息存储到数据库中。
RESTful API:API 组件,它主要用来提供外部访问接口。比如给客户端展示跟踪信息,或是外接系统访问以实现监控等。
Web UI:UI 组件,基于 API 组件实现的上层应用。通过 UI 组件用户可以方便而有直观地查询和分析跟踪信息。

快速上手
Zipkin 分为两端,一个是 Zipkin 服务端,一个是 Zipkin 客户端,客户端也就是微服务的应用。
客户端会配置服务端的 URL 地址,一旦发生服务间的调用的时候,会被配置在微服务里面的 Sleuth 的监听器监听,并生成相应的 Trace 和 Span 信息发送给服务端。
发送的方式主要有两种,一种是 HTTP 报文的方式,还有一种是消息总线的方式如 RabbitMQ。

不论哪种方式,我们都需要:

一个 Eureka 服务注册中心,这里我们就用之前的eureka项目来当注册中心。
一个 Zipkin 服务端。
两个微服务应用,trace-a和trace-b,其中trace-a中有一个 REST 接口/trace-a,调用该接口后将触发对trace-b应用的调用。
方式一:HTTP
在 Spring Cloud Sleuth 中对 Zipkin 的整合进行了自动化配置的封装,所以我们可以很轻松的引入和使用它。

Zipkin 服务端
关于 Zipkin 的服务端,在使用 Spring Boot 2.x 版本后,官方就不推荐自行定制编译了,反而是直接提供了编译好的 jar 包来给我们使用,详情请看 upgrade to Spring Boot 2.0 NoClassDefFoundError UndertowEmbeddedServletContainerFactory · Issue #1962 · openzipkin/zipkin · GitHub

简而言之就是:私自改包,后果自负。

所以官方提供了一键脚本

curl -sSL https://zipkin.io/quickstart.sh | bash -s
java -jar zipkin.jar

如果用 Docker 的话,直接

docker run -d -p 9411:9411 openzipkin/zipkin

任一方式启动后,访问 http://localhost:9411/zipkin/ 就能看到如下界面,嗯还有汉化看起来不错

至此服务端就 OK 了。

微服务应用

创建两个基本的 Spring Boot 工程,名字分别为trace-a和trace-b
两个工程的 pom.xml 均引入以下依赖坐标

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>springboot</groupId>
    <artifactId>trace-a</artifactId>
    <version>0.0.1</version>

    <!-- Spring Boot 启动父依赖 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>

    <properties>
        <dubbo-spring-boot>1.0.0</dubbo-spring-boot>
    </properties>

    <dependencyManagement><!-- spring-cloud 依赖 -->
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/libs-snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>


    <dependencies>

        <!-- Spring Boot Web 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>



        <!-- Spring Boot Test 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- Junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

    </dependencies>
</project>

trace-a和race-b的yml,修改相应的name即可

spring:
  application:
    name: trace-a
  sleuth:
    web:
      client:
        enabled: true
    sampler:
      probability: 1.0 # 将采样比例设置为 1.0,也就是全部都需要。默认是 0.1
  zipkin:
    base-url: http://192.168.1.101:9411/ # 指定了 Zipkin 服务器的地址
server:
  port: 8081
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

只需要在pom文件里配置spring-cloud-sleuth-zipkin并且在配置文件里配置spring.zipkin.baseUrl,那么service就可以把监控日志发送给目标是baseUrl指向的Zipkin服务器,就是会自动调用zipkin的某个rest接口将监控日志传给它。

trace-a的java类

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerExchangeFilterFunction;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

/**
 * Spring Boot 应用启动类
 */
// Spring Boot 应用的标识
@RestController
@SpringBootApplication
public class ClientApplication {

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

    @Autowired
    private LoadBalancerExchangeFilterFunction lbFunction;

    @Bean
    public WebClient webClient() {
        return WebClient.builder().baseUrl("http://trace-b")
                .filter(lbFunction)
                .build();
    }

    @GetMapping("/trace-a")
    public Mono<String> trace() {
        System.out.println("===call trace-a===");

        return webClient().get()
                .uri("/trace-b")
                .retrieve()
                .bodyToMono(String.class);
    }

}

trace-b的java类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

/**
 * Spring Boot 应用启动类
 */
// Spring Boot 应用的标识
@RestController
@SpringBootApplication
public class ClientApplication {


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

	@GetMapping("/trace-b")
	public Mono<String> trace() {
		System.out.println("===call trace-b===");

		return Mono.just("Trace");
	}

}

服务启动

这里我们服务发现依然用的是Eureka

启动eureka-server项目(在本节的git源码中)
依次启动tarce-a,tarce-b

访问 http://localhost:8080/trace-a 可以得到返回值Trace,同时还能在它们的控制台中分别输出的相关信息

访问 http://localhost:9411/zipkin
点击 Find Traces 会看到有一条记录

UI界面

在这里插入图片描述
trace详情
在这里插入图片描述

span详情
在这里插入图片描述

全局依赖

整个调用链的路径在这里插入图片描述
点击服务名,弹出如下框,显示出了调用关系,在这里插入图片描述

点击具体的服务名,出现如下界面

在这里插入图片描述
Number of calls : 总的调用数(除去异常的)

Number of errors:调用异常的次数

sharedCode源码交流群,欢迎喜欢阅读源码的朋

数据配置成消息总线 RabbitMQ(也可以是Kafka)

如果是生产环境,一般需要用到第二套方案。就是先把日志发送到消息队列,然后再由zipkin接受,zipkin接收后保存日志到数据库。
因为之前说的 Zipkin 不再推荐我们来自定义 Server 端了,所以在最新版本的 Spring Cloud 依赖管理里已经找不到 zipkin-server 了。
那么如果直接用官方提供的 jar 包怎么从 RabbitMQ 中获取 trace 信息呢?

我们可以通过环境变量让 Zipkin 从 RabbitMQ 中读取信息,就像这样:

RABBIT_ADDRESSES=localhost java -jar zipkin.jar

可配置的环境变量如下表所示:

属性 环境变量 描述
zipkin.collector.rabbitmq.concurrency RABBIT_CONCURRENCY 并发消费者数量,默认为1
zipkin.collector.rabbitmq.connection-timeout RABBIT_CONNECTION_TIMEOUT 建立连接时的超时时间,默认为 60000毫秒,即 1 分钟
zipkin.collector.rabbitmq.queue RABBIT_QUEUE 从中获取 span 信息的队列,默认为 zipkin
zipkin.collector.rabbitmq.uri RABBIT_URI 符合 RabbitMQ URI 规范 的 URI,例如amqp://user:pass@host:10000/vhost
如果设置了 URI,则以下属性将被忽略。

属性 环境变量 描述
zipkin.collector.rabbitmq.addresses RABBIT_ADDRESSES 用逗号分隔的 RabbitMQ 地址列表,例如localhost:5672,localhost:5673
zipkin.collector.rabbitmq.password RABBIT_PASSWORD 连接到 RabbitMQ 时使用的密码,默认为 guest
zipkin.collector.rabbitmq.username RABBIT_USER 连接到 RabbitMQ 时使用的用户名,默认为guest
zipkin.collector.rabbitmq.virtual-host RABBIT_VIRTUAL_HOST 使用的 RabbitMQ virtual host,默认为 /
zipkin.collector.rabbitmq.use-ssl RABBIT_USE_SSL 设置为true则用 SSL 的方式与 RabbitMQ 建立链接

关于 Zipkin 的 Client 端,也就是微服务应用,我们就在之前 trace-a、trace-b 的基础上修改,只要在他们的依赖里都引入spring-cloud-stream-binder-rabbit就好了,别的不用改。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>

不过为了说明是通过 RabbitMQ 传输的信息,我将spring.zipkin.base-url均改为http://localhost:9412/,即指向一个错误的地址。

分别重启 trace-a、trace-b 工程,并启动 Zipkin Server

RABBIT_ADDRESSES=localhost java -jar zipkin.jar

然后访问 http://localhost:8080/trace-a 并刷新 Zipkin UI,看到如下内容,就说明 Sleuth+Zipkin+RabbitMQ 整合成功了。

此时看 RabbitMQ Admin,会看到多了一个名为 zipkin 的 Queue

rabbit和数据库配置如下
spring 官方的例子找不到消息中间件的具体配置,可能是在某个地方有默认,笔者还是自己配置了一下:

  spring.sleuth.sampler.percentage=1.0
  spring.rabbitmq.host=127.0.0.1
  spring.rabbitmq.port=5672
  spring.rabbitmq.username=guest
  spring.rabbitmq.password=guest
  spring.rabbitmq.virtualHost=/
  
  消息队列只需要配置上就会自动创建队列。数据库配置也是如此,其官方例子中也有,只需配置上就会自动建表。
  spring.datasource.schema=classpath:/mysql.sql
  spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/test
  spring.datasource.username=root
  spring.datasource.password=123
  spring.datasource.initialize=true
  spring.datasource.continueOnError=true
  spring.sleuth.enabled=false
  zipkin.storage.type=mysql

Zipkin正式环境系统架构方案

方案一
在这里插入图片描述

架构说明:
1.应用接入zipkin客户端,将span的信息直接推送给kafka

2.zipkin-server定kafka中订阅主体为sleuth的消息,将span中的信息推送到elasticsearch中

3.zipkin-ui项目负责从elasticsearch读取信息,分析信息,呈现图标给用户。

方案二
在这里插入图片描述

架构说明:
1.接入链路追踪的客户端,仅需对本地日志输出进行配置,输出span的信息到本地文件里面
2.部署logstash,读取应用的span日志信息,然后将span信息发送给kafka
3.zipkin-server通过消费kafka中,topic为“sleuth”的消息,将span信息存入elasticsearch
4.zipkin-ui项目负责从elasticsearch读取信息,分析信息,呈现图标给用户。
通过logstash分析本地日志的方式,对应用的侵入性,性能影响,高可用的影响 都是降到最低的。

两种方案比较

方案方案一方案二
优点部署复杂度较低,开发成本相对较低应用的侵入性,性能影响,高可用的影响 都是降到最低的。
缺点当kafka或者zookeeper宕机时, 已经启动的应用没有影响,对于正在启动的应用会启动失败部署复杂度较大。

当然UI也可以根据自己的设计进行修改,相对来说前端的工作量要大很多,比如首页可以做成自定义的如下报表形式。
在这里插入图片描述

源码地址

https://github.com/BambooZhang/spring-cloud
chapter12 SpringCloud第十二篇-服务追踪Zipkin+Spring Cloud Sleuth

参考内容

https://my.oschina.net/wuweixiang/blog/3025546
https://blog.csdn.net/sqzhao/article/details/70568637
sleuth+zipkin+kafka+logstash链路追踪二次开发方案
https://blog.csdn.net/u012394095/article/details/94389644

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值