九、Spring Cloud Sleuth 链路监控
官网:
Spring Cloud Sleuth
Zipkin
一、链路监控介绍
1. 简介
在互联网的发展中,随着用户量、并发量、数据量的增长,我们的架构也在逐渐演变: 单体架构 -> 集群 -> 垂直拆分 -> 水平拆分 -> SOA -> 微服务
,而此时的一个客户端的请求,可能已经不一次简单的http通信。以用户下单为例,可能是经过了 网关路由、用户服务(注册中心、负载均衡)、订单服务(注册中心、负载均衡)、商品服务(注册中心、负载均衡)..等等。而此时如果出现问题,问题的定位也不再是直接去读取容器的日志,而是需要有一个监控整个请求链路、收集每次请求的耗时、响应情况等,并收集这些数据进行可视化展示的服务组件。
链路监控开源技术:
- Zipkin : 进行数据收集、查找、展现
- Pinpoint
- Skywalking
- Cat
- Spring Cloud Sleuth
实现逻辑:
数据上报,每个集成了sleuth的节点将请求信息上报给 sleuth 服务。
监控服务进行数据收集,展示。
2. Spring Cloud Sleuth介绍
Spring Cloud Sleuth 提供了一套完整的服务跟踪的解决方案,在分布式系统中提供追踪解决方案并且兼容支持了zipkin。可以支持 链路追踪、性能分析、可视化。
术语(Terminology)
- Trace:
一系列spans组成的一个树状结构。TraceId标记一次完整的请求,可以看做一整个链路所有的请求统一的父Id - Span:
基本工作单元,例如,在一个新建的span中发送一个RPC等同于发送一个回应请求给RPC,span通过一个64位ID唯一标识,trace以另一个64位ID表示,span还有其他数据信息,比如摘要、时间戳事件、关键值注释(tags)、span的ID、以及进度ID(通常是IP地址)。span在不断的启动和停止,同时记录了时间信息,当你创建了一个span,你必须在未来的某个时刻停止它。 - Annotation:
用来及时记录一个事件的存在,一些核心annotations用来定义一个请求的开始和结束
cs- Client Sent -客户端发起一个请求,这个annotion描述了这个span的开始
sr- Server Received -服务端获得请求并准备开始处理它,如果将其sr减去cs时间戳便可得到网络延迟
ss- Server Sent -注解表明请求处理的完成(当请求返回客户端),如果ss减去sr时间戳便可得到服务端需要的处理请求时间
cr- Client Received -表明span的结束,客户端成功接收到服务端的回复,如果cr减去cs时间戳便可得到客户端从服务端获取回复的所有所需时间
以具体的一张链路监控图为例:
service1 --调用--> service2; service2 --调用--> service3; service2 --调用--> service4
二、模拟下单进行链路监控
1. 模拟下单链路
首先我们需要创建一套比较完整的链路,然后在每个链路节点添加 Sleuth 进行链路的监控。
以用户购买商品下单为例,模拟下单流程,结合之前使用到的Spring Cloud Netflix组件完成下单场景:
对购物车内1到多件商品进行下单,访问下单请求,传入: 商品id、商品数量、用户id等。
![](https://img2020.cnblogs.com/blog/1660657/202008/1660657-20200817212739085-1018685885.png)
模拟下单请求: (下单做的三件事:a.锁库存 b.获取商品信息 c.生成订单)
1. 首先客户端发起下单服务,访问网关gateway
2. 网关路由,将下单请求通过负载均衡分发至订单服务 order-service (eureka、openfeign、ribbon)
3. 订单服务
a. 负载均衡访问商品服务,锁定库存 goods-service
b. 负载均衡访问商品服务,获取订单对应商品列表明细数据 goods-service
c. 订单服务内部根据商品数据生成订单 order-service
具体项目架构不在此详述。
项目结构:
demo地址: 案例demo地址
测试结果:
2. 集成 Sleuth 进行链路监控
集成Spring Cloud Slueth+Zipkin实现链路监控,并分析链路监控中的一些关键指标和因素
a. 添加sleuth依赖
如果需要对整个订单链路进行监控,每个节点都需要引入 sleuth 依赖,我们这里对这条链路上的 gateway、order、goods服务添加sleuth组件依赖。
<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>
b. 启用sleuth日志
gateway服务日志配置:
logging:
level:
root: debug
订单、商品服务日志配置:
logging:
level:
org.springframework.cloud.sleuth: debug
c. 分析日志
之后,我们再次发起下单请求,观察后台日志:
网关服务日志:
2019-08-17 22:34:55.047 DEBUG [spring-cloud-gateway-8080,73bf090c1f6f8f6e,73bf090c1f6f8f6e,false] 14040 --- [ctor-http-nio-3] o.s.c.g.h.RoutePredicateHandlerMapping : Route matched: order_route
2019-08-17 22:34:55.047 DEBUG [spring-cloud-gateway-8080,73bf090c1f6f8f6e,73bf090c1f6f8f6e,false] 14040 ... ...
2019-08-17 22:34:55.047 DEBUG [spring-cloud-gateway-8080,73bf090c1f6f8f6e,73bf090c1f6f8f6e,false] 14040 ... ...
... ...
... ...
订单服务日志: 通过feign发起了两次商品服务的调用
2019-08-17 22:34:55.057 DEBUG [shen-mall-order-service,73bf090c1f6f8f6e,09950cf87d99f8a1,false] 14468 --- [nio-8082-exec-3] c.s.i.w.c.f.TraceLoadBalancerFeignClient : Before send
2019-08-17 22:34:55.057 DEBUG [shen-mall-order-service,73bf090c1f6f8f6e,09950cf87d99f8a1,false] 14468 --- [nio-8082-exec-3] o.s.c.s.i.w.c.f.LazyTracingFeignClient : Sending a request via tracing feign client [org.springframework.cloud.sleuth.instrument.web.client.feign.TracingFeignClient@153cc4c4] and the delegate [feign.Client$Default@2fabcbd1]
2019-08-17 22:34:55.058 DEBUG [shen-mall-order-service,73bf090c1f6f8f6e,09950cf87d99f8a1,false] 14468 --- [nio-8082-exec-3] o.s.c.s.i.w.c.feign.TracingFeignClient : Handled send of NoopSpan(73bf090c1f6f8f6e/7e2603d8bb55ac66)
2019-08-17 22:34:55.088 DEBUG [shen-mall-order-service,73bf090c1f6f8f6e,09950cf87d99f8a1,false] 14468 --- [nio-8082-exec-3] o.s.c.s.i.w.c.feign.TracingFeignClient : Handled receive of NoopSpan(73bf090c1f6f8f6e/7e2603d8bb55ac66)
2019-08-17 22:34:55.089 DEBUG [shen-mall-order-service,73bf090c1f6f8f6e,09950cf87d99f8a1,false] 14468 --- [nio-8082-exec-3] c.s.i.w.c.f.TraceLoadBalancerFeignClient : After receive
2019-08-17 22:34:55.091 DEBUG [shen-mall-order-service,73bf090c1f6f8f6e,09950cf87d99f8a1,false] 14468 --- [nio-8082-exec-3] c.s.i.w.c.f.TraceLoadBalancerFeignClient : Before send
2019-08-17 22:34:55.092 DEBUG [shen-mall-order-service,73bf090c1f6f8f6e,09950cf87d99f8a1,false] 14468 --- [nio-8082-exec-3] o.s.c.s.i.w.c.f.LazyTracingFeignClient : Sending a request via tracing feign client [org.springframework.cloud.sleuth.instrument.web.client.feign.TracingFeignClient@153cc4c4] and the delegate [feign.Client$Default@2fabcbd1]
2019-08-17 22:34:55.093 DEBUG [shen-mall-order-service,73bf090c1f6f8f6e,09950cf87d99f8a1,false] 14468 --- [nio-8082-exec-3] o.s.c.s.i.w.c.feign.TracingFeignClient : Handled send of NoopSpan(73bf090c1f6f8f6e/f43adf54d8533001)
2019-08-17 22:34:55.101 DEBUG [shen-mall-order-service,73bf090c1f6f8f6e,09950cf87d99f8a1,false] 14468 --- [nio-8082-exec-3] o.s.c.s.i.w.c.feign.TracingFeignClient : Handled receive of NoopSpan(73bf090c1f6f8f6e/f43adf54d8533001)
2019-08-17 22:34:55.101 DEBUG [shen-mall-order-service,73bf090c1f6f8f6e,09950cf87d99f8a1,false] 14468 --- [nio-8082-exec-3] c.s.i.w.c.f.TraceLoadBalancerFeignClient : After receive
商品服务日志:
2019-08-17 22:34:55.060 INFO [shen-mall-goods-service,73bf090c1f6f8f6e,7e2603d8bb55ac66,false] 7364 --- [nio-8083-exec-9] c.b.g.g.feign.GoodsFeignClient : begin GoodsFeignClient.decreaseStock: [ItemStockDto(itemId=100023501, num=3), ItemStockDto(itemId=100026701, num=2)]
2019-08-17 22:34:55.097 INFO [shen-mall-goods-service,73bf090c1f6f8f6e,f43adf54d8533001,false] 7364 --- [io-8083-exec-10] c.b.g.g.feign.GoodsFeignClient : begin GoodsFeignClient.getItemsByIds: [100023501, 100026701]
可见,当我们配置了sleuth
时,后台服务在记录日志时,会添加类似于[spring-cloud-gateway-8080,73bf090c1f6f8f6e,73bf090c1f6f8f6e,false]
格式的日志,记录链路的情况。
其中格式为:
[appname,tranceId,spanId,true/false]
appname: 服务名
tranceId: 标记一次完整的请求,一整个链路所有的请求统一的父Id
spanId: 一次请求生成一个spanId
true/false: 是否需要把输出的数据展示到其它平台,上报信息。
Annonation: 标记
CS: Client Sent
-> SR: Server Received
-> SS: Server Sent
-> CR: Client Received
有了 cs、sr、ss、cr这些标记,我们就会知道整个链路里的一个请求从CS
客户端发起到SR
服务端接接受的具体时间,两种相减就可得出网络通信时长;SR
-SS
时间可得出服务端执行逻辑所需时长..等
但这些需要手动计算,其实spring cloud已经提供了可视化的工具帮助我们监控,比如spring-cloud-starter-zipkin
3. 集成 Zipkin 实现链路数据可视化监控
a. 安装zipkin服务
首先参考官网文档,安装Zipkin
curl -sSL https://zipkin.io/quickstart.sh | bash -s
启用服务
java -jar zipkin.jar
此时访问9411端口 http://192.168.197.128:9411/zipkin/可以打开链路监控页面
b. 链路服务添加依赖配置
网关、订单、商品服务添加zip依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
网关、订单、商品服务添加配置
spring:
...
...
sleuth:
sampler:
probability: 1.0 # sleuth取样概率 0.5代表50%
zipkin:
base-url: http://192.168.197.128:9411/
c. zipkin UI 监控
此时我们重发一次下单请求,会发现服务日志会变成这样的
2019-08-19 21:14:43.632 DEBUG [spring-cloud-gateway-8080,51ff0b531711ad0c,51ff0b531711ad0c,true] ...
2019-08-19 21:14:43.632 DEBUG [spring-cloud-gateway-8080,51ff0b531711ad0c,51ff0b531711ad0c,true] ...
2019-08-19 21:14:43.632 DEBUG [spring-cloud-gateway-8080,51ff0b531711ad0c,51ff0b531711ad0c,true] ...
最后一位已经变为true
,表示监控已经上报。
打开 http://192.168.197.128:9411/zipkin/ 也可以看到链路跟踪请求
可以看到请求总耗时,每个节点请求的耗时,请求类型,CS、SR、SS、CR
等
- 链路里有Redis是因为网关gateway使用了redis做持久化操作,会从redis读取最新的网关数据;
- 并行执行 gateway发起 post请求,耗时 1.07s
- 网关路由至订单服务 orer-service,post请求
- 之后调用商品服务 goods-service, 先调用put请求扣减库存,之后调用 get请求查询商品明细
和我们设计的链路一模一样,展示的非常清楚。首次请求耗时1s多是因为redis等的数据预热,之后订单请求基本上100ms左右。
当然,如果服务链路之间出去请求异常,Zipkin UI内也会统计展示。
d. zipkin设计
zipkin本身包含
Collector : 收集模块,收集节点上报的数据进行验证
Storage : 进行数据的持久化模块,支持像mysql、es等,默认内存存储
Search : 搜索模块
UI : 页面展示模块