前言
经过近期的努力,已经为公司初步搭建起了一套基于 springcloud、docker、k8s、gitlab-ci 的微服务结构。虽然可以整条线跑起来了,但是还有很多支撑服务有待完善,今天主要讲一下我是如何通过 Spring Cloud Sleuth + Zipkin 来解决分布式链路跟踪问题的,以及过程中出现的问题。
Spring Cloud Sleuth 简介
Spring Cloud Sleuth 为服务之间调用提供链路追踪。通过Sleuth可以很清楚的了解到一个服务请求经过了哪些服务,每个服务处理花费了多长。从而让我们可以很方便的理清各微服务间的调用关系。
Zipkin 简介
Zipkin 是一个开放源代码分布式的跟踪系统,由 Twitter 公司开源,它致力于收集服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集、存储、查找和展现。
每个服务向 Zipkin 报告计时数据,例如用户每次请求服务的处理时间等,可方便的监测系统中存在的瓶颈。
Zipkin 会根据调用关系通过 Zipkin UI 生成依赖关系图。
Spring Cloud Sleuth 与 Zipkin 的关系
Spring Cloud Sleuth 配合 Zipkin,将信息发送到 Zipkin,利用 Zipkin 的存储来存储信息,同时可以利用 Zipkin UI 来展示数据。Spring Cloud Sleuth 与需要进行供链路追踪的系统进行绑定。Spring Cloud 提供了spring-cloud-sleuth-zipkin 来方便集成 zipkin (指的是Zipkin Client,而不是Zipkin服务器),该 jar 包可以通过 spring-cloud-starter-zipkin 依赖来引入。
开始搭建 Zipkin Server
在整个结构中,我理解有这么几种角色:
- Zipkin Server
数据收集、存储、查找和展现。 - 作为 Zipkin Client 的「服务提供者」
通过 Spring Cloud Sleuth 将请求发送到 Zipkin Server。 - 「服务调用者」
向「服务提供者」发起请求。
前提条件
这里首先要说一下,本文中涉及到的所有系统,依赖的软件版本如下:
- spring-boot:2.1.3.RELEASE
- spring-cloud:Greenwich.SR1
一、搭建 Zipkin Server
Spring Boot 2.0 之前 Zipkin Server 是需要我们自己创建一个对应的 project 的。而 Spring Boot 2.0 之后,Zipkin 已不再推荐自定义 Zipkin Server 了,官方推荐了以下2种方式来部署 Zipkin Server:
- 通过 Jar 包部署
下载官方提供的可执行 Jar 包,例如:zipkin-server-2.12.9-exec.jar,然后在服务器上运行即可,例如:java -jar zipkin-server-2.12.9-exec.jar。 - 通过 image 部署
通过官方提供的镜像,例如:openzipkin/zipkin,采用 docke r或者 k8s 进行部署。
在前言中已经提到,我的所有服务是通过 k8s 进行部署的,所以,Zipkin Server 也不例外。k8s 环境如何搭建不是本文的重点,所以这里就不进行介绍了,直接说一下 Zipkin Server 所涉及的2个 YAML 文件:
- annoroad-zipkin-deployment.yaml
Zipkin Server 将会作为 k8s 的 Deployment 资源投递到 k8s 环境中,代码如下:
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: annoroad-zipkin
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: annoroad-zipkin
template:
metadata:
labels:
app: annoroad-zipkin
spec:
terminationGracePeriodSeconds: 60
containers:
- name: annoroad-zipkin
env:
- name: DEPLOY_TAG
value: tag5
- name: MY_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: MY_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: MY_POD_NAMESPACE # 传入当前命名空间
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: MY_POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: SERVICE_NAME # 因为pod 通过域名互相访问,需要使用headless服务名称
value: annoroad-zipkin-service
- name: STORAGE_TYPE
value: mysql
- name: MYSQL_HOST
value: rm-1azzwo8172t6v014mm.mysql.rds.aliyuncs.com
- name: MYSQL_USER
value: user
- name: MYSQL_PASS
value: 123321
- name: MYSQL_DB
value: annoroad_zipkin_database
- name: COLLECTOR_PORT
value: '10400'
- name: QUERY_PORT
value: '10401'
image: registry-vpc.cn-beijing.aliyuncs.com/annoroad-cloud/annoroad-zipkin:latest
imagePullPolicy: Always #每次都重新拉取镜像
readinessProbe:
httpGet:
path: /health
port: 10401
initialDelaySeconds: 30
timeoutSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 10401
initialDelaySeconds: 60
timeoutSeconds: 10
ports:
- name: http
containerPort: 10400
这里提到的 registry-vpc.cn-beijing.aliyuncs.com/annoroad-cloud/annoroad-zipkin:latest 镜像,其实就是 openzipkin/zipkin 镜像,为了公司内部统一镜像名称我将 openzipkin/zipkin 打成 tag(registry-vpc.cn-beijing.aliyuncs.com/annoroad-cloud/annoroad-zipkin ),过程如下:
- 拉取 openzipkin/zipkin 镜像
[root@123 /]# docker pull openzipkin/zipkin
- 打tag
[root@123 /]# docker tag openzipkin/zipkin registry.cn-beijing.aliyuncs.com/annoroad-cloud/annoroad-zipkin
- 推送到私有镜像仓库
[root@123 /]# docker push registry.cn-beijing.aliyuncs.com/annoroad-cloud/annoroad-zipkin
本文中 zipkin server 通过设置环境变量 STORAGE_TYPE:mysql 来指定存储类型(mysql),默认为缓存存储(STORAGE_TYPE:mem)。zipkin server 还可通过环境变量来获取 mysql 的相关信息,例如:MYSQL_HOST、MYSQL_USER、MYSQL_PASS 等等,所以我们可以根据真实环境来设置这些环境变量。我们还可以通过变量 STORAGE_TYPE 切换不同的存储策略。
这里还有注意两个PORT:
- COLLECTOR_PORT
The port to listen for thrift RPC scribe requests. Defaults to 9410 - QUERY_PORT
Listen port for the http api and web ui; Defaults to 9411。后边提到的 base_url 使用的是该端口。
- annoroad-zipkin-service.yaml
为 Zipkin Server 创建一个 k8s 对应的 Service 资源,使 Zipkin Server 在 k8s 环境中有一个固定的对外的入口,例如:登录 Zipkin UI 查询等等,代码如下:
apiVersion: v1
kind: Service
metadata:
name: annoroad-zipkin-service
labels:
app: annoroad-zipkin-service
spec:
ports:
- name: 4collector
port: 10400
protocol: TCP
targetPort: 10400
- name: 4query
port: 10401
protocol: TCP
targetPort: 10401
selector:
app: annoroad-zipkin
type: LoadBalancer
YAML 文件已经准备就绪,下面就可以部署 Zipkin Server 了,执行以下命令:
[root@123 /]# kubectl apply -f annoroad-zipkin-deployment.yaml -n test --record
[root@123 /]# kubectl create -f annoroad-zipkin-service.yaml -n test
Zipkin Server 已经部署了,我们如何能看到UI界面呢?首选要获取 Zipkin UI 访问入口,如下:
[root@123 /]# kubectl get svc -n test
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
annoroad-zipkin-service 252.16.10.32 23.6.80.234 10400:31351/TCP,10401:30040/TCP 1d
上边的 23.6.80.234 是一个公网地址,有了这个地址我们就可以访问 Zipkin UI 了。打开浏览器,在地址栏输入 http://23.6.80.234:10401,将会出现如下界面:
至此,恭喜你,Zipkin Server 已部署完成了!!!!!!!
这里还有一点需要说明的是,上图中的服务名 annoroad-alpha 对应的是 annoroad-alpha 项目中 application.xml 文件中的 spring.application.name,如下图:
如果未指定该值,则该 project(annoroad-alpha) 在 zipkin 中的服务名为 default。
二、配置 「服务提供者」
这里我以 annoroad-alpha 项目为例,步骤如下:
- 在 pom.xml 文件中增加依赖包,如下:
</dependencies>
<!-- zipkin 分布式请求跟踪监控 begin -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<!-- zipkin 分布式请求跟踪监控 end -->
</dependencies>
- 在application.yml文件中增加 Sleuth + ZipKin 相关配置,如下:
spring:
# 调用链 Sleuth + ZipKin
sleuth:
sampler:
# 采用比例,默认 0.1 全部采样 1.0
probability: 1.0
zipkin:
# 指定了 Zipkin 服务器的地址
base-url: http://annoroad-zipkin-service:10401/
这里的 annoroad-zipkin-service 对应了 k8s 环境中 name 为 annoroad-zipkin-service 的Service,也就是 annoroad-zipkin-service.yaml 定义的 Service。
annoroad-alpha 是如何将请求信息推送给 Zipkin Server 的呢?Zipkin 提供了很多种方式来实现。
本文采用的 http 的方式,也就 annoroad-alpha 通过请求 base-url 将请求 annoroad-alpha 的信息推送给 Zipkin Server。
这里再着重说一下初次配置 base-url 的几点疑惑:
- 10401 为 zipkin 的 QUERY_PORT,而不是 COLLECTER_PORT
- 对于 base-url,首先会将 base-url(http://annoroad-zipkin-service:10401/) 作为一个注册中心里的一个应用名称从注册中心查找相关服务,如果不存在,也不会报错,base-url 会被当做普通的 ip 地址,或者域名来处理,所以,后台打印如下信息不必理会!!!!!!
2019-09-27 14:27:37.863[INFO ]com.netflix.loadbalancer.BaseLoadBalancer: 197-Client: localhost instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=localhost,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
2019-09-27 14:27:37.871[INFO ]com.netflix.loadbalancer.DynamicServerListLoadBalancer: 222-Using serverListUpdater PollingServerListUpdater
2019-09-27 14:27:37.874[INFO ]com.netflix.loadbalancer.DynamicServerListLoadBalancer: 150-DynamicServerListLoadBalancer for client localhost initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=localhost,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@5161aaf1
三、「服务调用者」
这里说的「服务调用者」就是 Postman,通过 Postman 请求 annoroad-alpha 服务,annoroad-alpha 再通过请求 base-url 将请求信息推送给 Zipkin Server,最终效果如下:
四、Zipkin UI
这里拿一个实际的调用场景来讲一下 Zipkin UI,调用关系如下:
如上图,整个调用链是 postman -> annoroad-openapi -> annoroad-alpha -> annoroad-beta,这里 的 annoroad-alpha、annoroad-beta 增加了对 spring-cloud-starter-zipkin 的依赖,也就是说对着两个服务进行了调用链路跟踪。
打开 Zipkin UI,我们可以看到如下界面:
这里红框中的部分称为“Trace”,TA 是一系列“Span”组成的一个树状结构。我们点开 TA,将会看到如下界面:
这里 annoroad-alpha 为根 Span,此 Span 中 span id 和 trace id 值相同,两个子 Span 都是 annoroad-beta,一个是请求 /annoroad-beta/hello,另外一个是请求 /annoroad-beta/bizexp。Span 是基本的工作单元,TA 包括一个64位的唯一ID,一个64位 trace 码,描述信息,时间戳事件,key-value ,如下图: