项目的源码地址
Spring Cloud Alibaba 工程搭建(1)
Spring Cloud Alibaba 工程搭建连接数据库(2)
Spring Cloud Alibaba 集成 nacos 以及整合 Ribbon 与 Feign 实现负载调用(3)
Spring Cloud Alibaba Ribbon 负载调用说明(4)
Spring Cloud Alibaba 核心理论 CAP与BASE理论简单理解(5)
Spring Cloud Alibaba Sentinel 集成与限流实战(6)
Spring Cloud Alibaba 网关 Gateway 集成(7)
前面我们已经搭建了好几个组件了,会发现,其实就是各个组件的引入,以及相关的配置,其实如果是简单使用的话,这块不算复杂,我们先从简单入手嘛,后面有个基础或者概念了,就可以深入去学习了。但是在基础上面我们会遇到一个问题,就是分布式的环境下面,怎么能快速定位问题呢?
问题的复杂性
这里我们先抛出两个常见问题
- 微服务调用链路出现了问题怎么快速排查?
- 微服务调用链路耗时长怎么定位是哪个服务?
链路追踪系统
分布式应用架构虽然满足了应用横向扩展的需求,但是运维和诊断的过程变得越来越复杂,例如会遇到接口诊断困难、应用性能诊断复杂、架构分析复杂等难题,传统的监控工具并无法满足,分布式链路系统由此诞生。
核心在于将一次请求分布式调用,使用GPS定位串起来,记录每个调用的耗时、性能等日志,并通过可视化工具展示出来。
AlibabaCloud全家桶还没对应的链路追踪系统,我们使用Sleuth和zipking搭建先。
Sleuth 链路追踪
Spring Cloud Sleuth 为 Spring Cloud 实现了分布式跟踪解决方案。兼容 Zipkin,HTrace 和其他基于日志的追踪系统,例如 ELK(Elasticsearch 、Logstash、 Kibana)。
Spring Cloud Sleuth
提供了以下功能:
- 链路追踪:通过 Sleuth 可以很清楚的看出一个请求都经过了那些服务,可以很方便的理清服务间的调用关系等。
- 性能分析:通过 Sleuth 可以很方便的看出每个采样请求的耗时,分析哪些服务调用比较耗时,当服务调用的耗时随着请求量的增大而增大时, 可以对服务的扩容提供一定的提醒。
- 数据分析:优化链路:对于频繁调用一个服务,或并行调用等,可以针对业务做一些优化措施。
- 可视化错误:对于程序未捕获的异常,可以配合 Zipkin 查看。
项目集成
先把网关的部分测试功能屏蔽掉
全局的过滤器注释掉 @Component
,这样子就不会起作用了
在每个模块的Pom 文件下面都加上 sleuth 的依赖包
<!--添加 sleuth -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
现在访问下:http://localhost:8888/order-server/api/v1/video_order/findById?videoId=30
接下来我们分别看下对应服务的日志输出
这里我们将三个日志都贴出来:
## gateway 网关
[api-gateway,c5c191b5b83b38da,c5c191b5b83b38da,true]
## 订单服务
[demo-order,c5c191b5b83b38da,0e1fd1455f72d197,true]
## 视频服务
[demo-video,c5c191b5b83b38da,a30ced4f7a906cbb,true]
参数说明
第一个值,spring.application.name 的值
第二个值,c5c191b5b83b38da,sleuth 生成的一个ID,叫Trace ID,用来标识一条请求链路,一条请求链路中包含一个Trace ID,多个Span ID
第三个值,c5c191b5b83b38da、span id 基本的工作单元,获取元数据,如发送一个http
第四个值:false,是否要将该信息输出到 zipkin 服务中来收集和展示。
相关术语
-
Trace (一条完整链路–包含很多span(微服务接口))
由一组Trace Id(贯穿整个链路)相同的Span串联形成一个树状结构。为了实现请求跟踪,当请求到达分布式系统的入口端点时,只需要服务跟踪框架为该请求创建一个唯一的标识(即TraceId),同时在分布式系统内部流转的时候,框架始终保持传递该唯一值,直到整个请求的返回。那么我们就可以使用该唯一标识将所有的请求串联起来,形成一条完整的请求链路。 -
Span
代表了一组基本的工作单元。为了统计各处理单元的延迟,当请求到达各个服务组件的时候,也通过一个唯一标识(SpanId)来标记它的开始、具体过程和结束。通过SpanId的开始和结束时间戳,就能统计该span的调用时间,除此之外,我们还可以获取如事件的名称。请求信息等元数据。 -
Annotation
用它记录一段时间内的事件,内部使用的重要注释:- cs(Client Send)客户端发出请求,开始一个请求的生命
- sr(Server Received)服务端接受到请求开始进行处理, sr-cs = 网络延迟(服务调用的时间)
- ss(Server Send)服务端处理完毕准备发送到客户端,ss - sr = 服务器上的请求处理时间
- cr(Client Reveived)客户端接受到服务端的响应,请求结束。 cr - sr = 请求的总时间
Zipkin
什么是zipkin?
Zipkin 是 Twitter 的一个开源项目,它基于Google Dapper实现,它致力于收集服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集、存储展现、查找和我们可以使用它来收集各个服务器上请求链路的跟踪数据,并通过它提供的REST API接口来辅助我们查询跟踪数据以实现对分布式系统的监控程序。
也提供了方便的UI组件来帮助我们直观的搜索跟踪信息和分析请求链路明细,比如:可以查询某段时间内各用户请求的处理时间等。
下载
我这边使用的是这个这个版本 zipkin-server-2.12.9-exec.jar
java -jar zipkin-server-2.12.9-exec.jar
访问:http://127.0.0.1:9411/zipkin/
zipkin组成:Collector、Storage、Restful API、WebUI组成
关于整个链路的时序图如下,其实通过时序图可以看到后面在返回给 User 的时候,有一个异步的操作将数据给到了 Collector 了,可以点击下这里,看下官方的说明
Zipkin+Sleuth整合实战
流程说明
这里我们来重复地说下这个过程:
- Sleuth 收集跟踪信息通过 http 请求发送给zipkin server
- Zipkin server进行跟踪信息的存储以及提供 Rest API 即可
- Zipkin UI调用其API接口进行数据展示默认存储是内存,可也用 mysql 或者 elasticsearch 等存储
项目集成
在每个模块的POM文件上面都增加依赖
<!--添加 zipkin -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
配置地址和采样百分比配置
spring:
application:
name: api-gateway
zipkin:
base-url: http://127.0.0.1:9411/ #zipkin地址
discovery-client-enabled: false #不用开启服务发现
sleuth:
sampler:
probability: 1.0 #采样百分
默认为0.1,即10%,这里配置1,是记录全部的sleuth信息,是为了收集到更多的数据(仅供测试用)。
在分布式系统中,过于频繁的采样会影响系统性能,所以这里配置需要采用一个合适的值。
启动服务
访问下:http://localhost:8888/order-server/api/v1/video_order/findById?videoId=30
这个时候控制台,也会有对应的日志出来
这里我们访问下zipkin 的面板,刷新下就可以看到相关的信息了
点击上面的请求信息,这里我们可以看到,整个请求耗时是:1.456s,经过了 3 个服务(所以深度为3)
- 第一个 api-gateway:就是整个请求的记录耗时
- 第二个 api-gateway:就是从网关开始到返回给前端的耗时
- 第三个 demo-order: 是从订单服务进入,然后返回给 api-gateway 的耗时
- 第四个 demo-video:是在视频服务中的耗时
这里点击具体的一块,可以看到明细信息
那我多请求几次看看,会发现下面请求时长就变化了,由于第一次请求会有一个预热的过程,所以第一次会慢一点
当我请求多次list接口:http://localhost:8888/order-server/api/v1/video_order/list,会发现报错的请求是这样子的
点击这里可以看到依赖分析
Zipkin持久化配置
首先,我们先看下官网地址,点击这里看看,我们可以放到 MongoDB,或者 ES,MYSQL 上面,都是可以的。
Zipkin 持久化
需要在我们的数据库增加下面的表
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 启动的时候,需要自定数据源就好
java -jar zipkin-server-2.12.9-exec.jar --STORAGE_TYPE=mysql --MYSQL_HOST=127.0.0.1 --MYSQL_TCP_PORT=3306 --MYSQL_DB=zipkin_log --MYSQL_USER=admin --MYSQL_PASS=123456