Spring Cloud详解(九)Sleuth日志跟踪
1. Sleuth简介
Spring Cloud Sleuth[sluːθ]是Spring Cloud提供的分布式系统服务链追踪组件,它大量借用了Google的Dapper,Twitter的Zipkin。
场景:
在分布式系统中,一个集群中有几十个微服务;
微服务调用微服务,一个或多个微服务的网络环境问题、硬件问题导致服务提供失败;
疑问:
我们怎么去定位这个出问题的服务?
怎么定位出问题接口?
怎么分析行为诱因?
难道要逐个的看log?
肯定不是,sleuth 可以给我们提供很好的方案,用服务链路追踪来快速查看。sleuth 集成了Zipkin、HTrace 几种链路追踪工具,我选择zipkin。
在微服务中,通常根据业务模块分服务,项目中前端发起一个请求,后端可能跨几个服务调用才能完成这个请求(如下图)。
如果系统越来越庞大,服务之间的调用与被调用关系就会变得很复杂,假如一个请求中需要跨几个服务调用,其中一个服务由于网络延迟等原因挂掉了,那么这时候我们需要分析具体哪一个服务出问题了就会显得很困难。Spring Cloud Sleuth服务链路跟踪功能,就可以帮助我们快速的发现错误根源以及监控分析每条请求链路上的性能等等。
Spring Cloud Sleuth是一个在应用中实现日志跟踪的强有力的工具。使用Sleuth库可以应用于计划任务 、多线程服务或复杂的Web请求,尤其是在一个由多个服务组成的系统中。当我们在这些应用中来诊断问题时,即使有日志记录也很难判断出一个请求需要将哪些操作关联在一起。
如果想要诊断复杂操作,通常的解决方案是在请求中传递唯一的ID到每个方法来识别日志。 而Sleuth可以与日志框架Logback、SLF4J轻松地集成,通过添加独特的标识符来使用日志跟踪和诊断问题。
微服务跟踪(sleuth)其实是一个工具,它在整个分布式系统中能跟踪一个用户请求的过程(包括数据采集,数据传输,数据存储,数据分析,数据可视化),捕获这些跟踪数据,就能构建微服务的整个调用链的视图,这是调试和监控微服务的关键工具。
SpringCloudSleuth有4个特点:
特点 | 说明 |
---|---|
提供链路追踪 | 通过sleuth可以很清楚的看出一个请求经过了哪些服务,可以方便的理清服务局的调用关系 |
性能分析 | 通过sleuth可以很方便的看出每个采集请求的耗时,分析出哪些服务调用比较耗时,当服务调用的耗时,随着请求量的增大而增大时,也可以对服务的扩容提供一定的提醒作用 |
数据分析优化链路 | 对于频繁地调用一个服务,或者并行地调用等,可以针对业务做一些优化措施 |
可视化 | 对于程序未捕获的异常,可以在zipkpin界面上看到 |
2. Maven依赖
<!--sleuth-->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
3. Application.properties配置
配置应用名称,以便日志输出。
# 应用程序名称
spring.cloud.config.name=goods-service
4. 日志输出测试
4.1 编写控制器
@RestController
@RequestMapping("goods")
@Slf4j
public class GoodsController {@Autowiredprivate RestTemplate restTemplate;/*** 注入Rest模板* @return*/@Beanpublic RestTemplate getRestTemplate() {return new RestTemplate();}@GetMappingpublic String getGoodsByUser() {String response = (String) restTemplate.exchange("http://localhost:8080/user",HttpMethod.GET, null, new ParameterizedTypeReference<String>() {}).getBody();log.info("测试sleuth");return response;}
}
4.2 查看日志结果:
日志的格式为:[application name, traceId, spanId, export]
[goods-client,5a43ec515cf49d23,5a43ec515cf49d23,true] 18076 --- [nio-8081-exec-7] c.xtsz.goods.controller.GoodsController : 测试sleuth
说明:
- application name — 应用的名称,也就是application.properties中的spring.application.name参数配置的属性。
- traceId — 为一个请求分配的ID号,用来标识一条请求链路。
- spanId — 表示一个基本的工作单元,一个请求可以包含多个步骤,每个步骤都拥有自己的spanId。一个请求包含一个TraceId,多个SpanId
- export — 布尔类型。表示是否要将该信息输出到类似Zipkin这样的聚合器进行收集和展示。
5. 异步线程池
5.1 配置类
@Configuration
@EnableAsync
public class ThreadConfig extends AsyncConfigurerSupport {@Autowiredprivate BeanFactory beanFactory;@Overridepublic Executor getAsyncExecutor() {// 线程池定义ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();threadPoolTaskExecutor.setCorePoolSize(7);threadPoolTaskExecutor.setMaxPoolSize(42);threadPoolTaskExecutor.setQueueCapacity(11);threadPoolTaskExecutor.setThreadNamePrefix("MyExecutor-");threadPoolTaskExecutor.initialize();// sleuth 中延时跟踪执行return new LazyTraceExecutor(beanFactory, threadPoolTaskExecutor);}
}
5.2 业务类
@Service
@Slf4j
public class UserService {@Asyncpublic void asyncMethod() throws InterruptedException {log.info("开始执行异步方法");Thread.sleep(1000L);log.info("异步方法执行结束");}
}
5.3 控制器
@RestController
@RequestMapping("user")
@Slf4j
public class UserController {@Autowiredprivate UserService userService;@RequestMapping("/async")public String async() throws InterruptedException {System.out.println("####控制器输出#### 1");log.info("####异步方法调用前####");userService.asyncMethod();log.info("####异步方法调用后####");return "success";}
}
5.4 测试
地址:http://localhost:8080/user/async
####控制器输出#### 1
2019-09-18 11:15:18.335 INFO [user-client,3f575919b0df993a,3f575919b0df993a,true] 19040 --- [nio-8080-exec-5] com.xtsz.user.controller.UserController : ####异步方法调用前####
2019-09-18 11:15:18.335 INFO [user-client,3f575919b0df993a,3f575919b0df993a,true] 19040 --- [nio-8080-exec-5] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService
2019-09-18 11:15:18.338 INFO [user-client,3f575919b0df993a,3f575919b0df993a,true] 19040 --- [nio-8080-exec-5] com.xtsz.user.controller.UserController : ####异步方法调用后####
2019-09-18 11:15:18.342 INFO [user-client,3f575919b0df993a,05fddf5ed5bbbf59,true] 19040 --- [ MyExecutor-1] com.xtsz.user.service.UserService : 开始执行异步方法
2019-09-18 11:15:19.020 INFO [user-client,,,] 19040 --- [ender@5ae1c281}] c.netflix.config.ChainedDynamicProperty : Flipping property: localhost.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2019-09-18 11:15:19.036 INFO [user-client,,,] 19040 --- [ender@5ae1c281}] c.n.u.concurrent.ShutdownEnabledTimer : Shutdown hook installed for: NFLoadBalancer-PingTimer-localhost
2019-09-18 11:15:19.036 INFO [user-client,,,] 19040 --- [ender@5ae1c281}] c.netflix.loadbalancer.BaseLoadBalancer : Client: localhost instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=localhost,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
2019-09-18 11:15:19.039 INFO [user-client,,,] 19040 --- [ender@5ae1c281}] c.n.l.DynamicServerListLoadBalancer : Using serverListUpdater PollingServerListUpdater
2019-09-18 11:15:19.041 INFO [user-client,,,] 19040 --- [ender@5ae1c281}] c.n.l.DynamicServerListLoadBalancer : 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@69326b62
2019-09-18 11:15:19.343 INFO [user-client,3f575919b0df993a,05fddf5ed5bbbf59,true] 19040 --- [ MyExecutor-1] com.xtsz.user.service.UserService : 异步方法执行结束
3f575919b0df993a:traceId相同
3f575919b0df993a,05fddf5ed5bbbf59: spanId不同。
Sleuth将traceId传入了异步方法并创建了以新的spanId
,代表这是同一个请求但进入了另一个处理阶段,由一个异步线程来执行。
6. 计划任务
6.1 计划任务配置类
/*** 实现了SchedulingConfigurer 接口并重写了configureTasks 方法,* 同时在类上面添加了@EnableScheduling 注解。*/
@Configuration
@EnableAsync
@EnableScheduling
public class SchedulingConfig extends AsyncConfigurerSupport implements SchedulingConfigurer {/*** 注册计划任务* @param scheduledTaskRegistrar*/@Overridepublic void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {scheduledTaskRegistrar.setScheduler(schedulingExecutor());}@Beanpublic Executor schedulingExecutor() {// 生成一个线程池return Executors.newScheduledThreadPool(1);}
}
6.2 业务类
@Service
@Slf4j
public class UserService {@Asyncpublic void asyncMethod() throws InterruptedException {log.info("开始执行异步方法");Thread.sleep(1000L);log.info("异步方法执行结束");}
}
6.3 计划任务类
@Service
@Slf4j
public class SchedulingService {private final UserService userService;/*** 初始化对象* @param userService*/@Autowiredpublic SchedulingService(UserService userService) {this.userService = userService;}@Scheduled(fixedDelay = 30000)public void scheduledWork() throws InterruptedException {log.info("开始计划任务");userService.asyncMethod();log.info("结束计划任务");}
}
6.4 控制器类
@RestController
@RequestMapping("user")
@Slf4j
public class UserController {@Autowiredprivate UserService userService;@RequestMapping("/async")public String async() throws InterruptedException {System.out.println("####控制器输出#### 1");log.info("####异步方法调用前####");userService.asyncMethod();log.info("####异步方法调用后####");return "success";}
}
6.5 测试
结果:
2019-09-18 11:27:46.311 INFO [user-client,621cbb0fca03c421,621cbb0fca03c421,true] 13584 --- [pool-2-thread-1] com.xtsz.user.service.SchedulingService : 开始计划任务
2019-09-18 11:27:46.312 INFO [user-client,621cbb0fca03c421,621cbb0fca03c421,true] 13584 --- [pool-2-thread-1] com.xtsz.user.service.SchedulingService : 结束计划任务
2019-09-18 11:27:46.312 INFO [user-client,80d6ab72b300f539,80d6ab72b300f539,true] 13584 --- [cTaskExecutor-2] com.xtsz.user.service.UserService : 开始执行异步方法
2019-09-18 11:27:47.313 INFO [user-client,80d6ab72b300f539,80d6ab72b300f539,true] 13584 --- [cTaskExecutor-2] com.xtsz.user.service.UserService : 异步方法执行结束
可以看到Sleuth为每个任务实例都创建一个新的traceId和spanId。
Spring Cloud Sleuth可以应用在各种各样的单一Web应用中。我们可以使用这项技术轻松地为一个请求采集日志,即使请求跨越多个线程。帮助我们在多线程环境下进行清晰的调试,通过识别traceId和spanId来确定每一个操作和操作中的每一步,这样可以减轻我们做日志分析的复杂性。
7. logback-spring.xml 配置
# 配置日志文件
logging.config=classpath:logback-spring.xml
注:当文件名为logback-spring.xml时 不需要配置logging.config
<?xml version="1.0" encoding="UTF-8"?>
<!--该日志将日志级别不同的log信息保存到不同的文件中 -->
<configuration><include resource="org/springframework/boot/logging/logback/defaults.xml" /><springProperty scope="context" name="springAppName"source="spring.application.name" /><!-- 日志在工程中的输出位置 --><property name="LOG_FILE" value="${BUILD_FOLDER:-build}/${springAppName}" /><!-- 控制台的日志输出样式 --><property name="CONSOLE_LOG_PATTERN"value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}" /><!-- 控制台输出 --><appender name="console" class="ch.qos.logback.core.ConsoleAppender"><filter class="ch.qos.logback.classic.filter.ThresholdFilter"><level>INFO</level></filter><!-- 日志输出编码 --><encoder><pattern>${CONSOLE_LOG_PATTERN}</pattern><charset>utf8</charset></encoder></appender><!-- 为logstash输出的JSON格式的Appender --><appender name="logstash"class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${LOG_FILE}.json</file><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!--日志文件输出的文件名 --><fileNamePattern>${LOG_FILE}.json.%d{yyyy-MM-dd}.gz</fileNamePattern><!--日志文件保留天数 --><MaxHistory>3</MaxHistory></rollingPolicy><!-- 日志输出编码 --><encoderclass="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder"><providers><timestamp><timeZone>UTC</timeZone></timestamp><pattern><pattern>{"severity": "%level","service": "${springAppName:-}","trace": "%X{X-B3-TraceId:-}","span": "%X{X-B3-SpanId:-}","exportable": "%X{X-Span-Export:-}","pid": "${PID:-}","thread": "%thread","class": "%logger{40}","rest": "%message"}</pattern></pattern></providers></encoder></appender><!-- 为logstash输出的JSON格式的Appender --><appender name="logstash2"class="net.logstash.logback.appender.LogstashTcpSocketAppender"><destination>localhost:9600</destination><!-- 日志输出编码 --><encoderclass="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder"><providers><timestamp><timeZone>UTC</timeZone></timestamp><pattern><pattern>{"severity": "%level","service": "${springAppName:-}","trace": "%X{X-B3-TraceId:-}","span": "%X{X-B3-SpanId:-}","exportable": "%X{X-Span-Export:-}","pid": "${PID:-}","thread": "%thread","class": "%logger{40}","rest": "%message"}</pattern></pattern></providers></encoder></appender><!-- 日志输出级别 --><root level="INFO"><appender-ref ref="console" /><appender-ref ref="logstash" /><appender-ref ref="logstash2" /></root>
</configuration>
8. sleuth中的一些术语
- Span:基本工作单元,例如,在一个新建的span中发送一个RPC等同于发送一个回应请求给RPC,span通过一个64位ID唯一标识,trace以另一个64位ID表示,span还有其他数据信息,比如摘要、时间戳事件、关键值注释(tags)、span的ID、以及进度ID(通常是IP地址)span在不断的启动和停止,同时记录了时间信息,当你创建了一个span,你必须在未来的某个时刻停止它。
- Trace:一系列spans组成的一个树状结构,例如,如果你正在跑一个分布式工程,你可能需要创建一个trace。
- Annotation:用来及时记录一个事件的存在,一些核心annotations用来定义一个请求的开始和结束
- cs - Client Sent -客户端发起一个请求,这个annotion描述了这个span的开始
- sr - Server Received -服务端获得请求并准备开始处理它,如果将其sr减去cs时间戳便可得到网络延迟
- ss - Server Sent -注解表明请求处理的完成(当请求返回客户端),如果ss减去sr时间戳便可得到服务端需要的处理请求时间
- cr - Client Received -表明span的结束,客户端成功接收到服务端的回复,如果cr减去cs时间戳便可得到客户端从服务端获取回复的所有所需时间
Spring Cloud详解(九)Sleuth日志跟踪相关推荐
- 全网最全微服务架构—Spring Cloud详解,没有比这更详细的了!
原文地址: https://blog.51cto.com/14570694/2482244 本文不是讲解如何使用Spring Cloud的教程,而是探讨Spring Cloud是什么,以及它诞生的背景 ...
- Spring入门详解
typora-copy-images-to: upload Spring入门详解 Spring框架是Java开发中最常用的框架,功能非常强大 源码下载:Spring Framework jar包.文档 ...
- Spring IoC详解
Spring IoC详解 原文地址:Spring IoC详解 写在最前 本文将主要写Spring最核心的部分,为什么写这篇的原因也是因为在刚开始学习Spring的时候,学得太粗糙了.感觉学了个皮毛,从 ...
- Spring AOP详解(转载)所需要的包
上一篇文章中,<Spring Aop详解(转载)>里的代码都可以运行,只是包比较多,中间缺少了几个相应的包,根据报错,几经百度搜索,终于补全了所有包. 截图如下: 在主测试类里面,有人怀疑 ...
- Spring JDBC详解
<Spring JDBC详解> 本文旨在讲述Spring JDBC模块的用法.Spring JDBC模块是Spring框架的基础模块之一. 一.概述 在Spring JDBC模块中,所有的 ...
- Spring 体系结构详解
Spring 体系结构详解 核心容器(Core Container) Core和Beans模块提供了Spring最基础的功能,提供IOC和依赖注入特性.这里的基础概念是BeanFactory,它提供对 ...
- [转载]Spring配置文件详解一:
2019独角兽企业重金招聘Python工程师标准>>> 原文地址:Spring配置文件详解一:<context:annotation-config/>与<conte ...
- struts2+hibernate+spring配置详解
#struts2+hibernate+spring配置详解 struts2+hibernate+spring配置详解 哎 ,当初一个人做好难,现在终于弄好了,希望自学这个的能少走些弯路. 以下是自己配 ...
- ORB-SLAM2详解(四)跟踪
ORB-SLAM2详解(四)跟踪 本人邮箱:sylvester0510@163.com,欢迎交流讨论, 欢迎转载,转载请注明网址http://blog.csdn.net/u010128736/ 这 ...
最新文章
- 【Qt】Qt再学习(七):QLocalServer、QLocalSocket
- mysql 5.8 新特性_MySQL 5.4的新特性解析
- 斯坦福机器学习公开课学习笔记(2)—监督学习 梯度下降
- uva5984(简单计算几何)
- thinkphp加锁抢购商品
- Sentry UDFWhiteList bug分析
- 【Python数据结构】——链表
- 能力=知识+技能+经验
- 94年的博士后又拿到了这个金奖!原来是他的学弟
- 怎么看电脑能不能升级win10系统|检测电脑适不适合安装win10的方法
- 中国最富包租婆!每年坐地收租200亿,身家相当于十个老干妈
- 单片机 c语言 pwm输出,单片机中PWM程序工作原理图文详解
- iOS开发之UITableView全面解析
- python架构师是做什么的_架构师的工作都干些什么?!想做架构师必看!
- datagrid commandname
- 浅谈智慧校园建设中存在的问题及解决方案
- Theory: If statement(理论:如果语句)
- Unity URP 渲染管线着色器编程 104 之 镜头光晕(lensflare)的实现
- c# WindowsForm上使用Panel制作画板的一些小功能
- 【整理】C#中GridView隐藏指定列的方法
热门文章
- 一行神奇的javascript代码
- 不用修改注册表和组策略也能在 Win11 报名教师资格证
- 最新小储云商城V1.782免授权源码
- 从头开始敲代码之《从BaseApplication/Activity开始(五)》(自定义控件,实现点击/滑动翻页)
- Matlab : 切换中、英文用户界面 (中文变英文)
- 解读2022中国城市大脑首批三项标准(新版)
- Python编写一球从100米高度自由落下,每次落地后反跳回原高度的一半;再落下,求它在第10次落地时,共经过多少米?第10次反弹多高?
- 计算机视觉之图像分割——水平集方法_ACWE2001
- Java数组数据类型
- 什么是极客(Geek)?怎样成为一名极客?