微服务架构的项目,一次请求可能会调用多个微服务,这样就会产生多个微服务的请求日志,当我们想要查看整个请求链路的日志时,就会变得困难,所幸的是我们有一些集中日志收集工具,比如很热门的ELK,我们需要把这些日志串联起来,这是一个很关键的问题,如果没有串联起来,查询起来很是很困难,我们的做法是在开始请求系统时生成一个全局唯一的id,这个id伴随这整个请求的调用周期,即当一个服务调用另外一个服务的时候,会往下传递,形成一条链路,当我们查看日志时,只需要搜索这个id,整条链路的日志都可以查出来了。

现在以dubbo微服务架构为背景,举个栗子:

  1. 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代码,公共拦截器的前置拦截中代码如下:

  1. public class CommonInterceptor implements HandlerInterceptor {
  2. @Override
  3. public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler)
  4. throws Exception {
  5. // ...
  6. // 初始化全局的Context容器
  7. Request request = initRequest(httpServletRequest);
  8. // 新建一个全局唯一的请求traceId,并set进request中
  9. request.setTraceId(JrnGenerator.genTraceId());
  10. // 将初始化的请求信息放进ThreadLocal中
  11. Context.initialLocal(request);
  12. // ...
  13. return true;
  14. }
  15. // ...
  16. }

系统内部上下文对象:

  1. public class Context {
  2. // ...
  3. private static final ThreadLocal<Request> REQUEST_LOCAL = new ThreadLocal<>();
  4. public final static void initialLocal(Request request) {
  5. if (null == request) {
  6. return;
  7. }
  8. REQUEST_LOCAL.set(request);
  9. }
  10. public static Request getCurrentRequest() {
  11. return REQUEST_LOCAL.get();
  12. }
  13. // ...
  14. }

拦截器实现了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文件,内容如下:

  1. provider=com.objcoding.dubbo.filter.ProviderFilter
  2. consumer=com.objcoding.dubbo.filter.ConsumerFilter

消费者端拦截处理:


  1. public class ConsumerFilter implements Filter {
  2. @Override
  3. public Result invoke(Invoker<?> invoker, Invocation invocation)
  4. throws RpcException {
  5. //1.从ThreadLocal获取请求信息
  6. Request request = Context.getCurrentRequest();
  7. //2.将Context参数放到RpcContext
  8. RpcContext rpcCTX = RpcContext.getContext();
  9. // 将初始化的请求信息放进ThreadLocal中
  10. Context.initialLocal(request);
  11. // ...
  12. }
  13. }

Context.getCurrentRequest();就是从ThreadLocal中拿到Request请求内容,contextToDubboContext(request);将Request内容放进当前线程的RpcContext容器中。

很容易联想到提供者也就是把RpcContext中的内容拿出来放到ThreadLocal中:

  1. public class ProviderFilter extends AbstractDubboFilter implements Filter{
  2. @Override
  3. public Result invoke(Invoker<?> invoker, Invocation invocation)
  4. throws RpcException {
  5. // 1.获取RPC远程调用上下文
  6. RpcContext rpcCTX = RpcContext.getContext();
  7. // 2.初始化请求信息
  8. Request request = dubboContextToContext(rpcCTX);
  9. // 3.将初始化的请求信息放进ThreadLocal中
  10. Context.initialLocal(request);
  11. // ...
  12. }
  13. }

接下来我们还要配置log4j2,使得我们同一条请求在关联的每一个容器打印的消息,都有一个共同的traceId,那么我们在ELK想要查询某个请求时,只需要搜索traceId,就可以看到整条请求链路的日志了。

我们在Context上下文对象的initialLocal(Request request)方法中在log4j2的上下文中添加traceId信息:

  1. public class Context {
  2. // ...
  3. final public static String TRACEID = "_traceid";
  4. public final static void initialLocal(Request request) {
  5. if (null == request) {
  6. return;
  7. }
  8. // 在log4j2的上下文中添加traceId
  9. ThreadContext.put(TRACEID, request.getTraceId());
  10. REQUEST_LOCAL.set(request);
  11. }
  12. // ...
  13. }

