springcloud之链路追踪

写在前面

源码 。
本文一起来看下链路追踪的功能,链路追踪是一种找出病因的手段,可以类比医院的检查仪器,服务医生治病救人,而链路追踪技术是辅助开发人员查找线上问题的。

1:为什么微服务需要链路追踪

孔子同志月过,有则改之,无则加勉,其中的后半句无则加勉,springcloud已经通过提供sentinel组件 ,但如何才能做到有则改之呢,想要改之,就必须知道要改啥,即要知道程序的bug是哪里造成的,而要定位问题的具体所在,在微服务场景下,有时候并不是一件那么容易的事情,因为可能涉及到非常多组件的调用,甚至还有消息队列,数据库之类的中间件,如下图:
在这里插入图片描述

组件多,并且一般多是多节点集群方式部署,用户的请求量还大,想要排查一个请求的错误,无异于大海捞针,因此啊,我们就需要一种技术来帮助我们将请求给串起来,这样在出现问题之后就可以拔出萝卜带出泥的定位到没有节点的请求日志,从而轻而易举的定位到问题所在,做到有则时的改之。而本文要分析的正是这样一个组件:sleuth。

那么sleuth是如何将每个请求串起来的呢?对于整个请求链路,sleuth定义了trace ID,对于一个请求定义了spanId,parentSpanId,其中spanId代表本次处理节点,parentSpanId代表上次处理节点,如下图:
在这里插入图片描述

有了调用链的日志之后,对于我们排查问题就会简单很多了,但是还是需要一个一个日志文件的来查找,其实还不是很方便,效率也比较低,此时如果能有一种图形化的展示方式,将这个跨进程的调用栈以UI的方式展示出来就太完美了,此时我们就需要zipkin组件的帮助了,所以应用程序sleuth需要将日志发送到zipkin供zipkin分析展示,但为了应用的解耦,我们一般会引入消息中间件,此时整个架构如下图:
在这里插入图片描述

接下来,进入实战。

2:实战

首先我们在模板,计算,用户三个模块添加依赖:

<!-- Sleuth依赖项 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

接着添加配置:

spring: 
  sleuth:
    sampler:
      # 采样率的概率,100%采样
      probability: 1.0
      # 每秒采样数字最高为1000
      rate: 1000

logging:
  level:
    ...
    org.springframework.cloud.sleuth: debug
  • probability
    0到1的数字,设置采样的百分比。1为全部采样,0为全都不采样。
  • rate
    设置每秒最大采样数。
    请求,如果看到类似如下的日志就说明sleuth已经成功开始采集了:
DEBUG [coupon-customer-serv,69e433d6432522e4,936d8af942b703d2] 81584 
--- [io-20002-exec-1] c.g.c.customer.feign.TemplateService:xxxx

好,接着我们来搭建rabbitmq环境,具体不在这里赘述,可移步这里 查看。

接着,来搭建zipkin,首先从maven 的中央仓库 下载 zipkin-server-2.23.9-exec.jar,接着执行如下命令启动zipkin服务:

# 注意mq地址改成你自己的
java -jar zipkin-server-2.23.9-exec.jar --zipkin.collector.rabbitmq.addresses=localhost:5672

另外mq默认的信息如下:
在这里插入图片描述

如果需要指定的话,再启动命令中特殊指定即可,启动成功后会看到zipkin特有的logo:

$ java -jar zipkin-server-2.23.9-exec.jar --zipkin.collector.rabbitmq.addresses=192.168.10.79:5673

                  oo
                 oooo
                oooooo
               oooooooo
              oooooooooo
             oooooooooooo
           ooooooo  ooooooo
          oooooo     ooooooo
         oooooo       ooooooo
        oooooo   o  o   oooooo
       oooooo   oo  oo   oooooo
     ooooooo  oooo  oooo  ooooooo
    oooooo   ooooo  ooooo  ooooooo
   oooooo   oooooo  oooooo  ooooooo
  oooooooo      oo  oo      oooooooo
  ooooooooooooo oo  oo ooooooooooooo
      oooooooooooo  oooooooooooo
          oooooooo  oooooooo
              oooo  oooo

     ________ ____  _  _____ _   _
    |__  /_ _|  _ \| |/ /_ _| \ | |
      / / | || |_) | ' / | ||  \| |
     / /_ | ||  __/| . \ | || |\  |
    |____|___|_|   |_|\_\___|_| \_|

