Dubbo 全链路追踪日志的实现
微服务架构的项目,一次请求可能会调用多个微服务,这样就会产生多个微服务的请求日志,当我们想要查看整个请求链路的日志时,就会变得困难,所幸的是我们有一些集中日志收集工具,比如很热门的ELK,我们需要把这些日志串联起来,这是一个很关键的问题,如果没有串联起来,查询起来很是很困难,我们的做法是在开始请求系统时生成一个全局唯一的id,这个id伴随这整个请求的调用周期,即当一个服务调用另外一个服务的时候,会往下传递,形成一条链路,当我们查看日志时,只需要搜索这个id,整条链路的日志都可以查出来了。
现在以dubbo微服务架构为背景,举个栗子:
A -> B -> C
我们需要将A/B/C/三个微服务间的日志按照链式打印,我们都知道Dubbo的RpcContext只能做到消费者和提供者共享同一个RpcContext,比如A->B,那么A和B都可以获取相同内容的RpcContext,但是B->C时,A和C就无法共享相同内容的RpcContext了,也就是无法做到链式打印日志了。
那么我们是如何做到呢?
我们可以用左手交换右手的思路来解决,假设左手是线程的ThreadLocal,右手是RpcContext,那么在交换之前,我们首先将必要的日志信息保存到ThreadLocal中。
在我们的项目微服务中大致分为两种容器类型的微服务,一种是Dubbo容器,这种容器的特点是只使用spring容器启动,然后使用dubbo进行服务的暴露,然后将服务注册到zookeeper,提供服务给消费者;另一种是SpringMVC容器,也即是我们常见的WEB容器,它是我们项目唯一可以对外开放接口的容器,也是充当项目的网关功能。
在了解了微服务容器之后,我们现在知道了调用链的第一层一定是在SpringMVC容器层中,那么我们直接在这层写个自定义拦截器就ojbk了,talk is cheap,show you the demo code:
举例一个Demo代码,公共拦截器的前置拦截中代码如下:
public class CommonInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler)
throws Exception {
// ...
// 初始化全局的Context容器
Request request = initRequest(httpServletRequest);
// 新建一个全局唯一的请求traceId,并set进request中
request.setTraceId(JrnGenerator.genTraceId());
// 将初始化的请求信息放进ThreadLocal中
Context.initialLocal(request);
// ...
return true;
}
// ...
}
系统内部上下文对象:
public class Context {
// ...
private static final ThreadLocal<Request> REQUEST_LOCAL = new ThreadLocal<>();
public final static void initialLocal(Request request) {
if (null == request) {
return;
}
REQUEST_LOCAL.set(request);
}
public static Request getCurrentRequest() {
return REQUEST_LOCAL.get();
}
// ...
}
拦截器实现了org.springframework.web.servlet.HandlerInterceptor
接口,它的主要作用是用于拦截处理请求,可以在MVC层做一些日志记录与权限检查等操作,这相当于MVC层的AOP,即符合横切关注点的所有功能都可以放入拦截器实现。
这里的initRequest(httpServletRequest);
就是将请求信息封装成系统内容的请求对象Request
,并初始化一个全局唯一的traceId放进Request中,然后再把它放进系统内部上下文ThreadLocal字段中。
接下来讲讲如何将ThreadLocal中的内容放到RpcContext中,在讲之前,我先来说说Dubbo基于spi扩展机制,官方文档对拦截器扩展解释如下:
服务提供方和服务消费方调用过程拦截,Dubbo 本身的大多功能均基于此扩展点实现,每次远程方法执行,该拦截都会被执行,请注意对性能的影响。
也就是说我们进行服务远程调用前,拦截器会对此调用进行拦截处理,那么就好办了,在消费者调用远程服务之前,我们可以偷偷把ThreadLocal的内容放进RpcContext容器中,我们可以基于dubbo的spi机制扩展两个拦截器,一个在消费者端生效,另一个在提供者端生效:
在META-INF中加入com.alibaba.dubbo.rpc.Filter文件,内容如下:
provider=com.objcoding.dubbo.filter.ProviderFilter
consumer=com.objcoding.dubbo.filter.ConsumerFilter
消费者端拦截处理:
public class ConsumerFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation)
throws RpcException {
//1.从ThreadLocal获取请求信息
Request request = Context.getCurrentRequest();
//2.将Context参数放到RpcContext
RpcContext rpcCTX = RpcContext.getContext();
// 将初始化的请求信息放进ThreadLocal中
Context.initialLocal(request);
// ...
}
}
Context.getCurrentRequest();
就是从ThreadLocal中拿到Request请求内容,contextToDubboContext(request);
将Request内容放进当前线程的RpcContext容器中。
很容易联想到提供者也就是把RpcContext中的内容拿出来放到ThreadLocal中:
public class ProviderFilter extends AbstractDubboFilter implements Filter{
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation)
throws RpcException {
// 1.获取RPC远程调用上下文
RpcContext rpcCTX = RpcContext.getContext();
// 2.初始化请求信息
Request request = dubboContextToContext(rpcCTX);
// 3.将初始化的请求信息放进ThreadLocal中
Context.initialLocal(request);
// ...
}
}
接下来我们还要配置log4j2,使得我们同一条请求在关联的每一个容器打印的消息,都有一个共同的traceId,那么我们在ELK想要查询某个请求时,只需要搜索traceId,就可以看到整条请求链路的日志了。
我们在Context上下文对象的initialLocal(Request request)
方法中在log4j2的上下文中添加traceId信息:
public class Context {
// ...
final public static String TRACEID = "_traceid";
public final static void initialLocal(Request request) {
if (null == request) {
return;
}
// 在log4j2的上下文中添加traceId
ThreadContext.put(TRACEID, request.getTraceId());
REQUEST_LOCAL.set(request);
}
// ...
}
接下来实现org.apache.logging.log4j.core.appender.rewrite.RewritePolicy
:
@Plugin(name = "Rewrite", category = "Core", elementType = "rewritePolicy", printObject = true)
public final class MyRewritePolicy implements RewritePolicy {
// ...
@Override
public LogEvent rewrite(final LogEvent source) {
HashMap<String, String> contextMap = Maps.newHashMap(source.getContextMap());
contextMap.put(Context.TRACEID, contextMap.containsKey(Context.TRACEID) ? contextMap.get(Context.TRACEID) : NULL);
return new Log4jLogEvent.Builder(source).setContextMap(contextMap).build();
}
// ...
}
RewritePolicy的作用是我们每次输出日志,log4j都会调用这个类进行一些处理的操作。
配置log4j2.xml:
<Configuration status="warn">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout
pattern="[%d{yyyy/MM/dd HH:mm:ss,SSS}][${ctx:_traceid}]%m%n" />
</Console>
<!--定义一个Rewrite-->
<Rewrite name="Rewrite">
<MyRewritePolicy/>
<!--引用输出模板-->
<AppenderRef ref="Console"/>
</Rewrite>
</Appenders>
<Loggers>
<!--使用日志模板-->
<Logger name="com.objcoding.MyLogger" level="debug" additivity="false">
<!--引用Rewrite-->
<AppenderRef ref="Rewrite"/>
</Logger>
</Loggers>
</Configuration>
自定义日志类:
public class MyLogger {
private static final Logger logger = LoggerFactory.getLogger(MyLogger.class);
public static void info(String msg, Object... args) {
if (canLog() == 1 && logger.isInfoEnabled()) {
logger.info(msg, args);
}
}
public static void debug(String message, Object... args) {
if (canLog() == 1 && logger.isDebugEnabled()) {
logger.debug(message, args);
}
}
// ..
}
更多精彩文章请关注作者维护的公众号「后端进阶」,这是一个专注后端相关技术的公众号。
关注公众号并回复「后端」免费领取后端相关电子书籍。
欢迎分享,转载请保留出处。
Dubbo 全链路追踪日志的实现的更多相关文章
- 基于SLF4J的MDC机制和Dubbo的Filter机制,实现分布式系统的日志全链路追踪
原文链接:基于SLF4J的MDC机制和Dubbo的Filter机制,实现分布式系统的日志全链路追踪 一.日志系统 1.日志框架 在每个系统应用中,我们都会使用日志系统,主要是为了记录必要的信息和方便排 ...
- Node.js 应用全链路追踪技术——[全链路信息获取]
全链路追踪技术的两个核心要素分别是 全链路信息获取 和 全链路信息存储展示. Node.js 应用也不例外,这里将分成两篇文章进行介绍:第一篇介绍 Node.js 应用全链路信息获取, 第二篇介绍 N ...
- 全链路追踪traceId,ThreadLocal与ExecutorService
关于全链路追踪traceId遇到线程池的问题,做过架构的估计都遇到过,现在以写个demo,总体思想就是获取父线程traceId,给子线程,子线程用完移除掉. mac上的chrome时不时崩溃,写了一大 ...
- go微服务框架kratos学习笔记九(kratos 全链路追踪 zipkin)
目录 go微服务框架kratos学习笔记九(kratos 全链路追踪 zipkin) zipkin使用demo 数据持久化 go微服务框架kratos学习笔记九(kratos 全链路追踪 zipkin ...
- skywalking与pinpoint全链路追踪方案对比
由于公司目前有200多微服务,微服务之间的调用关系错综复杂,调用关系人工维护基本不可能实现,需要调研一套全链路追踪方案,初步调研之后选取了skywalking和pinpoint进行对比; 选取skyw ...
- 【AWS】使用X-Ray做AWS云上全链路追踪监控系统
功能 AWS X-Ray 是一项服务,收集应用程序所请求的相关数据,并提供用于查看.筛选和获取数据洞察力的工具,以确定问题和发现优化的机会. 对于任何被跟踪的对您应用程序的请求,不仅可以查看请求和响应 ...
- Spring Cloud全链路追踪实现(Sleuth+Zipkin+RabbitMQ+ES+Kibana)
简介 在微服务架构下存在多个服务之间的相互调用,当某个请求变慢或不可用时,我们如何快速定位服务故障点呢?链路追踪的实现就是为了解决这一问题,本文采用Sleuth+Zipkin+RabbitMQ+ES+ ...
- Spring Cloud 全链路追踪实现
简介 在微服务架构下存在多个服务之间的相互调用,当某个请求变慢或不可用时,我们如何快速定位服务故障点呢?链路追踪的实现就是为了解决这一问题,本文采用Sleuth+Zipkin+RabbitMQ+ES+ ...
- 全链路追踪技术选型:pinpoint vs skywalking
目前分布式链路追踪系统基本都是根据谷歌的<Dapper大规模分布式系统的跟踪系统>这篇论文发展而来,主流的有zipkin,pinpoint,skywalking,cat,jaeger等. ...
随机推荐
- 04-numpy读取本地数据和索引
1.numpy读取数据 CSV:Comma-Separated Value,逗号分隔值文件 显示:表格状态 源文件:换行和逗号分隔行列的格式化文本,每一行的数据表示一条记录 由于csv便于展示,读取和 ...
- Scala和Java混合项目搭建:(Eclipse)
Scala和Java混合项目搭建:(Eclipse) 项目结构: pom.xml: <project xmlns="http://maven.apache.org/POM/4.0. ...
- 确认自己所用的python版本
总结: 目前有两个版本的python处于活跃状态:python2,python3 有多种流行的python运行环境:cpython(应用最广泛的python解释器,如无对解释器有要求,一般用这个,默认 ...
- JavaScript设计模式——原型模式
原型模式: 原型模式是指原型实例指向创建对象的种类,并通过拷贝这些原型创建新的对象,是一种用来创建对象的模式,也就是创建一个对象作为另一个对象的prototype属性: prototype警告:学习了 ...
- 【TencentOS tiny】又有一个操作系统开源
新闻 2019年9月18日,腾讯宣布将开源 自主研发的轻量级物联网实时操作系统TencentOS tiny.相比市场上其它系统,腾讯TencentOS tiny在资源占用.设备成本.功耗管理以及安全稳 ...
- Android Studio:多包名打包
来自:http://m.blog.csdn.net/u011315960/article/details/73251196 前言 最近有点小忙,博客都落下了,今天赶紧写点东西补上. 前几天商务找我,想 ...
- POJ1017&&UVA311 Packets(中文题面版)
感谢有道翻译--- Description A工厂生产的产品是用相同高度h的方形包装,尺寸为1* 1,2 * 2,3 * 3,4 * 4,5 * 5,6 6.这些产品总是以与产品高度h相同,尺寸为66 ...
- jQuery v1.10.2如何判断checkbox(复选框)是否被选中
做项目时,我们经常会用到jquery来做一些判断,今天自己遇上判断复选框是否选中,然后搜索查看,发现现在网上的都是错误的,下面罗列错误的: 1.$("#id").attr(&quo ...
- C++11多线程相关
有关线程的知识,从C++11开始有了一些变化,作为初学者,对其先有个初步认识,后面用到的时候再详细剖析
- Bran的内核开发教程(bkerndev)-05 打印到屏幕
打印到屏幕 现在, 我们需要尝试打印到屏幕上.为此, 我们需要管理屏幕滚动, 如果能允许使用不同的颜色就更好了.好在VGA视频卡为我们提供了一片内存空间, 允许同时写入属性字节和字符字节对, 可以 ...