(Dubbo架构)基于MDC+Filter的跨应用分布式日志追踪解决方案
在单体应用中,日志追踪通常的解决方案是给日志添加 tranID(追踪ID),生成规则因系统而异,大致效果如下:

查询时只要使用 grep 命令进行追踪id筛选即可查到此次调用链中所有日志,但是在 dubbo 分布式系统中,由于 tranID 底层存储在 ThreadLocal 中,由于应用分布在不同的机器中,无法跨应用共享,同一链路多个应用之间的 tranID 不一致,出现线上问题时,日志的排查就尤为棘手。
先说结论:dubbo 分布式应用也可使用MDC进行分布式日志追踪,但是需要配合dubbo提供的filter技术来实现,更细化的,如果想对系统无侵入加入追踪日志,则可再配合拦截器对请求进行统一拦截,实现追踪id自动生成,链路id自动传递,最终实现效果为任意系统日志的追踪id即可获取全系统全链路日志。
实现步骤:
定义 TraceUtil,该工具类主要作用为将操作MDC的步骤部分封装,提供初始化链路、传递链路id、销毁链路操作,即链路生命周期管理类。
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.rpc.RpcContext;
import org.slf4j.MDC; import java.util.UUID; public class TraceUtil { public final static String TRACE_ID = "trace_id";
public final static String TRACE_EXTENDED_INFO = "extended_info"; public static void initTrace(String extendedInfo) {
if (StringUtils.isBlank(MDC.get(TRACE_ID))) {
String traceId = generateTraceId();
setTraceId(traceId);
MDC.put(TRACE_EXTENDED_INFO, extendedInfo);
}
} public static void getTraceFrom(RpcContext context) {
String traceId = context.getAttachment(TRACE_ID);
if (StringUtils.isNotBlank(traceId)) {
setTraceId(traceId);
}
String uri = context.getAttachment(TRACE_EXTENDED_INFO);
if (StringUtils.isNotEmpty(uri)) {
MDC.put(TRACE_EXTENDED_INFO, uri);
}
} public static void putTraceInto(RpcContext context) {
String traceId = MDC.get(TRACE_ID);
if (StringUtils.isNotBlank(traceId)) {
context.setAttachment(TRACE_ID, traceId);
} String uri = MDC.get(TRACE_EXTENDED_INFO);
if (StringUtils.isNotBlank(uri)) {
context.setAttachment(TRACE_EXTENDED_INFO, uri);
}
} public static void clearTrace() {
MDC.clear();
} private static void setTraceId(String traceId) {
traceId = StringUtils.left(traceId, 36);
MDC.put(TRACE_ID, traceId);
} private static String generateTraceId() {
return UUID.randomUUID().toString();
}
}
DubboRequestDTO 和 DubboResponseDTO 定义如下:
@Data
public class DubboRequestDTO implements Serializable {
private String interfaceClass;
private String methodName;
private Object[] args;
} @Data
public class DubboResponseDTO implements Serializable {
private String interfaceClassName;
private String methodName;
private String result;
private long spendTime;
}
定义Dubbo Filter,如下,该类为 Dubbo Filter 接口实现类,主要作用为传递追踪ID,打印追踪日志
package com.serviceshare.component.tracking; import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.registry.Constants;
import org.apache.dubbo.rpc.*; import java.io.File;
import java.io.InputStream;
import java.util.Arrays; @Slf4j
@Activate(order = 999, group = {Constants.PROVIDER_PROTOCOL, Constants.CONSUMER_PROTOCOL})
public class DubboTraceFilter implements Filter { private static final int SLOW_METHOD_THRESHOLD = 3000; @Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 处理 Trace 信息
printRequest(invocation);
// 执行前
handleTraceId();
long start = System.currentTimeMillis();
Result result = invoker.invoke(invocation);
long end = System.currentTimeMillis();
// 执行后
final long executionTime = end - start;
printResponse(invocation, result, executionTime);
return result;
} private void printRequest(Invocation invocation) {
DubboRequestDTO requestDTO = new DubboRequestDTO();
requestDTO.setInterfaceClass(invocation.getInvoker().getInterface().getName());
requestDTO.setMethodName(invocation.getMethodName());
requestDTO.setArgs(getArgs(invocation));
log.info("RPC请求开始 , {}", requestDTO);
} private void printResponse(Invocation invocation, Result result, long spendTime) {
DubboResponseDTO responseDTO = new DubboResponseDTO();
responseDTO.setInterfaceClassName(invocation.getInvoker().getInterface().getName());
responseDTO.setMethodName(invocation.getMethodName());
responseDTO.setResult(JSON.toJSONString(result.getValue()));
responseDTO.setSpendTime(spendTime);
} private Object[] getArgs(Invocation invocation) {
Object[] args = invocation.getArguments();
args = Arrays.stream(args).filter(arg -> {
// 过滤大参
if (arg instanceof byte[] || arg instanceof Byte[] || arg instanceof InputStream || arg instanceof File) {
return false;
}
return true;
}).toArray();
return args;
} private void handleTraceId() {
RpcContext context = RpcContext.getContext();
if (context.isConsumerSide()) {
TraceUtil.putTraceInto(context);
} else if (context.isProviderSide()) {
TraceUtil.getTraceFrom(context);
}
}
}
启用Filter,Dubbo 采用 SPI 扩展方式,启用我们自己编写的 Filter ,非常方便,只需要在 classpath 路径下 META-INF 文件夹下新建 com.alibaba.dubbo.rpc.Filter 文件引入以下内容即可
(注意,包名替换为自己定义的 Filter 类包名)
com.serviceshare.component.tracking.DubboTraceFilter
修改 logback 配置文件 pattern,添加 extended_info 和 trace_id
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS}[%X{extended_info}][%X{trace_id}][%thread] %-5level %logger{50}:%L - %msg%n</pattern>
尝试在启动类插桩测试
public static void main(String[] args) throws UnknownHostException {
TraceUtil.initTrace("START-APP");
SpringApplication.run(SysApplication.class, args);
}
启动项目,查看效果

