作者为冰河忠实粉丝,可以加入【冰河技术】知识星球获取源码,也可以关注【冰河技术】公众号,加入社群学习,学习氛围好,干货满满。
1 分布式链路追踪
随着互联网业务快速扩展,软件架构也日益变得复杂,为了适应海量用户高并发请求,系统中越来越多的组件开始走向分布式化,如单体架构拆分为微服务、服务内缓存变为分布式缓存、服务组件通信变为分布式消息,这些组件共同构成了繁杂的分布式网络。
在这种背景下,一次请求往往需要涉及到多个服务,需要一些可以帮助理解系统行为、用于分析性能问题的工具,以便发生故障的时候,能够快速定位和解决问题。
单体架构中可以使用AOP在调用具体的业务逻辑前后分别打印一下时间即可计算出整体的调用时间,使用 AOP捕获异常也可知道是哪里的调用导致的异常。
但是在分布式微服务场景下,使用AOP技术是无法追踪到各个微服务的调用情况的,也就无法知道系统中处理一次请求的整体调用链路。
另外,在分布式与微服务场景下,我们需要解决如下问题:
- 如何快速发现并定位到问题。
- 如何尽可能精确的判断故障对系统的影响范围与影响程度。
- 如何尽可能精确的梳理出服务之间的依赖关系,并判断出服务之间的依赖关系是否合理。
- 如何尽可能精确的分析整个系统调用链路的性能与瓶颈点。
- 如何尽可能精确的分析系统的存储瓶颈与容量规划。
- 如何实时观测系统的整体调用链路情况。
上述问题就是分布式链路追踪技术要解决的问题,分布式链路追踪(Distributed Tracing),就是将一次分布式请求还原成调用链路,进行日志记录,性能监控并将一次分布式请求的调用情况集中展示。比如各个服务节点上的耗时、请求具体到达哪台机器上、每个服务节点的请求状态等等。
1.1 核心原理
微服务调用链路图
Server1节点:Span ID = A,Parent ID = null,Service 1 接收到请求。
Server2节点:Span ID = B,Parent ID= A,Service 1 发送请求到 Service 2 返回响应给Service 1 的过程。
Server3节点:Span ID = C,Parent ID= B,Service 2 发送请求到 Service 3 返回响应给Service 2 的过程。
Server4节点:Span ID = D,Parent ID= B,Service 2 发送请求到 Service 4 返回响应给Service 2 的过程。
Server5节点:Span ID = E,Parent ID= C,Service 3 发送请求到 Service 5 返回响应给Service 3 的过程。
Server6节点:Span ID = F,Parent ID= D,Service 4 发送请求到 Service 6 返回响应给Service 4 的过程。
通过 Parent ID 就可找到父节点,整个链路即可以进行跟踪追溯了。
1.2 解决方案
目前,行业内比较成熟的分布式链路追踪技术解决方案如下所示。
技术 | 说明 |
---|---|
Cat | 由大众点评开源,基于Java开发的实时应用监控平台,包括实时应用监控,业务监控 。 集成方案是通过代码埋点的方式来实现监控,比如: 拦截器,过滤器等。 对代码的侵入性很大,集成成本较高。风险较大。 |
ZipKin | 由Twitter公司开源,开放源代码分布式的跟踪系统,用于收集服务的定时数据,以解决微服务架构中的延迟问题,包括:数据的收集、存储、查找和展现。结合spring-cloud-sleuth使用较为简单, 集成方便, 但是功能较简单。 |
Pinpoint | Pinpoint是一款开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点是支持多种插件, UI功能强大,接入端无代码侵入。 |
Skywalking | SkyWalking是国人开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点是支持多 |
种插件, UI功能较强,接入端无代码侵入。 | |
Sleuth | Sleuth为Spring Cloud实现了分布式跟踪解决方案。 |
2 项目整合Sleuth实现链路追踪
Sleuth提供了分布式链路追踪能力,如果需要使用Sleuth的链路追踪功能,需要在项目中集成Sleuth。
2.1 简单实用
- 在每个微服务(用户微服务shop-user、商品微服务shop-product、订单微服务shop-order、网关服务shop-gateway)下的pom.xml文件中添加如下Sleuth的依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
- 将项目的application.yml文件备份成application-pre-filter.yml,并将application.yml文件的内容替换为application-sentinel.yml文件的内容,这一步是为了让整个项目集成Sentinel、SpringCloud Gateway和Nacos。application.yml替换后的文件内容如下所示。
server:
port: 10001
spring:
application:
name: server-gateway
main:
allow-bean-definition-overriding: true
cloud:
nacos:
discovery:
server-addr: ${NACOS_HOST:shop-nacos}:${NACOS_PORT:8848}
sentinel:
transport:
port: 7777
dashboard: 127.0.0.1:8888
web-context-unify: false
eager: true
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOriginPatterns: "*"
allowedMethods: "*"
allowCredentials: true
allowedHeaders: "*"
discovery:
locator:
enabled: true
route-id-prefix: gateway-
- 分别启动Nacos、Sentinel、用户微服务shop-user,商品微服务shop-product,订单微服务shop-order和网关服务shop-gateway,在浏览器中输入链接 http://localhost:10001/server-order/order/submit_order?userId=1001&productId=1001&count=1,如下所示。
- 分别查看用户微服务shop-user,商品微服务shop-product,订单微服务shop-order和网关服务shop-gateway的控制台输出,每个服务的控制台都输出了如下格式所示的信息。
[微服务名称,TraceID,SpanID,是否将结果输出到第三方平台]
具体如下绿色部分所示,可能因为版本关系,作者没有展示是否将结果输出到第三方平台结果,后续再优化。
- 用户服务 shop-user
- 商品服务 shop-product
- 订单服务 shop-order
- 网关服务 shop-gateway
可以看到,每个服务都打印出了链路追踪的日志信息,说明引入Sleuth的依赖后,就可以在命令行查看链路追踪情况。
2.2 抽样采集数据
Sleuth支持抽样采集数据。尤其是在高并发场景下,如果采集所有的数据,那么采集的数据量就太大了,非常耗费系统的性能。通常的做法是可以减少一部分数据量,特别是对于采用Http方式去发送采集数据,能够提升很大的性能。
Sleuth可以采用如下方式配置抽样采集数据。
spring:
sleuth:
sampler:
probability: 1.0
2.3 追踪自定义线程池
Sleuth支持对异步任务的链路追踪,在项目中使用@Async注解开启一个异步任务后,Sleuth会为异步任务重新生成一个Span。但是如果使用了自定义的异步任务线程池,则会导致Sleuth无法新创建一个Span,而是会重新生成Trace和Span。此时,需要使用Sleuth提供的LazyTraceExecutor类来包装下异步任务线程池,才能在异步任务调用链路中重新创建Span。
在服务中开启异步线程池任务,需要使用@EnableAsync。所以,在演示示例前,先在用户微服务shop-user的cn.mawenda.shop.ShopUserApplication启动类上添加@EnableAsync注解,如下所示。
/**
* 用户服务启用类
* @Author: Ma.wenda
* @Date: 2023-11-21 11:30
* @Version: 1.0
**/
@SpringBootApplication
@EnableTransactionManagement(proxyTargetClass = true)
@MapperScan(value = { "cn.mawenda.shop.user.mapper" })
@EnableDiscoveryClient
@EnableAsync
public class ShopUserApplication {
public static void main(String[] args) {
SpringApplication.run(ShopUserApplication.class, args);
}
}
2.3.1 演示使用@Async注解开启任务
- 在用户微服务shop-user的cn.mawenda.shop.user.service.UserService接口中定义一个asyncMethod()方法,如下所示。
/**
* 演示异步
*/
void asyncMethod();
- 在用户微服务shop-user的cn.mawenda.shop.user.service.impl.UserServiceImpl类中实现asyncMethod()方法,并在asyncMethod()方法上添加@Async注解,如下所示。
@Async
@Override
public void asyncMethod() {
log.info("执行了异步任务...");
}
- 在用户微服务shop-user的cn.mawenda.shop.user.controller.UserController类中新增asyncApi()方法,如下所示。
@GetMapping(value = "/async/api")
public String asyncApi() {
log.info("执行异步任务开始...");
userService.asyncMethod();
log.info("异步任务执行结束...");
return "asyncApi";
}
- 分别启动用户微服务和网关服务,在浏览器中输入链接http://localhost:10001/server-user/user/async/api
- 分别查看用户微服务和网关服务的控制台日志,如下所示。
- 用户微服务
- 网关服务
2.3.2 演示自定义任务线程池
在演示使用@Async注解开启任务的基础上继续演示自定义任务线程池,验证Sleuth是否为自定义线程池新创建了Span。
- 在用户微服务shop-user中新建cn.mawenda.shop.user.config包,在包下创建ThreadPoolTaskExecutorConfig类,继承org.springframework.scheduling.annotation.AsyncConfigurerSupport类,用来自定义异步任务线程池,代码如下所示。
/**
* Sleuth异步线程池配置
* @Author: Ma.wenda
* @Date: 2024-05-11 11:53
* @Version: 1.0
**/
@Configuration
@EnableAutoConfiguration
public class ThreadPoolTaskExecutorConfig extends AsyncConfigurerSupport {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(10);
executor.setThreadNamePrefix("trace-thread-");
executor.initialize();
return executor;
}
}
- 以debug的形式启动用户微服务和网关服务,并在cn.mawenda.shop.user.config.ThreadPoolTaskExecutorConfig#getAsyncExecutor()方法中打上断点,如下所示。
可以看到,项目启动后并没有进入cn.mawenda.shop.user.config.ThreadPoolTaskExecutorConfig#getAsyncExecutor()方法,说明项目启动时,并不会创建异步任务线程池。
- 在浏览器中输入链接http://localhost:10001/server-user/user/async/api,此时可以看到程序已经执行到cn.mawenda.shop.user.config.ThreadPoolTaskExecutorConfig#getAsyncExecutor()方法的断点位置。
说明异步任务线程池是在调用了异步任务的时候创建的。
接下来,按F8跳过断点继续运行程序,可以看到浏览器上的显示结果如下。
- 查看用户微服务与网关服务的控制台日志,分别存在如下日志。
- 用户微服务
- 网关服务
可以看到,使用自定义异步任务线程池时,在用户微服务中在执行异步任务时,重新生成了Trace和Span。
注意对比用户微服务中输出的三条日志信息,最后一条日志信息的TraceID和SpanID与前两条日志都不同。(此处没有复现两条ID都一样的情况)
2.3.2 演示包装自定义线程池
在自定义任务线程池的基础上继续演示包装自定义线程池,验证Sleuth是否为包装后的自定义线程池新创建了Span。
- 在用户微服务shop-user的cn.mawenda.shop.user.config.ThreadPoolTaskExecutorConfig类中注入BeanFactory,并在getAsyncExecutor()方法中使用org.springframework.cloud.sleuth.instrument.async.LazyTraceExecutor()来包装返回的异步任务线程池,修改后的cn.mawenda.shop.user.config.ThreadPoolTaskExecutorConfig类的代码如下所示。
/**
* Sleuth异步线程池配置
* @Author: Ma.wenda
* @Date: 2024-05-11 11:53
* @Version: 1.0
**/
@Configuration
@EnableAutoConfiguration
public class ThreadPoolTaskExecutorConfig extends AsyncConfigurerSupport {
@Autowired
private BeanFactory beanFactory;
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(10);
executor.setThreadNamePrefix("trace-thread-");
executor.initialize();
return new LazyTraceExecutor(this.beanFactory, executor);
}
}
- 分别启动用户微服务和网关服务,在浏览器中输入链接http://localhost:10001/server-user/user/async/api
- 查看用户微服务与网关服务的控制台日志,分别存在如下日志。
- 用户微服务
- 网关服务
2.4 自定义链路过滤器
在Sleuth中存在链路过滤器,并且还支持自定义链路过滤器。
2.4.1 自定义链路过滤器概述
TracingFilter是Sleuth中负责处理请求和响应的组件,可以通过注册自定义的TracingFilter实例来实现一些扩展性的需求。
2.4.2 演示自定义链路过滤器
本案例演示通过过滤器验证只有HTTP或者HTTPS请求才能访问接口,并且在访问的链接不是静态文件时,将traceId放入HttpRequest中在服务端获取,并在响应结果中添加自定义Header,名称为SLEUTH-HEADER,值为traceId。
- 在用户微服务shop-user中新建cn.mawenda.shop.user.filter包,并创建MyGenericFilter类,继承org.springframework.web.filter.GenericFilterBean类,代码如下所示。
@Component
@Order( Ordered.HIGHEST_PRECEDENCE + 6)
public class MyGenericFilter extends GenericFilterBean {
private Pattern skipPattern = Pattern.compile(SleuthWebProperties.DEFAULT_SKIP_PATTERN);
private final Tracer tracer;
public MyGenericFilter(Tracer tracer){
this.tracer = tracer;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)){
throw new ServletException("只支持HTTP访问");
}
Span currentSpan = this.tracer.currentSpan();
if (currentSpan == null) {
chain.doFilter(request, response);
return;
}
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = ((HttpServletResponse) response);
boolean skipFlag = skipPattern.matcher(httpServletRequest.getRequestURI()).matches();
if (!skipFlag){
String traceId = currentSpan.context().traceId();
httpServletRequest.setAttribute("traceId", traceId);
httpServletResponse.addHeader("SLEUTH-HEADER", traceId);
}
chain.doFilter(httpServletRequest, httpServletResponse);
}
}
- 在用户微服务shop-user的cn.mawenda.shop.user.controller.UserController类中新建sleuthFilter()方法,在sleuthFilter()方法中获取并打印traceId,如下所示。
@GetMapping(value = "/sleuth/filter/api")
public String sleuthFilter(HttpServletRequest request) {
Object traceIdObj = request.getAttribute("traceId");
String traceId = traceIdObj == null ? "" : traceIdObj.toString();
log.info("获取到的traceId为: " + traceId);
return "sleuthFilter";
}
- 分别启动用户微服务和网关服务,在浏览器中输入http://localhost:10001/server-user/user/sleuth/filter/api,如下所示。
查看浏览器的控制台,看到在响应的结果信息中新增了一个名称为SLEUTH-HEADER,值为f63ae7702f6f4bba的Header,如下所示。
说明使用Sleuth的过滤器可以处理请求和响应信息,并且可以在Sleuth的过滤器中获取到TraceID。
3 Sleuth整合ZipKin
3.1 ZipKin核心架构
Zipkin 是 Twitter 的一个开源项目,它基于Google Dapper论文实现,可以收集微服务运行过程中的实时链路数据,并进行展示。
3.1.1 ZipKin概述
Zipkin是一种分布式链路跟踪系统,能够收集微服务运行过程中的实时调用链路信息,并能够将这些调用链路信息展示到Web界面上供开发人员分析,开发人员能够从ZipKin中分析出调用链路中的性能瓶颈,识别出存在问题的应用程序,进而定位问题和解决问题。
3.1.2 ZipKin核心架构
ZipKin的核心架构图如下所示。
其中,ZipKin核心组件的功能如下所示。
- Reporter:ZipKin中上报链路数据的模块,主要配置在具体的微服务应用中。
- Transport:ZipKin中传输链路数据的模块,此模块可以配置为Kafka,RocketMQ、RabbitMQ等。
- Collector:ZipKin中收集并消费链路数据的模块,默认是通过http协议收集,可以配置为Kafka消费。
- Storage:ZipKin中存储链路数据的模块,此模块的具体可以配置为ElasticSearch、Cassandra或者MySQL,目前ZipKin支持这三种数据持久化方式。
- API:ZipKin中的API 组件,主要用来提供外部访问接口。比如给客户端展示跟踪信息,或是开放给外部系统实现监控等。
- UI: ZipKin中的UI 组件,基于API组件实现的上层应用。通过UI组件用户可以方便并且很直观地查询和分析跟踪信息。
Zipkin在总体上会分为两个端,一个是Zipkin服务端,一个是Zipkin客户端,客户端主要是配置在微服务应用中,收集微服务中的调用链路信息,将数据发送给ZipKin服务端。
3.2 项目整合ZipKin
Zipkin总体上分为服务端和客户端,我们需要下载并启动ZipKin服务端的Jar包,在微服务中集成ZipKin的客户端。
3.2.1 下载安装ZipKin服务端
- 下载ZipKin服务端Jar文件,可以直接在浏览器中输入如下链接进行下载。
# 本地下载
https://search.maven.org/remote_content?g=io.zipkin.java&a=zipkin-server&v=LATEST&c=exec
# Linux 下载
wget https://search.maven.org/remote_content?g=io.zipkin.java&a=zipkin-server&v=LATEST&c=exec
这里,我通过浏览器下载的ZipKin服务端Jar文件为:zipkin-server-2.12.9-exec.jar。
- 在命令行输入如下命令启动ZipKin服务端
java -Dserver.port=9411 -Dcsp.sentinel.dashboard.server=localhost:9411 -Dproject.name=zipkin-server -jar zipkin-server-2.12.9-exec.jar
- 由于ZipKin服务端启动时,默认监听的端口号为9411,所以,在浏览器中输入http://localhost:9411链接就可以打开ZipKin的界面,如下所示
3.2.2 项目整合ZipKin客户端
- 在每个微服务(用户微服务shop-user,商品微服务shop-product,订单微服务shop-order,网关服务shop-gateway)中添加ZipKin依赖,如下所示。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
- 在网关服务shop-gateway的application.yml文件中添加如下配置。
spring:
sleuth:
sampler:
probability: 1.0
zipkin:
base-url: http://127.0.0.1:9411
discovery-client-enabled: false
其中各配置的说明如下所示。
- spring.sleuth.sampler.probability:表示Sleuth的采样百分比。
- spring.zipkin.base-url:ZipKin服务端的地址。
- spring.zipkin.discovery-client-enabled:配置成false,使Nacos将其当成一个URL,不要按服务名处理。
- 分别启动用户微服务,商品微服务,订单微服务和服务网关,在浏览器中访问链接http://localhost:10001/server-order/order/submit_order?userId=1001&productId=1001&count=1,如下所示。
- 点击Zipkin界面上的查找按钮,如下所示。
可以看到网关到各服务的请求顺序与耗时情况
- 点开ZipKin界面上显示的调用链路,如下所示。
点开后的界面如下所示。
可以非常清晰的看到整个调用的访问链路。
我们还可以点击具体的节点来查看具体的调用信息。
例如我们点击网关微服务查看网关的具体链路,如下所示。
点开后如下所示。
可以看到,通过ZipKin能够查看服务的调用链路,并且能够查看具体微服务的调用情况。我们可以基于ZipKin来分析系统的调用链路情况,找出系统的瓶颈点,进而进行针对性的优化。
另外,ZipKin中也支持下载系统调用链路的Json数据,如下所示。
点击JSON按钮后,如下所示。
其中JSON内容如下。
[
[
{
"traceId": "481c7e6743594ca1",
"parentId": "4cc14150d04130d2",
"id": "df564bb384401b71",
"kind": "SERVER",
"name": "get /get/{uid}",
"timestamp": 1715650523590139,
"duration": 32612,
"localEndpoint": {
"serviceName": "server-user",
"ipv4": "192.168.3.45"
},
"remoteEndpoint": {
"ipv4": "192.168.3.45",
"port": 59113
},
"tags": {
"http.method": "GET",
"http.path": "/user/get/1001",
"mvc.controller.class": "UserController",
"mvc.controller.method": "getUser"
},
"shared": true
},
{
"traceId": "481c7e6743594ca1",
"parentId": "4cc14150d04130d2",
"id": "df564bb384401b71",
"kind": "CLIENT",
"name": "get",
"timestamp": 1715650523587404,
"duration": 36013,
"localEndpoint": {
"serviceName": "server-order",
"ipv4": "192.168.3.45"
},
"tags": {
"http.method": "GET",
"http.path": "/user/get/1001"
}
},
{
"traceId": "481c7e6743594ca1",
"parentId": "4cc14150d04130d2",
"id": "f4c6b511afd3c439",
"kind": "CLIENT",
"name": "get",
"timestamp": 1715650523626147,
"duration": 33558,
"localEndpoint": {
"serviceName": "server-order",
"ipv4": "192.168.3.45"
},
"tags": {
"http.method": "GET",
"http.path": "/product/get/1001"
}
},
{
"traceId": "481c7e6743594ca1",
"parentId": "4cc14150d04130d2",
"id": "1d3b39f18bb86f35",
"kind": "CLIENT",
"name": "get",
"timestamp": 1715650523705207,
"duration": 23456,
"localEndpoint": {
"serviceName": "server-order",
"ipv4": "192.168.3.45"
},
"tags": {
"http.method": "GET",
"http.path": "/product/update_count/1001/1"
}
},
{
"traceId": "481c7e6743594ca1",
"parentId": "481c7e6743594ca1",
"id": "4cc14150d04130d2",
"kind": "SERVER",
"name": "get /submit_order",
"timestamp": 1715650523556135,
"duration": 194880,
"localEndpoint": {
"serviceName": "server-order",
"ipv4": "192.168.3.45"
},
"remoteEndpoint": {
"ipv6": "::1"
},
"tags": {
"http.method": "GET",
"http.path": "/order/submit_order",
"mvc.controller.class": "OrderController",
"mvc.controller.method": "submitOrder"
},
"shared": true
},
{
"traceId": "481c7e6743594ca1",
"parentId": "4cc14150d04130d2",
"id": "f4c6b511afd3c439",
"kind": "SERVER",
"name": "get /get/{pid}",
"timestamp": 1715650523629157,
"duration": 30653,
"localEndpoint": {
"serviceName": "server-product",
"ipv4": "192.168.3.45"
},
"remoteEndpoint": {
"ipv4": "192.168.3.45",
"port": 59114
},
"tags": {
"http.method": "GET",
"http.path": "/product/get/1001",
"mvc.controller.class": "ProductController",
"mvc.controller.method": "getProduct"
},
"shared": true
},
{
"traceId": "481c7e6743594ca1",
"parentId": "4cc14150d04130d2",
"id": "1d3b39f18bb86f35",
"kind": "SERVER",
"name": "get /update_count/{pid}/{count}",
"timestamp": 1715650523706055,
"duration": 22236,
"localEndpoint": {
"serviceName": "server-product",
"ipv4": "192.168.3.45"
},
"remoteEndpoint": {
"ipv4": "192.168.3.45",
"port": 59114
},
"tags": {
"http.method": "GET",
"http.path": "/product/update_count/1001/1",
"mvc.controller.class": "ProductController",
"mvc.controller.method": "updateCount"
},
"shared": true
},
{
"traceId": "481c7e6743594ca1",
"parentId": "481c7e6743594ca1",
"id": "4cc14150d04130d2",
"kind": "CLIENT",
"name": "get",
"timestamp": 1715650523552529,
"duration": 198583,
"localEndpoint": {
"serviceName": "server-gateway",
"ipv4": "192.168.3.45"
},
"remoteEndpoint": {
"ipv4": "192.168.3.45",
"port": 8080
},
"tags": {
"http.method": "GET",
"http.path": "/order/submit_order"
}
},
{
"traceId": "481c7e6743594ca1",
"parentId": "481c7e6743594ca1",
"id": "ca4190e9e315cd9a",
"kind": "CLIENT",
"name": "get",
"timestamp": 1715650523545190,
"duration": 206252,
"localEndpoint": {
"serviceName": "server-gateway",
"ipv4": "192.168.3.45"
},
"tags": {
"http.method": "GET",
"http.path": "/order/submit_order"
}
},
{
"traceId": "481c7e6743594ca1",
"id": "481c7e6743594ca1",
"kind": "SERVER",
"name": "get",
"timestamp": 1715650523540109,
"duration": 213080,
"localEndpoint": {
"serviceName": "server-gateway",
"ipv4": "192.168.3.45"
},
"remoteEndpoint": {
"ipv6": "::1",
"port": 59110
},
"tags": {
"http.method": "GET",
"http.path": "/server-order/order/submit_order"
}
}
]
]
3.2.3 ZipKin数据持久化
3.2.3.1 持久化至Mysql
- 下载Mysql数据表结构https://github.com/openzipkin/zipkin/blob/master/zipkin-storage/mysql-v1/src/main/resources/mysql.sql
- 在数据库中运行SQL脚本,创建表,如下所示。
- 启动ZipKin时指定MySQL数据源,如下所示。
# 将数据源信息替换为自己的
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 --MYSQL_USER=root --MYSQL_PASS=root
- 启动ZipKin后,在浏览器中访问链接http://localhost:10001/server-order/order/submit_order?userId=1001&productId=1001&count=1,如下所示。
- 查看zipkin数据库中的数据,发现zipkin_annotations数据表与zipkin_spans数据表已经存在系统的调用链路数据。
- zipkin_annotations数据表部分数据如下所示。
- zipkin_spans数据表部分数据如下所示。
可以看到,ZipKin已经将数据持久化到MySQL中,重启ZipKin后就会从MySQL中读取数据,数据也不会丢失了。
3.2.3.2 持久化到ElasticSearch
此处不做详细展示,安装好Es后,更新启动ZipKin命令即可,如下所示。
java -jar zipkin-server-2.12.9-exec.jar --STORAGE_TYPE=elasticsearch --ESHOST=localhost:9200