接下来实现org.apache.logging.log4j.core.appender.rewrite.RewritePolicy

  1. @Plugin(name = "Rewrite", category = "Core", elementType = "rewritePolicy", printObject = true)
  2. public final class MyRewritePolicy implements RewritePolicy {
  3. // ...
  4. @Override
  5. public LogEvent rewrite(final LogEvent source) {
  6. HashMap<String, String> contextMap = Maps.newHashMap(source.getContextMap());
  7. contextMap.put(Context.TRACEID, contextMap.containsKey(Context.TRACEID) ? contextMap.get(Context.TRACEID) : NULL);
  8. return new Log4jLogEvent.Builder(source).setContextMap(contextMap).build();
  9. }
  10. // ...
  11. }

RewritePolicy的作用是我们每次输出日志,log4j都会调用这个类进行一些处理的操作。

配置log4j2.xml:

  1. <Configuration status="warn">
  2. <Appenders>
  3. <Console name="Console" target="SYSTEM_OUT">
  4. <PatternLayout
  5. pattern="[%d{yyyy/MM/dd HH:mm:ss,SSS}][${ctx:_traceid}]%m%n" />
  6. </Console>
  7. <!--定义一个Rewrite-->
  8. <Rewrite name="Rewrite">
  9. <MyRewritePolicy/>
  10. <!--引用输出模板-->
  11. <AppenderRef ref="Console"/>
  12. </Rewrite>
  13. </Appenders>
  14. <Loggers>
  15. <!--使用日志模板-->
  16. <Logger name="com.objcoding.MyLogger" level="debug" additivity="false">
  17. <!--引用Rewrite-->
  18. <AppenderRef ref="Rewrite"/>
  19. </Logger>
  20. </Loggers>
  21. </Configuration>

自定义日志类:

  1. public class MyLogger {
  2. private static final Logger logger = LoggerFactory.getLogger(MyLogger.class);
  3. public static void info(String msg, Object... args) {
  4. if (canLog() == 1 && logger.isInfoEnabled()) {
  5. logger.info(msg, args);
  6. }
  7. }
  8. public static void debug(String message, Object... args) {
  9. if (canLog() == 1 && logger.isDebugEnabled()) {
  10. logger.debug(message, args);
  11. }
  12. }
  13. // ..
  14. }

更多精彩文章请关注作者维护的公众号「后端进阶」,这是一个专注后端相关技术的公众号。

关注公众号并回复「后端」免费领取后端相关电子书籍。

欢迎分享,转载请保留出处。