:: version 2.23.9 :: commit d6b1cc3 ::

2024-01-16 17:19:11.706  INFO [/] 20860 --- [oss-http-*:9411] c.l.a.s.Server                           : Serving HTTP at /0:0:0:0:0:0:0:0:9411 - http://127.0.0.1:9411/

接着可以访问http://127.0.0.1:9411/:
在这里插入图片描述
zipkin会默认监听rabbitmq的队列zipkin来消费消息,所以此时你登录rabbitmq后台的话可以看到这个队列:
在这里插入图片描述

这样zipkin就搭建完成了。接着我们来改造服务,将采样信息生产到rabbitmq的zipkin队列中,首先在template,calculate,cusotm模块引入zipkin适配依赖,以及stream依赖完成生产消息的依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<!-- 提前剧透Stream -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>

接着添加配置:

spring:
  zipkin:
    sender:
      type: rabbit
    rabbitmq:
      addresses: 192.168.10.79:5673
      queue: zipkin
  rabbitmq:
    addresses: 192.168.10.79:5673

我们来几个请求后,就可以通过zipkin来查看了,会以栈的形式展现整个调用过程:
在这里插入图片描述
在这里插入图片描述
点击show就可以看到调用的整个过程了:
在这里插入图片描述
如果是某个服务调用报错的话,会显示为红色,如下:
在这里插入图片描述
如果知道traceId可以通过右上角直接搜索,如下:
在这里插入图片描述
zipkin也会根据服务调用关系生成依赖图,如下:
在这里插入图片描述

2.1:原理

首先我们打开spring.factories文件:
在这里插入图片描述
先看自动配置类TraceEnvironmentPostProcessor,这是一个EnvironmentPostProcessor,在该类中首先修改日志打印的格式,启动时会进入到这里:
在这里插入图片描述

在这里插入图片描述

这样日志格式就修改了,后续使用slf4j打印日志的话,只要有相关的信息,就会打印出来了了。接下来的问题就是spanId,tranceId等信息时什么时候设置的呢?继续看TraceWebAutoConfiguration自动配置类,源码如下:

@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(Tracer.class)
@ConditionalOnSleuthWeb
@Import({ SkipPatternConfiguration.class, TraceWebFluxConfiguration.class, TraceWebServletConfiguration.class })
@EnableConfigurationProperties(SleuthWebProperties.class)
@AutoConfigureAfter(BraveAutoConfiguration.class)
public class TraceWebAutoConfiguration {

}

继续看Import的TraceWebServletConfiguration,在该类中会创建TranceFilter,源码如下:

class TraceWebServletConfiguration {

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnProperty(value = "spring.sleuth.web.servlet.enabled", matchIfMissing = true)
	static class ServletConfiguration {

        ...
		@Bean
		@ConditionalOnMissingBean
		TracingFilter tracingFilter(CurrentTraceContext currentTraceContext, HttpServerHandler httpServerHandler) {
			return TracingFilter.create(currentTraceContext, httpServerHandler);
		}
        ...

	}

}

在TracingFilter会生成(第一个span时)或者时从header中获取traceId,spanId,等信息,并通过slf4j的MDC设置,这样,在打印日志的时候就会打印出这些信息了,源码如下:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
    // 生成span,这里执行完traceId,spanId等就有值了
    Span span = handler.handleReceive(new HttpServletRequestWrapper(req));
    ...
    // 设置到slf4j的MDC,这样打印日志就能获取到trance_id,span_id等信息了
    CurrentTraceContext.Scope scope = currentTraceContext.newScope(span.context());
    try {
        chain.doFilter(req, res);
    }
    ...
}

那么故事的最后就是通过openfeign调用其他服务时是怎么实现拦截从而打印日志了,以RestTemplate方式调用为例,继续看spring.factories文件的TraceWebClientAutoConfiguration, 源码如下:

class TraceWebClientAutoConfiguration {

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(RestTemplate.class)
	static class RestTemplateConfig {

		...
		@Configuration(proxyBeanMethods = false)
		protected static class TraceInterceptorConfiguration {
            ...
			@Bean
			static TraceRestTemplateBeanPostProcessor traceRestTemplateBeanPostProcessor(
					ListableBeanFactory beanFactory) {
				return new TraceRestTemplateBeanPostProcessor(beanFactory);
			}
            ...
		}

	}
    ...

}