多个系统都按照上述配置配置后,即可实现日志链路追踪,这样属于半侵入式集成,因为每个“入口”都需要手动插桩初始化链路,对系统所有已有接口造成了代码侵入,肯定有更完美的方案,那就是最终实现方案,无侵入式集成,怎么做?
首先,入口,不一定是 web 接口等一系列由内外部触发程序代码运行的点,都可以称为入口。还可能是定时任务,所以如果要保证每条日志都有迹可循的话,需要保证外部触发和内部触发的操作都要初始化MDC,Spring 提供的AOP+拦截器刚好可以实现这一点,借助拦截器我们拦截所有系统请求,在系统进入系统入口时,初始化链路,后续一系列调用自然而然都记录了追踪ID。
定时任务属于系统调度内部触发,同一定时任务如果不分片不会跨系统,但是也需要追踪日志,可以使用 Spring 提供的 AOP 进行定时任务注解定点拦截,实现定时任务执行前插桩,执行完毕后拆桩。
web请求自动插桩拦截器实现:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; /**
* web请求链路追踪拦截器
*
* @author shaozhengmao
* @create 2021-05-26 1:10 下午
* @desc
*/
@Configuration
public class TraceWebFilter extends OncePerRequestFilter { @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
TraceUtil.initTrace(request.getRequestURI());
filterChain.doFilter(request, response);
TraceUtil.clearTrace();
}
}
该类实现了 JavaEE 提供的 Filter 接口,即过滤器,对所有web请求进行拦截,前置设置追踪元信息,后置清空追踪元信息,实现了 web 请求全链路追踪,为什么不使用 Spring 提供的 WebMvcConfigurer 接口?如果使用 WebMvcConfigurer 接口,请求失败的情况下,追踪ID会丢失,可自行测试。
定时任务自动插桩实现:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component; import java.lang.reflect.Method; /**
* 定时任务链路追踪拦截器
* @author shaozhengmao
* @create 2021-05-26 1:32 下午
* @desc
*/
@Aspect
@Component
@Order(1)
public class TraceTaskConfigurer { @Around("@annotation(org.springframework.scheduling.annotation.Scheduled)")
public Object verifyRoleExecuteCommand(ProceedingJoinPoint pjp) throws Throwable {
// 获取当前拦截方法的对象
MethodSignature msig = (MethodSignature) pjp.getSignature();
Method targetMethod = pjp.getTarget().getClass().getDeclaredMethod(msig.getName(), msig.getMethod().getParameterTypes());
try {
TraceUtil.initTrace(targetMethod.getName());
return pjp.proceed();
} finally {
TraceUtil.clearTrace();
}
}
}
和链路追踪系统集成,SkyWlaking 提供了分布式链路追踪技术,当集成 SkyWlaking 后,可使用 SkyWlaking 的追踪id来初始化日志追踪id,这样既可无缝和 SkyWlaking 集成,从而实现,分布式调用链路追踪+分布式链路调用日志深度集成。
分布式追踪日志有什么好处,对于生产环境开发or运维人员排查错误有极大的帮助,可以很方便的找到一个请求完整的请求链路日志,当然,对于日志的探索,远不止此,随着系统的庞大,肯定会选择更好的处理方式,比如ELK,而分布式追踪ID对于集成 ELK 也提供了非常好的帮助。
集成步骤总结:
1.classpath 新增 /META-INF/dubbo/com.alibaba.dubbo.rpc.Filter 内容 com.emax.zhenghe.common.tracking.DubboTraceFilter
2.Spring扫描包新增 "com.emax.zhenghe.common.tracking"
3.logback 打印模板新增 [%X{extended_info}][%X{trace_id}]
最终效果
系统1:

系统2:

千里之行,始于足下
(Dubbo架构)基于MDC+Filter的跨应用分布式日志追踪解决方案的更多相关文章
- Dubbo 分布式 日志 追踪
使用dubbo分布式框架进行微服务的开发,一个大系统往往会被拆分成很多不同的子系统,并且子系统还会部署多台机器,当其中一个系统出问题了,查看日志十分麻烦. 所以需要一个固定的流程ID和机器ip地址等来 ...
- Dubbo分布式日志追踪
使用dubbo分布式框架进行微服务的开发,一个大系统往往会被拆分成很多不同的子系统,并且子系统还会部署多台机器,当其中一个系统出问题了,查看日志十分麻烦. 所以需要一个固定的流程ID和机器ip地址等来 ...
- 【日志追踪】(微服务应用和单体应用)-logback中的MDC机制
一.MDC介绍 MDC(Mapped Diagnostic Contexts)映射诊断上下文,该特征是logback提供的一种方便在多线程条件下的记录日志的功能, 某些应用程序采用多线程的方式来处理多 ...
- 基于spring boot 和MDC实现 同一笔记录的日志跟踪实现--1.filter
同一个项目中,一般包含controller/servlet.service.dao等.1笔记录的日志贯穿于controller.service.dao中,在并发情况下,那如何找出该笔日志? 可通过以下 ...
- Dubbo架构设计详解-转
Dubbo架构设计详解 2013-09-03 21:26:59 Yanjun Dubbo是Alibaba开源的分布式服务框架,它最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解 ...
- 最近帮客户实施的基于SQL Server AlwaysOn跨机房切换项目
最近帮客户实施的基于SQL Server AlwaysOn跨机房切换项目 最近一个来自重庆的客户找到走起君,客户的业务是做移动互联网支付,是微信支付收单渠道合作伙伴,数据库里存储的是支付流水和交易流水 ...
- dubbo系列一:dubbo介绍、dubbo架构、dubbo的官网入门使用demo
一.dubbo介绍 Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的RPC实现服务的输出和输入功能,可以和Spring框架无缝集成.简单地说,dubbo是一个基于Spri ...
- 基于CORS的GeoServer跨域访问策略
GeoServer的跨域访问问题,有多种解决方法,本文介绍一种基于CORS的GeoServer跨域访问方法. CORS简介 CORS是一个W3C标准,全称是"跨域资源共享"(Cro ...
- Dubbo 用户手册学习笔记 —— Dubbo架构
Dubbo的架构 节点角色说明 节点 角色说明 Provider 服务提供方 Consumer 服务消费方 Registry 服务注册与发现的注册中心 Monitor 统计服务的调用次数和调用时间的监 ...
随机推荐
- 最短路径(Floyd算法)
声明:图片及内容基于https://www.bilibili.com/video/BV1oa4y1e7Qt?from=articleDetail 多源最短路径的引入 Floyd算法 原理 加入a: 加 ...
- Etcd常用运维命令
目录 常用命令 常见操作 如何缩容? 如何扩容? 数据目录丢失或被误删除,节点启动失败或者加入集群报错? 操作步骤 操作步骤不正确的各种常见错误日志 常用命令 #查看集群member情况 etcdct ...
- 【秒懂音视频开发】14_AAC编码
AAC(Advanced Audio Coding,译为:高级音频编码),是由Fraunhofer IIS.杜比实验室.AT&T.Sony.Nokia等公司共同开发的有损音频编码和文件格式. ...
- 201871030127-王明强 实验二 个人项目—《D{0-1}背包问题 》项目报告
项目 内容 课程班级博客链接 班级博客 这个作业要求链接 作业要求 我的课程学习目标 (1)详细阅读<构建之法>学习并掌握PSP的具体流程(2)掌握背包问题,通过查阅相关资料,设计一个采用 ...
- CTF导引(一)
ctf预备知识: 视频:https://www.bilibili.com/video/av62214776?from=search&seid=1436604431801225989 CTF比赛 ...
- Java JFR 民间指南 - 事件详解 - jdk.ObjectAllocationInNewTLAB
重新申请 TLAB 分配对象事件:jdk.ObjectAllocationInNewTLAB 引入版本:Java 11 相关 ISSUES: JFR: RecordingStream leaks me ...
- Jenkins 自动触发执行的配置
1. 两种触发方式 2. jenkins 和 github 同步配置 ngrok 安装 webhook 配置 1. 两种触发条件 Jenkins 中建立的任务是可以设置自动触发,更进一步的实现自动化. ...
- 仅用一句SQL更新整张表的涨跌幅、涨跌率
问题场景 各大平台店铺的三项评分(物流.服务.商品)变化情况: 商品每日价格的变化记录: 股票的实时涨跌浮: 复现场景 表:主键ID,商品编号,记录时的时间,记录时的价格,创建时间. 问题:获取每个商 ...
- addslashes,htmlspecialchars,htmlentities转换或者转义php特殊字符防止xss攻击以及sql注入
一.转义或者转换的目的 1. 转义或者转换字符串防止sql注入 2. 转义或者转换字符防止html非过滤引起页面布局变化 3. 转义或者转换可以阻止javascript等脚本的xss攻击,避免出现类似 ...
- Building Fire Stations 39届亚洲赛牡丹江站B题
题意: 给你一棵树,让你再里面选取两个点作为**点,然后所有点的权值是到这两个点中最近的那个的距离,最后问距离中最长的最短是多少,输出距离还有那两个点(spj特判). 思路: 现场 ...