文章目录
- 一、背景
- 链路追踪介绍
- 为什么需要链路追踪?
- 那该如何解决呢?
- 二、常见的链路追踪技术有下面这些:
- 三、Sleuth
- 3.1、Sleuth(读作/sluːθ/)介绍
- 3.2、相关术语
- 3.3、Sleuth入门
- 四、多线程传递traceId
- 1.问题
- 2.解决方案
- 3. 业务组件组件
- 五、RabbitMQ中传递traceId
- 六、Zipkin
- 6.1、ZipKin介绍
- 6.2、ZipKin服务端安装
- 1、下载ZipKin的jar包
- 2、进入cmd黑窗口—通过命令行,输入下面的命令启动ZipKin Server
- 3、通过浏览器访问 http://localhost:9411访问。
- 6.3、Zipkin客户端集成
- 1、在每个微服务上添加依赖
- 2、添加配置
- 3、访问微服务
- 4、访问zipkin的UI界面,观察效果
- 5、点击其中一条记录,可观察一次访问的详细线路
- 6.4、ZipKin数据持久化
- 6.4.1、使用mysql实现数据持久化
一、背景
链路追踪介绍
在大型系统的微服务化构建中,一个系统被拆分成了许多模块。这些模块负责不同的功能,组合成系统,最终可以提供丰富的功能。在这种架构中,一次请求往往需要涉及到多个服务。互联网应用构建在不同的软件模块集上,这些软件模块,有可能是由不同的团队开发、可能使用不同的编程语言来实现、有可能布在了几千台服务器,横跨多个不同的数据中心。
为什么需要链路追踪?
微服务架构是通过业务来划分服务的,使用 REST 调用。对外暴露的一个接口,可能需要很多个服务协同才能完成这个接口功能,如果链路上任何一个服务出现问题或者网络超时,都会形成导致接口调用失败。随着业务的不断扩张,服务之间互相调用会越来越复杂。
那该如何解决呢?
分布式链路追踪(Distributed Tracing),就是将一次分布式请求还原成调用链路,进行日志记录,性能监控并将一次分布式请求的调用情况集中展示。比如各个服务节点上的耗时、请求具体到达哪台机器上、每个服务节点的请求状态等等。
二、常见的链路追踪技术有下面这些:
-
cat 由大众点评开源,基于Java开发的实时应用监控平台,包括实时应用监控,业务监控 。 集成 方案是通过代码埋点的方式来实现监控,比如: 拦截器,过滤器等。 对代码的侵入性很大,集成成本较高。风险较大。
-
zipkin 由Twitter公司开源,开放源代码分布式的跟踪系统,用于收集服务的定时数据,以解决微 服务架构中的延迟问题,包括:数据的收集、存储、查找和展现《图形化》。该产品结合spring-cloud-sleuth 使用较为简单, 集成很方便, 但是功能较简单。
-
pinpoint Pinpoint是韩国人开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点 是支持多种插件,UI功能强大,接入端无代码侵入。
-
SkyWalking是本土开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点是支持多种插件,UI功能较强,接入端无代码侵入。目前已加入Apache孵化器。
Sleuth (日志记录每一条链路上的所有节点,以及这些节点所在的机器,和耗时。) -
log4j SpringCloud 提供的分布式系统中链路追踪解决方案。
注意:SpringCloud alibaba技术栈中并没有提供自己的链路追踪技术的,我们可以采用Sleuth + Zipkin来做链路追踪解决方案。
三、Sleuth
3.1、Sleuth(读作/sluːθ/)介绍
SpringCloud Sleuth主要功能就是在分布式系统中提供追踪解决方案。它大量借用了Google Dapper的设计, 先来了解一下Sleuth中的术语和相关概念。
3.2、相关术语
- 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 = 请求的总时间
3.3、Sleuth入门
接下来通过我之前的项目案例整合Sleuth,完成入门案例的编写。
-
在pom.xml文件添加Sleuth依赖
<!--sleuth依赖--> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency>
启动微服务,调用之后,我们可以在控制台观察到sleuth的日志输出
其中shop-getway中的
f2d0f9e1ad17ecda
是TraceId,shop-product中的ad5e994c06101a23
是SpanId,依次调用有一个全局的TraceId,将调用链路串起来。仔细分析每个微服务的日志,不难看出请求的具体过程。查看日志文件并不是一个很好的方法,当微服务越来越多日志文件也会越来越多,通过Zipkin可以将日志聚合,并进行可视化展示和全文检索。
-
logback.xml配置
业务系统logback.xml表达式修改,traceId记录在:X-B3-TraceId,由于spanId很少关注,因此未添加。<!-- 文件输出格式 --> <property name="PATTERN" value="%-5level [%d{HH:mm:ss.SSS}] [traceId:%yellow(%X{X-B3-TraceId})] [%thread] %logger{36} - %msg%n"/>
-
BaseResult编写
import lombok.Data; @Data public class BaseResult { /** * httpCode */ private Integer code; /** * 业务code */ private String errorCode; /** * 业务信息 */ private String message; /** * 链路id */ private String traceId; public BaseResult() { } public BaseResult(Integer code, String message) { this.code = code; this.message = message; } public BaseResult(Integer code, String errorCode, String message) { this.code = code; this.errorCode = errorCode; this.message = message; } protected static final Integer CODE_SUCCESS = 200; protected static final Integer CODE_SYSTEM_ERROR = 500; protected static final Integer CODE_CLIENT_ERROR = 400; protected static final String MESSAGE_SUCCESS = "请求成功"; }
-
返回体属性赋值
/** * 链路追踪 */ @ControllerAdvice public class AddTraceIdResponseBodyAdvice implements ResponseBodyAdvice<BaseResult> { @Autowired private Tracer tracer; @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return BaseResult.class.isAssignableFrom(returnType.getParameterType()); } @Override public BaseResult beforeBodyWrite(BaseResult body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { body.setTraceId(tracer.currentSpan().context().traceIdString()); return body; } }
5.SpringContextUtil
/**
* spring上下文工具类
*
*/
@Component
public class SpringContextUtil implements ApplicationContextAware {
/**
* spring应用上下文
*/
private static ApplicationContext applicationContext;
/**
* 实现ApplicationContextAware接口的回调方法。设置上下文环境
* @param applicationContext
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
SpringContextUtil.applicationContext = applicationContext;
}
/**
* @return ApplicationContext
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 获取对象
* @param name
* @return Object
* @throws BeansException
*/
public static Object getBean(String name) throws BeansException {
return applicationContext.getBean(name);
}
/**
* 获取对象
* @param clazz
* @return Object
* @throws BeansException
*/
public static <T> T getBean(Class<T> clazz) throws BeansException {
return applicationContext.getBean(clazz);
}
}
- TraceUtil
public class TraceUtil {
public static String getTraceId(){
Tracer tracer=(Tracer)SpringContextUtil.getBean("tracer");
if(tracer!=null){
Span span=tracer.currentSpan();
TraceContext traceContext=span.context();
return traceContext.traceIdString();
}
return null;
}
}
- controller层使用
前端示例:
日志打印示例:
配置完成,只要控制台打印的日志都会带上此次线程的日志【内部传递通过ThreadLocal】,包括feign调用也能查询到对应的日志【feign之间的调用通过header参数传递】
四、多线程传递traceId
1.问题
单线程内traceId可以进行传递,多线程传递参数问题.
目前Zipkin类CurrentTraceContext给出对线程及线程池的的处理方法就是实现了Runnable重新实现了run方法,这样就解决了线程池的问题,当然不只提供了创建线程的方法,还包括线程池和Callable
【个人测试过,此种方法线程池无法获取到父线程traceId】
2.解决方案
线程池解决经过多次测试,使用LazyTraceThreadPoolTaskExecutor
即可实现traceId的。下面给出在base包中应对traceId包装使用的线程池
接口
/**
* 静态阻塞线程池
*/
public interface BlockThreadPoolService {
/**
* 向线程池中添加任务
* @param task
* @return 任务(必须实现Runnable接口)
*/
Future<?> addTask(Runnable task);
/**
* 异步执行一批任务,直到任务执行完成
* @param task
*/
void runTasksUntilEnd(List<Runnable> task);
/**
* 向线程池中添加循环运行的任务
* @param task 任务(必须实现Runnable接口)
* @param interval 时间间隔,单位毫秒
*/
void loopTask(Runnable task, long interval);
/**
* 向线程池中添加循环运行的任务
* @param task 任务(必须实现Runnable接口)
* @param interval 时间间隔,单位毫秒
* @param delay 延迟执行的时间,单位毫秒,表示任务在delay ms后开始被定时调度
*/
void loopTask(Runnable task, long interval, long delay);
/**
* 停止线程池
*/
public void stop();
}
实现类
/**
* 线程池基类
*/
@Slf4j
public class BlockThreadPoolServiceBase implements BlockThreadPoolService {
/** 主线程数 */
@Setter
private int corePoolSize = 20;
/** 最大线程数 */
@Setter
private int maximumPoolSize = 150;
/** 线程池维护线程所允许的空闲时间 */
@Setter
private int keepAliveTime = 60;
/** 线程池所使用的队列的大小 */
@Setter
private int queueSize = 100;
/** 是否已被初始化 */
@Setter
private boolean inited = false;
/** 单例延时线程池 */
private ScheduledExecutorService scheduledExecutorService;
/** trace跟踪线程池 */
private LazyTraceThreadPoolTaskExecutor lazyTraceThreadPoolTaskExecutor;
/**
* 初始化单例线程池
*/
public void init() {
if(inited) {
return;
}
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(corePoolSize);
threadPoolTaskExecutor.setMaxPoolSize(maximumPoolSize);
threadPoolTaskExecutor.setQueueCapacity(queueSize);
threadPoolTaskExecutor.setKeepAliveSeconds(keepAliveTime);
threadPoolTaskExecutor.setThreadNamePrefix("nssa-Thread-");
threadPoolTaskExecutor.setAllowCoreThreadTimeOut(true);
threadPoolTaskExecutor.initialize();
this.lazyTraceThreadPoolTaskExecutor = new LazyTraceThreadPoolTaskExecutor(SpringContextUtil.getApplicationContext(),threadPoolTaskExecutor);
inited = true;
}
/**
* 添加任务
* @param task
* @return
*/
@Override
public Future<?> addTask(Runnable task) {
if(!inited) {
init();
}
return this.lazyTraceThreadPoolTaskExecutor.submit(TtlRunnable.get(task));
}
@Override
public void stop() {
lazyTraceThreadPoolTaskExecutor.shutdown();
if(scheduledExecutorService != null) {
scheduledExecutorService.shutdownNow();
}
}
@Override
public synchronized void loopTask(Runnable task, long interval) {
loopTask(task, interval, 0);
}
@Override
public void loopTask(Runnable task, long interval, long delay) {
if(scheduledExecutorService == null) {
ThreadFactory threadFactory = new ScheduledThreadFactory("schedule-pool-%d-%s");
scheduledExecutorService = Executors.newScheduledThreadPool(1, threadFactory);
}
int minInterval=100;
if(interval < minInterval) {
throw new IllegalArgumentException("不允许调度100ms以内的循环任务");
}
scheduledExecutorService.scheduleAtFixedRate(TtlRunnable.get(task), delay, interval, TimeUnit.MILLISECONDS);
}
@Override
public void runTasksUntilEnd(List<Runnable> tasks) {
List<Future<?>> futures = new ArrayList<Future<?>>();
for(Runnable task : tasks) {
futures.add(addTask(task));
}
for(Future<?> f : futures) {
try {
f.get();
} catch (Exception e) {
log.warn("", e);
}
}
}
/**
* 获取单例线程池实例
* @return
*/
protected LazyTraceThreadPoolTaskExecutor getExecutorService() {
return lazyTraceThreadPoolTaskExecutor;
}
/**
* 动态生成一个定时任务线程池
*/
static class ScheduledThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
ScheduledThreadFactory(String namePrefix) {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
this.namePrefix = String.format(namePrefix, poolNumber.getAndIncrement(), "%d");
}
String getThreadName() {
return String.format(namePrefix,
threadNumber.getAndIncrement());
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, getThreadName(), 0);
if (!t.isDaemon()){
t.setDaemon(true);
}
if (t.getPriority() != Thread.NORM_PRIORITY){
t.setPriority(Thread.NORM_PRIORITY);
}
return t;
}
}
}
原生jdk提供的InheritableThreadLocal,可以解决父子线程的变量传递,但是父线程传递到线程池时会存在数据丢失的情况。为了解决此问题,在此线程池中任务提交到线程池时使用了阿里的开源组件ttl,用于解决父子线程变量传递
感兴趣的可以看一下对应的源码,设计很巧妙,附上链接:github.com/alibaba/tra…
3. 业务组件组件
- 多线程使用上,因为线程的新建与销毁上下文需要一定的时间,因为存在线程池。建议线程执行时间短或者新建线程比较频繁的任务都放到线程池中解决,比如消息发送。
- 强烈建议应用中尽量不要存在太多的线程池,建议最多2个,线程池可以根据业务情况进行划分或者不划分。大量的定时等情况建议走一下MQ,让MQ削峰一下,降低应用运行的CPU负载。
- 建议与当前主线程无关,但有可能阻塞主线程的代码段交给线程池处理,比如:MQ消息发送。
业务应用base包线程池实例,新建一个单例线程池
/**
* 消息发送线程池
*/
public class MessageThreadPool {
private volatile static BlockThreadPoolService blockThreadPoolService;
public static BlockThreadPoolService getThreadPool() {
if(blockThreadPoolService == null){
synchronized (MessageThreadPool.class){
if(blockThreadPoolService == null) {
blockThreadPoolService = new BlockThreadPoolServiceBase();
}
}
}
return blockThreadPoolService ;
}
}
单任务调用
MessageThreadPool.getThreadPool().addTask(
()-> System.out.println("测试")
);
多任务调用
Runnable r1=()->log.info("hello1:");
Runnable r2=()->log.info("hello1:");
Runnable r3=()->log.info("hello1:");
Runnable r4=()->log.info("hello1:");
MessageThreadPool.getThreadPool().runTasksUntilEnd(
Lists.newArrayList(r1,r2,r3,r4)
);
五、RabbitMQ中传递traceId
发送消息:发送消息时头部加入当前线程的traceId即可【可使用TraceUtil.getTraceId()获取】
接收消息:消息接收之后参数使用@header去除对应traceId,然后调用slf4j的工具类,MDC.put(“X-B3-TraceId”,traceId),即可跟踪到对应链路信息。
上面这个只是一种解决思路,但是接收消息的步骤存在问题,如果有线程池的话就无法传递进去了,有待探索。
六、Zipkin
6.1、ZipKin介绍
Zipkin 是 Twitter 的一个开源项目,它基于Google Dapper实现,它致力于收集服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集、存储展现、查找和我们可以使用它来收集各个服务器上请求链路的跟踪数据,并通过它提供的REST API接口来辅助我们查询跟踪数据以实现对分布式系统的监控程序,从而及时地发现系统中出现的延迟升高问题并找出系统性能瓶颈的根源,除了面向开发的 API 接口之外,它也提供了方便的UI组件来帮助我们直观的搜索跟踪信息和分析请求链路明细,比如:可以查询某段时间内各用户请求的处理时间等。
Zipkin 提供了可插拔数据存储方式:In-Memory、MySql、Cassandra 以及 Elasticsearch。
上图展示了 Zipkin 的基础架构,它主要由 4 个核心组件构成:
- Collector:收集器组件,它主要用于处理从外部系统发送过来的跟踪信息,将这些信息转换为Zipkin 内部处理的 Span 格式,以支持后续的存储、分析、展示等功能。
- Storage:存储组件,它主要对处理收集器接收到的跟踪信息,默认会将这些信息存储在内存中,我们也可以修改此存储策略,通过使用其他存储组件将跟踪信息存储到数据库中。
- RESTful API:API 组件,它主要用来提供外部访问接口。比如给客户端展示跟踪信息,或是外接系统访问以实现监控等。
- Web UI:UI 组件,基于 API 组件实现的上层应用。通过 UI 组件用户可以方便而有直观地查询和分析跟踪信息。
Zipkin分为两端,一个是 Zipkin 服务端,一个是 Zipkin 客户端,客户端也就是微服务的应用。客户端会配置服务端的 URL 地址,一旦发生服务间的调用的时候,会被配置在微服务里面的 Sleuth 的监听器监听,并生成相应的 Trace 和 Span 信息发送给服务端。
6.2、ZipKin服务端安装
1、下载ZipKin的jar包
官网下载: https://zipkin.io/pages/quickstart.html
2、进入cmd黑窗口—通过命令行,输入下面的命令启动ZipKin Server
java -jar zipkin-server-2.12.9-exec.jar
3、通过浏览器访问 http://localhost:9411访问。
6.3、Zipkin客户端集成
ZipKin客户端和Sleuth的集成非常简单,只需要在微服务中添加其依赖和配置即可。
1、在每个微服务上添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
2、添加配置
#配置zipkin
zipkin:
base-url: http://192.168.31.60:9411
discovery-client-enabled: false # 不要让nacos把zipkin注册进去(可以不写)
sleuth:
sampler:
probability: 1.0
3、访问微服务
这是我自己的访问路径,你们在访问的时候换成自己的访问路径
http://192.168.31.60:9000/order/payOrder?pid=1&num=2
4、访问zipkin的UI界面,观察效果
5、点击其中一条记录,可观察一次访问的详细线路
6.4、ZipKin数据持久化
Zipkin Server默认会将追踪数据信息保存到内存,但这种方式不适合生产环境。Zipkin支持将追踪数据持久化到mysql数据库或elasticsearch中。
6.4.1、使用mysql实现数据持久化
1、创建mysql数据环境(创建数据库及表)
CREATE DATABASE `zipkin` /*!40100 DEFAULT CHARACTER SET utf8 */;
use 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;
2、关闭zipkin服务,重新打开cmd黑窗口输入以下数据来启动在启动ZipKin Server的时候,指定数据保存的mysql的信息。
java -jar zipkin-server-2.23.2-exec.jar --STORAGE_TYPE=mysql --MYSQL_HOST=192.168.31.60 --MYSQL_TCP_PORT=3306 --MYSQL_DB=zipkin --MYSQL_USER=root --MYSQL_PASS=123@qwe
根据自己的信息更改以下对应数据信息
- MYSQL_HOST:本机IP
- MYSQL_TCP_PORT:端口号
- MYSQL_DB:数据库名
- MYSQL_USER:用户名
- MYSQL_PASS:密码
启动之后就实现了数据持久化(将链路追踪信息存放到mysql数据库中),重新启动后再次打开访问zipkin的UI界面,链路信息将不会消失。