【Spring Cloud】Sleuth+Zipkin全链路日志追踪接入实战

news2024/10/6 12:33:30

文章目录

  • 一、背景
    • 链路追踪介绍
    • 为什么需要链路追踪?
    • 那该如何解决呢?
  • 二、常见的链路追踪技术有下面这些:
  • 三、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,完成入门案例的编写。

  1. 在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可以将日志聚合,并进行可视化展示和全文检索。

  2. 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"/>
    
  3. 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 = "请求成功";
    
    }
    
  4. 返回体属性赋值

    /**
     * 链路追踪
     */
    @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);
    }
}
  1. 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;
    }
}
  1. 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界面,链路信息将不会消失。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/457646.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

cookie和session—javaEE

1.cookie 1.1定义 单纯的说cookie指的是cookie技术&#xff0c;是客户端保存数据的一种技术 1.2保存的方式 &#xff08;1&#xff09;客户端写js代码 &#xff08;2&#xff09;服务端返回响应头set-cookie字段的值让客户端保存在本地硬盘或浏览器的相关路径中 1.3作用 …

Oracle的学习心得和知识总结(二十三)|Oracle数据库Real Application Testing之Database Replay相关视图

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《Oracle Database SQL Language Reference》 2、参考书籍&#xff1a;《PostgreSQL中文手册》 3、EDB Postgres Advanced Server User Gui…

[C++]模板初阶与STL简介

目录 模板初阶与STL简介&#xff1a;&#xff1a; 1.泛型编程 2.函数模板 3.类模板 4.什么是STL 5.STL的版本 6.STL的六大组件 7.STL的缺陷 模板初阶与STL简介&#xff1a;&#xff1a; 1.泛型编程 如何实现一个通用的交换函数呢? void Swap(int& left, int& righ…

《Netty》从零开始学netty源码(四十二)之PoolChunk.runsAvailMap

runsAvailMap PoolChunk中的runsAvailMap属性用于存放可用的run的信息&#xff0c;PoolChunk中每一次分配内存都会更新runsAvailMap中可用的run的起始信息及末尾信息&#xff0c;先看下它的数据结构&#xff1a; 我们看下它的构造函数是如何赋值的&#xff1a; PoolChunk的默认…

为什么MySQL索引更适合B+树而不是二叉树、B树

概述&#xff1a; 在当今社会&#xff0c;程序员内卷非常的严重&#xff0c;如果没有过硬的技术&#xff0c;很难在众多的程序员中脱颖而出&#xff0c;例如&#xff0c;以前问数据库方面的知识&#xff0c;只会问些增删改查语句表面的东西&#xff0c;而如今却要问数据库底层…

【翻译一下官方文档】之uniapp的网络请求

uni.request(OBJECT) 发起网络请求。 参数名类型必填默认值说明平台差异说明urlString是开发者服务器接口地址dataObject/String/ArrayBuffer否请求的参数App 3.3.7 以下不支持 ArrayBuffer 类型headerObject否设置请求的 header&#xff0c;header 中不能设置 RefererApp、H5…

关于链表的题目—leetcode

第一题&#xff1a;删除链表中的指定节点 问题描述&#xff1a; 给定单向链表的头指针和一个要删除的节点的值&#xff0c;定义一个函数删除该节点。 返回删除后的链表的头节点。 示例 1: 输入: head [4,5,1,9], val 5 输出: [4,1,9] 解释: 给定你链表中值为 5 的第二个节点…

【redis】缓存预热雪崩穿透击穿

【redis】缓存预热雪崩穿透击穿&#xff08;上&#xff09; 文章目录 【redis】缓存预热雪崩穿透击穿&#xff08;上&#xff09;前言一、面试题二、缓存预热三、缓存雪崩发生原因预防&#xff0b;解决高可用&#xff1a;多缓存结合&#xff1a; 人民币玩家 四、缓存穿透是什么…

谷歌云端硬盘Drive批量下载大文件或大文件夹的稳定方法

本文介绍在谷歌云端硬盘&#xff08;Drive&#xff09;中&#xff0c;快速、稳定下载大文件、文件夹的方法。 在使用谷歌Drive下载文件或文件夹时&#xff0c;我们往往会遇到下载不稳定或失败的情况&#xff1b;在下载较大的文件或文件夹时&#xff0c;这一问题出现的频率更多。…

NLP语义识别在人工智能领域中的应用与前景

