Spring Cloud 集成 链路追踪
摘要
关于 链路追踪
当我们在使用微服务时,会需要有这样的需求,当一个服务调用了另一个服务,另一个服务又调用了其他的服务,其他的服务又调用了更多的服务,这样一条无止境的调用链,若其中一个环节出现问题,整个请求就会崩塌,当我们去排查问题时,往往需要从调用链的源头开始找起,一步一步的深入排查,这是一件很费劲的事情。
所以我们需要在日志中加入一个“ 标识 ”,来表示这是一个请求。
所需工具
Log4j2
Log4j2 在此作为日志输出的核心组件( 异步日志组件 )
Zipkin
Zipkin 在此作为链路追踪的核心服务,我们需要他做的事情其实只有一个,就是生成 “标识” 。
Sleuth
Sleuth 在此配合 Zipkin 服务,主要负责系统采样工作。
1. Log4j2
首先,引入 Log4j2 的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.6</version>
</dependency>
注:disruptor是一个基于无锁化环形队列的高性能并发框架,log4j2就是借助它进行高性能日志异步输出的。
接着,增加 log4j2.xml 文件来配置日志信息
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<!-- 输出控制台的配置 -->
<Console name="console" target="SYSTEM_OUT">
<!-- 指定日志输出格式 【日期时间 等级 日志】 -->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</Console>
<!-- 日志记录配置 -->
<RollingFile name="wholeRolling" fileName="/home/msg.log" filePattern="/home/msg-%d{yyyy-MM-dd}.log">
<!-- 过滤掉一些等级的日志 -->
<Filters>
<ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
</Policies>
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{TRACE_ID}] %-5level %class{36} %L %M - %msg%xEx%n"/>
<DefaultRolloverStrategy max="99999">
<Delete basePath="/home" maxDepth="2">
<IfFileName glob="msg-*.log"/>
<IfLastModified age="5d"/>
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
<!-- 日志记录配置 -->
<RollingFile name="errorRolling" fileName="/home/error.log" filePattern="/home/error-%d{yyyy-MM-dd}.log">
<Filters>
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
</Policies>
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{TRACE_ID}] %-5level %class{36} %L %M - %msg%xEx%n"/>
<DefaultRolloverStrategy max="99999">
<Delete basePath="/home" maxDepth="2">
<IfFileName glob="error-*.log"/>
<IfLastModified age="5d"/>
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</Appenders>
<Loggers>
<root level="info">
<AppenderRef ref="console"/>
<appender-ref ref="wholeRolling"/>
<appender-ref ref="errorRolling"/>
</root>
<logger name="log" level="debug" additivity="false">
<AppenderRef ref="console"/>
<appender-ref ref="wholeRolling"/>
<appender-ref ref="errorRolling"/>
</logger>
<logger name="com.xnyzc.lhy" level="debug" additivity="false">
<AppenderRef ref="console"/>
<appender-ref ref="wholeRolling"/>
<appender-ref ref="errorRolling"/>
</logger>
</Loggers>
</Configuration>
当新增了以上配置之后,此时,我们控制台输出的日志已经具备以下格式
2020-07-10 00:27:42.490 INFO xxx xxx
这里添加一个 Log4j2 的小插曲,如何使用 Log4j2 来添加一个请求标识
使用 Log4j2 生成请求标识
首先,新增一个 Interceptor 类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
/**
* @author cuixiaojian
* @create 2020-07-10 00:57
*/
@Component
public class LogInterceptor extends HandlerInterceptorAdapter {
private Logger log = LoggerFactory.getLogger(this.getClass());
/**
* 日志跟踪标识
*/
private static final String TRACE_ID = "TRACE_ID";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 使用 UUID 生成标识
String traceId = UUID.randomUUID().toString();
if (StringUtils.isEmpty(MDC.get(TRACE_ID))) {
// MDC是log4j用于存储应用程序的上下文信息(context infomation),从而便于在log中使用这些上下文信息。
// 向 MDC 中添加一个标识信息
MDC.put(TRACE_ID, traceId);
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
// 请求结束后,移除该标识
MDC.remove(TRACE_ID);
}
}
接着,将此 Interceptor 类,加入 WebMVC 中,至此所有的请求都会通过 logInterceptor 类
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 在此重写方法中添加 logInterceptor 类
registry.addInterceptor(logInterceptor);
}
}
最后,在 Log4j2.xml 中添加配置,指定日志输出格式
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{TRACE_ID}] %-5level %class{36} %L %M - %msg%xEx%n"/>
然后再当我们发起一个HTTP请求时候,控制台输出的日志具备以下格式
2020-07-10 00:27:40.277 [85b5cde89-25186da-9e63a944-3bcf2ee3] INFO xxx xxx
其实不难看出,大括号中的 UUID 就是本次请求的唯一标识,当我们通过该 “标识” 去检索日志时,就会检索出该请求的所有日志,但是使用 Log4j2 生成的请求标识,他的作用域仅仅在一个服务中,换句话说就是在微服务中以这种方式生成 “标识” 不可行。因为当一个服务调用到另一个服务时,其实是发起了第二次请求,依然会被 mvc 拦截,生成新的请求 “标识”。
因为 Log4j2 生成的请求标识不可行,所以我们需要一个作用在整个微服务系统之上的组件 - Zipkin
2. Zipkin
Zipkin 作为链路追踪的核心服务, Twitter 公司 为我们提供了各种各样的平台搭建方式。
- 将 Zipkin 集成到自己的微服务系统 Module 中;
- 使用 Jar 包启动 Zipkin 服务;
- 使用 docker-compose 搭建 Zipkin 服务;
我们简单介绍一、二种方式,使用第三种 Docker 方式搭建 Zipkin 平台
1. 将 Zipkin 集成到自己的微服务系统 Module 中
1. 在项目中创建一个 Module
2. 引入 Zipkin 依赖
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
</dependency>
3. 添加配置文件 yml 或 properties
server.port=9411
spring.application.name=zipkin
zipkin.storage.type=mem
4. 启动类添加 @EnableZipkinServer 注解
5. 启动 Zipkin 服务,访问 http://localhost:9411/zipkin/,可进入 Zipkin 控制台 UI 界面
2. 使用 Jar 包启动 Zipkin 服务
1. 官网 Jar 包下载地址:https://dl.bintray.com/openzipkin/maven/io/zipkin/zipkin-server/
2. 下载后执行如下命令启动服务,默认端口为 9411
java -jar zipkin-server-2.19.3-exec.jar
3. 或者我们也可以执行如下命令将 Zipkin 服务作为守护进程后台运行
nohup java -jar zipkin-server-2.19.3-exec.jar &
4. 访问 http://localhost:9411/zipkin/,可进入 Zipkin 控制台 UI 界面
3. 使用 docker-compose 搭建 Zipkin 服务
参考资料:
http://www.apgblogs.com/docker-compose-zipkin/
https://windmt.com/2018/04/24/spring-cloud-12-sleuth-zipkin/
https://www.jianshu.com/p/7d9ff93bc89e
首先,需要安装 Docker 和 Docker-compose
// 安装 Docker 社区版
1. 安装 Docker
yum install -y docker-ce-18.03.1.ce
2. 启动并加入开机启动
systemctl start docker
systemctl enable docker
3. 验证安装是否成功
docker version
// 安装 Docker-compose (下载时间较长)
1. 通过访问 https://github.com/docker/compose/releases/latest 得到最新的 docker-compose 版本
curl -L https://github.com/docker/compose/releases/download/1.23.2/docker-compose-`uname -s`-`uname -m` -o /usr/bin/docker-compose
2. 给 docker-compose 授权
chmod +x /usr/bin/docker-compose
接着即可以使用 Docker-compose
// 创建配置文件
version: '2'
services:
zipkin:
// 指定镜像
image: openzipkin/zipkin
// 容器名称
container_name: zipkin
// 配置参数(将链路信息保存至ES)
environment:
- STORAGE_TYPE=elasticsearch
- ES_HOSTS=192.168.3.109:9200
- ES_INDEX=zipkin
- ES_INDEX_SHARDS=1
- ES_INDEX_REPLICAS=1
network_mode: host
ports:
# Port used for the Zipkin UI and HTTP Api
- 9411:9411
// 进入 yml 配置文件所在目录,执行命令创建容器
docker-compose up -d
// 访问页面
http://localhost:9411/zipkin/
Zipkin 平台搭建成功之后,即可以配置与我们的微服务系统连接,这里就需要用到 Sleuth组件
3. Sleuth
1. 引入 Sleuth 依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
<version>1.2.5.RELEASE</version>
</dependency>
2. 在 yml 或 properties 中配置
spring:
sleuth:
web:
client:
enabled: true
sampler:
# 将采样比例设置为 1.0,也就是全部都需要。默认是 0.1
probability: 0.2
zipkin:
# 指定了 Zipkin 服务器的地址
base-url: http://localhost:9411/
至此,还差最后一步配置 Log4j2 ,链路追踪已经完成,启动项目,来查看日志信息
配置 Log4j2 的日志输出格式,添加 “[%X{X-B3-TraceId},%X{X-B3-SpanId}]”
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{X-B3-TraceId},%X{X-B3-SpanId}] %-5level %class{36} %L %M - %msg%xEx%n"/>
查看输出日志
此时,我们的日志以具备链路信息,查看服务调用链后会发现,我们便可以通过 TraceId 来检索一个请求的调用链下的所有日志信息。
2020-07-10 00:27:40.277 [85b5cde8925186da,9e63a9443bcf2ee3] INFO xxx xxx