继续看TraceRestTemplateBeanPostProcessor这个后置bean处理器,最终执行如下方法,设置sleuth日志打印的拦截器:

// org.springframework.cloud.sleuth.instrument.web.client.TraceRestTemplateBeanPostProcessor#postProcessAfterInitialization
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    if (bean instanceof RestTemplate) {
        RestTemplate rt = (RestTemplate) bean;
        // 设置拦截器
        new RestTemplateInterceptorInjector(interceptor()).inject(rt);
    }
    return bean;
}

这样在发起真正的日志调用之前就能打印日志了,其他的方式,如grpc,feign,也都是通过类似的方式来实现的,但不同的通信方式拦截的方式不同罢了,具体用到哪种再去研究吧!

3:elk

通过sleuth我们已经可以定位到异常发生在哪个模块了,并且可能也已经知道初步的异常信息了,但是仅仅如此还是没有办法定位到具体的异常原因,还需要详细的上下文日志信息,自然我们可以通过tranceId,spanId这些信息到具体的日志文件中去查找,但一般我们的实例都是多节点部署的,少则三四个,多则几十个上百个,所以人肉挨个查找日志文件的效率无疑会非常低。如果是能将所有的日志进行汇总,并提供一种简便的查询方式,就再也完美不过了,而elk ,就能很好的满足我们的需求。

首先来现在docker 的elk镜像 ,这里使用sebp/elk,如下命令:

# 比较大,耐心等待
docker pull sebp/elk:7.16.1
# 我这里设置为5G内存,小了容易不够用导致无法正常启动
docker run -p 5601:5601 -p 9200:9200 -p 5044:5044 -it --name elk --memory 5G sebp/elk:7.16.1

项目的日志文件,有错误可以通过日志文件排查问题:

==> /var/log/logstash/logstash-plain.log <==

==> /var/log/kibana/kibana5.log <==

==> /var/log/elasticsearch/elasticsearch.log <==

启动后可访问es:
在这里插入图片描述
kibana:
在这里插入图片描述
接着进入容器修改文件/etc/logstash/conf.d/02-beats-input.conf

docker exec -it elk /bin/bash

input {
    tcp {
        port => 5044
        codec => json_lines
    }
}

output {
    elasticsearch {
        hosts => ["localhost:9200"]
        index => "geekbang"
    }
}

指定输入源和输出源,支持的详细输入源和输出源如下:
Logstash Input 插件列表 。

Logstash Output 插件列表 。

退出容器后,重启容器:

docker restart elk

接着我们来改造应用,支持将日志写到logstash中,首先在template,calcualte,custom三个模块中引入依赖,支持logback写数据到logstash:

<dependency>
    <groupId>net.logstash.logback</groupId>
    <artifactId>logstash-logback-encoder</artifactId>
    <version>7.0.1</version>
</dependency>

接着添加两个appender,分别输出日志到控制台和logstash:

<!-- 控制台 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>DEBUG</level>
        </filter>
        <!-- 日志输出编码 -->
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <!-- 输出的JSON格式的信息到Logstash -->
    <appender name="logstash"
              class="net.logstash.logback.appender.LogstashTcpSocketAppender">
        <!-- 这是Logstash的连接方式 -->
        <destination>192.168.10.79:5044</destination>
        <!--<encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder">
        </encoder>-->
        <!-- 日志输出的JSON格式 -->
        <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
            <providers>
                <timestamp>
                    <timeZone>UTC</timeZone>
                </timestamp>
                <pattern>
                    <pattern>
                        {
                        "severity": "%level",
                        "service": "${applicationName:-}",
                        "trace": "%X{traceId:-}",
                        "span": "%X{spanId:-}",
                        "pid": "${PID:-}",
                        "thread": "%thread",
                        "class": "%logger{40}",
                        "rest": "%message"
                        }
                    </pattern>
                </pattern>
            </providers>
        </encoder>
    </appender>

通过appender logstash我们指定了json格式的数据。

  • 日志完整配置请参考源码。