自然语言处理&#xff08;NLP&#xff09;是人工智能领域中的一个重要分支&#xff0c;它致力于让计算机能够理解并处理人类自然语言。语义识别是NLP中的一个重要技术&#xff0c;它可以使计算机更好地理解人类语言的含义和意图。在本文中&#xff0c;我们将探讨NLP语义识别在人…

4月24号软件更新资讯合集.....

GoFrame v2.4 版本发布&#xff0c;企业级 Golang 开发框架 大家好啊&#xff0c;GoFrame 框架今天发布了 v2.4.0 正式版本啦&#xff01;&#x1f44f;&#x1f44f;&#x1f44f;&#x1f44f; 该版本最大的亮点在于提供了微服务开发的功能特性、开发工具以及工程脚手架&am…

第三章作业:关系数据库

第三章作业&#xff1a;关系数据库 目录 第三章作业&#xff1a;关系数据库选择题简答题1、关系代数&#xff1a;产生学生成绩表&#xff0c;包括学号、姓名、课程名、学分和成绩。题目代码 2、关系代数&#xff1a;检索选择了课程号为“C2"的学生学号和姓名。题目代码 3、…

月获2万份简历,硕士占比超70%!中欧基金如何破圈打造雇主品牌?

成立于2006年的中欧基金&#xff0c;作为国内首批实现员工持股的基金公司&#xff0c;坚持以人为本&#xff0c;相信优秀的业绩要靠优秀的人才来创造。 因此&#xff0c;中欧基金在完善公司治理机制基础上&#xff0c;实现不仅有敢打硬仗能打胜仗的将才&#xff0c;还有更多不…

Pytorch损失函数

基本用法 criterion LossCriterion() #构造函数有自己的参数loss criterion(x, y) #调用标准时也有参数 1 L1范数损失 L1Loss 计算 output 和 target 之差的绝对值。 torch.nn.L1Loss(reductionmean)参数&#xff1a; reduction-三个值 none: 不使用约简&#xff1b; me…

S32K系列MCU学习介绍

前言 最近因为工作需要&#xff0c;在学习恩智浦的S32K312&#xff0c;开发一款汽车PDU。 一、S32K3系列 1.特点 S32K系列是恩智浦公司于2017年推出的面向汽车电子的微控制器。S32K3 系列包括基于 Arm Cortex-M7 的 MCU&#xff0c;采用单步、双步和锁步内核配置&#xff0…

滴水逆向3期笔记与作业——01汇编

防止OneNote丢失。 海哥牛逼。 01汇编笔记 01进制进制定义10-2进制转换八进制 02数据宽度/逻辑运算数据宽度与存储逻辑运算计算机做加法的本质作业 03通用寄存器_内存读写通用寄存器表通用寄存器图内存读写计算机操作系统位数意义 04内存地址_堆栈寻址公式PUSH指令POP指令作业 …

《稻》念袁老,孙溟㠭先生为纪念袁隆平老先生治印一方

孙溟㠭篆刻作品《稻》 孙溟㠭篆刻作品《稻》 稻穗熟了&#xff0c;袁老走了。溟㠭先生为纪念袁隆平老先生而治印一枚。 拓印左侧禾苗繁茂&#xff0c;稻田里蛙声一片。拓印右侧为袁老的样子&#xff0c;人瘦心厚&#xff0c;顶着烈日&#xff0c;照料自己试验的稻田。袁老一…

3.黑马springboot开发篇自己修改笔记

SpringBoot开发实用篇 ​ KF-1.热部署 ​ 什么是热部署&#xff1f;简单说就是你程序改了&#xff0c;现在要重新启动服务器&#xff0c;嫌麻烦&#xff1f;不用重启&#xff0c;服务器会自己悄悄的把更新后的程序给重新加载一遍&#xff0c;这就是热部署。 ​ 热部署的功能…

[渗透教程]-017-入侵检测与社交网络安全

文章目录 1.入侵检测1.1 入侵检测基本概念1.2 入侵艰难侧系统评估指标1.3 入侵检测基本技术1.4 通用入侵检测框架2.社交网络安全1.入侵检测 1.1 入侵检测基本概念 入侵检测(Intrusion Detection),指对系统的运行状态进行监视,发现各种攻击企图、攻击行为或者攻击结果,以保证…

大神们分享STM32的学习方法

单片机用处这么广&#xff0c;尤其是STM32生态这么火&#xff01;如何快速上手学习呢&#xff1f; 第一&#xff1a;你要考虑的是&#xff0c;要用STM32实现什么 为什么使用STM32而不是8051? 是因为51的频率太低&#xff0c;无法满足计算需求?是51的管脚太少&#xff0c;无法…