参考:Spring Cloud(十二):分布式链路跟踪 Sleuth 与 Zipkin【Finchley 版】
Spring Cloud Sleuth 是Spring Cloud的一个组件,主要功能是在分布式系统中提供服务链路追踪的解决方案。
微服务架构是一个分布式架构,微服务系统按业务划分服务单元,一个微服务系统往往有很多个服务单元。由于服务单元数量众多,业务的复杂性较高,如果出现了错误和异常,很难去定位。主要体现在一个请求可能需要调用很多个服务,而内部服务的调用复杂性决定了问题难以定位。所以在微服务架构中,必须实现分布式链路追踪,去跟进一个请求到底有哪些服务参与,参与的顺序又是怎样的,从而达到每个请求的步骤清晰可见,出了问题能够快速定位的目的。
现今业界分布式服务跟踪的理论基础主要来自于 Google 在2010年发的一篇论文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》,使用最为广泛的开源实现是 Twitter 的 Zipkin,为了实现平台无关、厂商无关的分布式服务跟踪,CNCF 发布了布式服务跟踪标准 Open Tracing。国内,淘宝的 “鹰眼”、京东的 “Hydra”、大众点评的 “CAT”、新浪的 “Watchman”、唯品会的 “Microscope”、窝窝网的 “Tracing” 都是这样的系统。
Spring Cloud Sleuth 也为我们提供了一套完整的解决方案。在本章中,我们将详细介绍如何使用 Spring Cloud Sleuth + Zipkin 来为我们的微服务架构增加分布式服务跟踪的能力。
Spring Cloud Sleuth
一般的,一个分布式服务跟踪系统主要由三部分构成:
- 数据收集
- 数据存储
- 数据展示
根据系统大小不同,每一部分的结构又有一定变化。譬如,对于大规模分布式系统,数据存储可分为实时数据和全量数据两部分,实时数据用于故障排查(Trouble Shooting),全量数据用于系统优化;数据收集除了支持平台无关和开发语言无关系统的数据收集,还包括异步数据收集(需要跟踪队列中的消息,保证调用的连贯性),以及确保更小的侵入性;数据展示又涉及到数据挖掘和分析。虽然每一部分都可能变得很复杂,但基本原理都类似。
服务追踪的追踪单元是从客户发起请求(request)抵达被追踪系统的边界开始,到被追踪系统向客户返回响应(response)为止的过程,称为一个 trace。每个 trace 中会调用若干个服务,为了记录调用了哪些服务,以及每次调用的消耗时间等信息,在每次调用服务时,埋入一个调用记录,称为一个 span。这样,若干个有序的 span 就组成了一个 trace。在系统向外界提供服务的过程中,会不断地有请求和响应发生,也就会不断生成 trace,把这些带有 span 的 trace 记录下来,就可以描绘出一幅系统的服务拓扑图。附带上 span 中的响应时间,以及请求成功与否等信息,就可以在发生问题的时候,找到异常的服务;根据历史数据,还可以从系统整体层面分析出哪里性能差,定位性能优化的目标。
Spring Cloud Sleuth 为服务之间调用提供链路追踪。通过 Sleuth 可以很清楚的了解到一个服务请求经过了哪些服务,每个服务处理花费了多长。从而让我们可以很方便的理清各微服务间的调用关系。此外 Sleuth 可以帮助我们:
- 耗时分析: 通过 Sleuth 可以很方便的了解到每个采样请求的耗时,从而分析出哪些服务调用比较耗时;
- 可视化错误: 对于程序未捕捉的异常,可以通过集成 Zipkin 服务界面上看到;
- 链路优化: 对于调用比较频繁的服务,可以针对这些服务实施一些优化措施。
Spring Cloud Sleuth 可以结合 Zipkin,将信息发送到 Zipkin,利用 Zipkin 的存储来存储信息,利用 Zipkin UI 来展示数据。
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-server
项目来当注册中心。 - 一个 Zipkin 服务端。
- 两个微服务应用。gateway-service作为服务网关工程,负责请求的转发,同时也作为链路追踪客户端,负载产生链路数据,并上传给Zipkin服务端。user-service是一个服务提供者,对外暴漏API接口,同时作为链路追踪客户端,负载产生链路数据。
- 一个 Eureka 服务注册中心,这里我们就用之前的
方式一:HTTP
Zipkin 服务端
关于 Zipkin 的服务端,在使用 Spring Boot 2.x 版本后,官方就不推荐自行定制编译了,反而是直接提供了编译好的 jar 包来给我们使用,详情请看 upgrade to Spring Boot 2.0 NoClassDefFoundError UndertowEmbeddedServletContainerFactory · Issue #1962 · openzipkin/zipkin · GitHub
并且以前的@EnableZipkinServer
也已经被打上了@Deprecated
If you decide to make a custom server, you accept responsibility for troubleshooting your build or configuration problems, even if such problems are a reaction to a change made by the OpenZipkin maintainers. In other words, custom servers are possible, but not supported.
EnableZipkinServer.java - github.com/openzipkin/zipkin/blob/master/zipkin-server/src/main/java/zipkin/server/EnableZipkinServer.java
简而言之就是:私自改包,后果自负。
所以官方提供了一键脚本(Windows下需要安装curl,不过如果你安装了Git客户端,可以直接在Git Bash中使用)
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 了。
微服务应用
构建User Service
新建Module工程user-service,作为服务提供者,对位暴漏API接口,pom文件继承主maven工程的pom文件,引入eureka client、zipkin的起步依赖,其中zipkin的依赖中包含了sleuth的起步依赖。
<?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>com.cralor</groupId> <artifactId>user-service</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>user-service</name> <description>Demo project for Spring Boot</description> <parent> <groupId>com.cralor</groupId> <artifactId>chap11-sleuth</artifactId> <version>0.0.1-SNAPSHOT</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties>
<dependencies> <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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
配置文件中,指定程序名user-service,端口号8762,服务注册地址http://localhost:8761/eureka/,Zipkin Server地址http://localhost:9411。Spring Cloud Sleuth 有一个 Sampler 策略,可以通过这个实现类来控制采样算法。采样器不会阻碍 span 相关 id 的产生,但是会对导出以及附加事件标签的相关操作造成影响。 Sleuth 默认采样算法的实现是 Reservoir sampling,具体的实现类是 PercentageBasedSampler,默认的采样比例为: 0.1(即 10%)。不过我们可以通过spring.sleuth.sampler.probability
来设置,所设置的值介于 0.0 到 1.0 之间,1.0 则表示全部采集。
server:
port: 8762
spring:
application:
name: user-service
sleuth:
sampler:
probability: 1.0 # 将采样比例设置为 1.0,也就是全部都需要。默认是 0.1
zipkin:
base-url: http://localhost:9411 # 指定了 Zipkin 服务器的地址
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
在UserController类建一个“/user/hi”的API接口,对外提供服务
@RestController @RequestMapping("/user") public class UserController { @GetMapping("/hi") public String hi(){ return "i'm cralor"; } }
构建Gateway Service
新建工程gateway-service作为服务网关,将请求转发到user-service。作为zipkin客户端,上传链路数据到zipkin服务器。pom文件引入eureka client、zipkin和zuul的依赖。
<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-netflix-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency>
配置文件中指定程序名gateway-service,端口号5000,服务注册地址http://localhost:8761/eureka/,Zipkin Server地址http://localhost:9411。以“/user-api/**”开头的请求转发到服务名为user-service的服务。
server:
port: 5000
spring:
application:
name: gateway-service
sleuth:
sampler:
probability: 1.0 # 将采样比例设置为 1.0,也就是全部都需要。默认是 0.1
zipkin:
base-url: http://localhost:9411 # 指定了 Zipkin 服务器的地址
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
zuul:
routes:
api-a:
path: /user-api/**
serviceId: user-service #将以"/user-api/**"开头的Uriqq转发到服务名为user-service的服务
启动类加上@EnableZuulProxy注解,开启zuul代理功能。
@EnableZuulProxy @SpringBootApplication public class GatewayServiceApplication { public static void main(String[] args) { SpringApplication.run(GatewayServiceApplication.class, args); } }
启动Zipkin服务器,依次启动eureka-server、user-service和gateway-service,浏览器访问http://localhost:5000/user-api/user/hi,显示
访问Zipkin服务器http://localhost:9411/zipkin/,点击 Find Traces 会看到有一条记录
点击记录进去页面,可以看到每一个服务所耗费的时间和顺序
点击依赖分析,可以看到项目之间的调用关系
方式二:消息总线 RabbitMQ
因为之前说的 Zipkin 不再推荐我们来自定义 Server 端了,所以在最新版本的 Spring Cloud 依赖管理里已经找不到 zipkin-server 了。
那么如果直接用官方提供的 jar 包怎么从 RabbitMQ 中获取 trace 信息呢?
我们可以通过环境变量让 Zipkin 从 RabbitMQ 中读取信息,就像这样:
java -jar zipkin.jar --zipkin.collector.rabbitmq.addressed=localhost
通过这种方式可以启动zipkin然后使用rabbitmq进行链路追踪。另外在zipkin中配置的rabbitmq的用户名和密码是guest、guest如果你的rabbitmq用户名密码不是这个也要修改配置启动。
zipkin.jar的yml配置文件内容可在此处查看:https://github.com/openzipkin/zipkin/blob/master/zipkin-server/src/main/resources/zipkin-server-shared.yml
这是配置文件的截图
关于 Zipkin 的 Client 端,也就是微服务应用,我们就在之前的基础上修改,只要在他们的依赖里都引入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/
,即指向一个错误的地址。
分别重启 user-service、gateway-service 工程,并启动 Zipkin Serve。浏览器访问http://localhost:5000/user-api/user/hi,http://localhost:9411/zipkin/,查看 RabbitMQ Admin(http://localhost:15672/)
(?我使用RabbitMQ这个只成功了一次,后来Zipkin Serve就接受不到了,还在找原因...?)
请参考:https://windmt.com/2018/04/24/spring-cloud-12-sleuth-zipkin/
案例代码地址:https://github.com/cralor7/springcloud