接着我们来启动项目,发几个请求,这些数据就会被写到es中了,接着我们访问http://192.168.10.83:5601/需要首先创建index,大概操作如下:
在这里插入图片描述
接着点击如下位置:
在这里插入图片描述
一切顺利的话会进入如下页面:
在这里插入图片描述
接着选择一个时间段,refresh,就可以看到我们的日志信息了:
在这里插入图片描述
每一项就是es的每个文档了,其中的rest就是具体的日志信息,是我们在logback日志中设置的,可对比下图:
在这里插入图片描述
这样比如我们定位到了如下的调用链追踪日志:
在这里插入图片描述
可以定位到问题发生在template模块,并且指定知道是发生了java.lang.ArithmeticException,但为什么会出现这种情况就需要继续排查上下文的详细日志,此时我们就可以通过traceId:3cf4d5302905cd73到kibana中查看详细日志了:
在这里插入图片描述

写在后面

参考文章列表

Slf4j之MDC机制 。
spring cloud之集成sentinel 。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

崩溃了!我说用attach进行问题定位,面试官问我原理

Arthas&#xff08;阿尔萨斯&#xff09;是一款开源的Java诊断和监控工具&#xff0c;可以在生产环境中进行实时的应用程序分析和故障排查。Arthas的实现原理主要基于Java Instrumentation API和Java Agent技术。 Java Agent 是 Java 编程语言提供的一种特殊机制&#xff0c;允…

【Qt】—— Qt的基本介绍

目录 &#xff08;一&#xff09;什么是Qt &#xff08;二&#xff09; Qt的发展史 &#xff08;三&#xff09;Qt⽀持的平台 &#xff08;四&#xff09; Qt版本 &#xff08;五&#xff09;Qt的优点 &#xff08;六&#xff09;Qt的应⽤场景 &#xff08;七&#xff09…

77.网游逆向分析与插件开发-背包的获取-物品类的C++还原

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;76.网游逆向分析与插件开发-背包的获取-背包地址的逆向分析-CSDN博客 码云地址&#xff08;ui显示角色数据 分支&#xff09;&#xff1a;https://gitee.com/dye_your_fingers/sro_-ex.git 码云版本…

【2020】百度校招Java研发工程师笔试卷(第二批)算法题

贴一下我去年9月份写的博客 三道编程题&#xff0c;一道数学题&#xff0c;两道图论&#xff0c;哎嘿嘿&#xff0c;我就是不会做&#xff0c;哎嘿嘿&#xff0c;哭了。。。 一.最小值 牛牛给度度熊出了一个数学题&#xff0c;牛牛给定数字n,m,k&#xff0c;希望度度熊能找到…

文件操作与IO(2)

Java中通过java.io.File类来对一个文件(包括目录)进行抽象的描述.注意,有File对象,并不代表真实存在该文件. File概述 我们先来看看File类中的常见属性,构造方法和方法. 属性 修饰符及类型属性说明static StringpathSeparator依赖系统的路径分隔符,String类型的表示static …

VM使用教程--SDK取图 视频笔记

本笔记均由海康机器人官网的V学院视频中记录所得&#xff0c;属于省流大师了[doge] 图像采集 图像采集包括1图像源&#xff0c;2多图采集&#xff0c;3输出图像&#xff0c;4缓存图像&#xff0c;5光源 1图像源 图像源包括本地图像&#xff0c;相机采图&#xff0c;SDK 本…

MySQL主从集群

MySQL主从集群 主从模式、集群模式&#xff0c;都是在一个项目中使用多个mysql节点进行存储和读取数据。 当单机模式部署&#xff0c;不满足安全性、高可用、高并发等需求的时候&#xff0c;就需要考虑主从模式或者集群模式部署。 什么是主从模式&#xff1f; 主从模式&…

结构体大揭秘:代码中的时尚之选(上)

目录 结构结构的声明结构成员的类型结构体变量的定义和初始化结构体成员的访问结构体传参 结构 结构是一些值的集合&#xff0c;这些值被称为成员变量。之前说过数组是相同类型元素的集合。结构的每个成员可以是不同类型的变量&#xff0c;当然也可以是相同类型的。 我们在生活…

【数据库原理】(38)数据仓库

数据仓库&#xff08;Data Warehouse, DW&#xff09;是为了满足企业决策分析需求而设计的数据环境&#xff0c;它与传统数据库有明显的不同。 一.数据库仓库概述 定义: 数据仓库是一个面向主题的、集成的、相对稳定的、反映历史变化的数据集合&#xff0c;用于支持企业管理和…

DAY06_SpringBoot—入门properties/YML文件lombok插件及使用