Dubbo 全链路追踪日志的实现的更多相关文章

  1. 基于SLF4J的MDC机制和Dubbo的Filter机制,实现分布式系统的日志全链路追踪

    原文链接:基于SLF4J的MDC机制和Dubbo的Filter机制,实现分布式系统的日志全链路追踪 一.日志系统 1.日志框架 在每个系统应用中,我们都会使用日志系统,主要是为了记录必要的信息和方便排 ...

  2. Node.js 应用全链路追踪技术——[全链路信息获取]

    全链路追踪技术的两个核心要素分别是 全链路信息获取 和 全链路信息存储展示. Node.js 应用也不例外,这里将分成两篇文章进行介绍:第一篇介绍 Node.js 应用全链路信息获取, 第二篇介绍 N ...

  3. 全链路追踪traceId,ThreadLocal与ExecutorService

    关于全链路追踪traceId遇到线程池的问题,做过架构的估计都遇到过,现在以写个demo,总体思想就是获取父线程traceId,给子线程,子线程用完移除掉. mac上的chrome时不时崩溃,写了一大 ...

  4. go微服务框架kratos学习笔记九(kratos 全链路追踪 zipkin)

    目录 go微服务框架kratos学习笔记九(kratos 全链路追踪 zipkin) zipkin使用demo 数据持久化 go微服务框架kratos学习笔记九(kratos 全链路追踪 zipkin ...

  5. skywalking与pinpoint全链路追踪方案对比

    由于公司目前有200多微服务,微服务之间的调用关系错综复杂,调用关系人工维护基本不可能实现,需要调研一套全链路追踪方案,初步调研之后选取了skywalking和pinpoint进行对比; 选取skyw ...

  6. 【AWS】使用X-Ray做AWS云上全链路追踪监控系统

    功能 AWS X-Ray 是一项服务,收集应用程序所请求的相关数据,并提供用于查看.筛选和获取数据洞察力的工具,以确定问题和发现优化的机会. 对于任何被跟踪的对您应用程序的请求,不仅可以查看请求和响应 ...

  7. Spring Cloud全链路追踪实现(Sleuth+Zipkin+RabbitMQ+ES+Kibana)

    简介 在微服务架构下存在多个服务之间的相互调用,当某个请求变慢或不可用时,我们如何快速定位服务故障点呢?链路追踪的实现就是为了解决这一问题,本文采用Sleuth+Zipkin+RabbitMQ+ES+ ...

  8. Spring Cloud 全链路追踪实现

    简介 在微服务架构下存在多个服务之间的相互调用,当某个请求变慢或不可用时,我们如何快速定位服务故障点呢?链路追踪的实现就是为了解决这一问题,本文采用Sleuth+Zipkin+RabbitMQ+ES+ ...

  9. 全链路追踪技术选型:pinpoint vs skywalking

    目前分布式链路追踪系统基本都是根据谷歌的<Dapper大规模分布式系统的跟踪系统>这篇论文发展而来,主流的有zipkin,pinpoint,skywalking,cat,jaeger等. ...

随机推荐

  1. Shell之Xargs命令

    目录 Shell之Xargs命令 参考 xargs命令简介 xargs命令格式 xargs实例说明 Shell之Xargs命令

  2. _self.$scopedSlots.default is not a function报错

    问题: 当同一页面有elementUI的多个table表格时,如果用到v-if来动态展示表格,切换时出现如下报错: 原因: 是因为表格是element-ui通过循环产生的,而vue在dom重新渲染时有 ...

  3. LitePal的聚合函数

    传统的聚合函数用法   虽说是聚合函数,但它的用法其实和传统的查询还是差不多的,即仍然使用的是select语句.但是在select语句当中我们通常不会再去指定列名,而是将需要统计的列名传入到聚合函数当 ...

  4. display——table-cell属性

    display的table和table-cell一般情况下用的不多,所以很少有人去关注它,但他们两个联手起来会给你惊喜! 当两个或者两个以上标签一起使用显示在同一行时,以前常用的是float.posi ...

  5. SQL SERVER数据库多having 用法

    举实例:查询大于500的数据,并按时间进行汇总排序 select  CONVERT(VARCHAR(10),DGH,23),COUNT(*) from  yxhis2017..VTBMZGHMX201 ...

  6. bugku 很普通的数独

    下载下是一个没有后缀的文件,使用winhex打开,头文件为50 4b 03 为zip文件,修改后缀,打开压缩包,是一大堆数独图片. 仔细看了好久,发现这几张图片像二维码,而且1 5 21这三张图的位置 ...

  7. 引入flask_cache时出现ModuleNotFoundError: No module named 'flask.ext'

    环境: centos 7.3 python 3.6 flask 1.0.2 flask-cache 0.13.1 引入flask_cache后运行时,出现以下错误 Traceback (most re ...

  8. Java之微信公众号开发

    这次以文本回复作为案例来讲解Java相关得微信公众号开发. 首先必须要有一个个人微信公众号 个人微信公众号相关的接口权限有限,不过用于个人学习体验一下足够了,如图: 然后进入微信公众后台,点击基本配置 ...

  9. 绕过CDN方法整理

    来自文章链接:https://zhuanlan.zhihu.com/p/33440472 0x01 判断ip是否为网站真实ip 1. Nslookup: Win下使用nslookup命令进行查询,若返 ...

  10. JSON:JSON对象和JSON数组混排的复杂字符串

    在java中的一个好用的JSON工具包:net.sf.json.JSONObject 和 net.sf.json.JSONArray 一 解析JSON对象和JSON数组类型混排的复杂字符串 举个例子: ...