目录 1 SpringBoot1.1 SpringBoot介绍1.2 SpringBoot入门案例1.2.1 安装SpringBoot插件1.2.2 创建SpringBoot项目 1.3 关于SpringBoot项目说明1.3.1 关于POM.xml文件说明1.3.2 依赖配置项1.3.3 build标签 1.4 SpringBoot Maven操作1.4.1 项目打包1.4.2 java命令运行项目 1.5 关…

数据脱敏(二)脱敏算法-哈希脱敏

脱敏算法篇使用阿里云数据脱敏算法为模板,使用算子平台快速搭建流程来展示数据 哈希脱敏是一种数据安全处理技术&#xff0c;主要用于保护敏感信息。它将原始数据&#xff08;如密码、身份证号等&#xff09;通过哈希算法转换成固定长度的哈希值&#xff0c;即使哈希值被泄露&a…

【GitHub项目推荐--老照片变清晰】【转载】

先来看一个效果图&#xff0c;这个开源项目能把模糊爆浆的老照片 1 s 内变成清晰、高清的有色照片。 而以上这些效果&#xff0c;无需专业 PS 技能&#xff0c;只用一个网页端的 Demo、点点鼠标上传图片就能搞定。 这个修复神器&#xff0c;由腾讯 PCG ARC 实验室研发&#xf…

Java 面向对象 06 对象内存图(黑马)

之前设计的如下图&#xff1a; 方法区和内存在物理上是一块的&#xff0c;但是有不好的地方&#xff0c;所以变成了这种形式&#xff1a; 一个对象的内存图&#xff1a; 在创建对象时虚拟机至少做了以下七步&#xff1a; 解释&#xff1a; 第一步&#xff1a; 第二步&#x…

使用golang对接微软Azure AI翻译

文章目录 一、官方地址二、准备工作三、代码示例 一、官方地址 https://learn.microsoft.com/zh-CN/azure/ai-services/translator/translator-text-apis?tabsgo 二、准备工作 创建服务 创建服务连接地址&#xff1a;https://portal.azure.com/#create/Microsoft.CognitiveS…

如何本地部署虚拟数字克隆人 SadTalker

环境&#xff1a; Win10 SadTalker 问题描述&#xff1a; 如何本地部署虚拟数字克隆人 SadTalker 解决方案&#xff1a; SadTalker&#xff1a;学习逼真的3D运动系数&#xff0c;用于风格化的音频驱动的单图像说话人脸动画 单张人像图像&#x1f64e; ♂️音频&#x1f3…

数据结构:顺序循环队列

队列是限制在两端操作进行插入操作与删除操作的线性表&#xff0c;允许进行插入操作的一端称为"队尾"&#xff0c;允许进行删除操作的一端称为“队头”。当线性表中没有元素时&#xff0c;称为“空队”。队列的特点是先进先出。 队列两种规定&#xff1a; 1、front…

算法第二十一天-丑数

丑数 题目要求 解题思路 首先判断数字是不是为0或者负数&#xff0c;两者均不可能成为丑数&#xff1b; 之后对n进行不断整除&#xff0c;直到无法除尽为止。 简单判断最后的数是不是1即可。 代码 class Solution:def isUgly(self, n: int) -> bool:if n<0:return Fa…

1.redhat网卡配置

想要通过cmd ping通redhat 1.在redhat输入:ifconfig 将自己主机网络适配器VMware Network Adapter VMnet1的IPv4配置在同一网段,掩码是255.255.255.0,所以最后一位不同就可以 推荐用FileZilla远程上传文件

【手撕C语言 第六集】函数(上)

文章目录 一、函数是什么&#xff1f;二、C语言中函数的分类&#xff1a;1.库函数1.1 如何学会使用库函数&#xff1f; 2. 自定义函数 三、函数的参数1.实际参数&#xff08;实参&#xff09;&#xff1a;2.形式参数&#xff08;形参&#xff09;&#xff1a; 四、函数的调用&a…

Java研学-spring框架(一)

一 概述 1 介绍 Spring框架是一个开源的Java EE应用程序框架&#xff0c;旨在简化Java企业级应用的开发难度和开发周期&#xff0c;主要通过控制反转&#xff08;IoC&#xff09;和面向切面编程&#xff08;AOP&#xff09;等技术实现。   容器&#xff08;Container&#x…