dubbo分布式服务实现日志链路追踪traceid

很久没更新博客了。
这几天做traceid的时候,发现网上博客很多,但是大多都有一些点没讲很清楚,特别是SPI这块。这里写了一段亲测可用的代码贴出来让大家使用。兄弟们用完之后点歌收藏,点个赞啥的。也是我更新干货的动力。

开发排查系统问题用得最多的手段就是查看系统日志,但是在分布式环境下使用日志定位问题还是比较麻烦,需要借助 全链路追踪ID 把上下文串联起来,使用这种技术后,从前端的返回头中就可以确认某一条需要查错的请求的追踪id,以此为标识在海量日志中找到整个请求的调用链路,避免掉相同方法请求日志信息的影响,特别是高并发场景下尤为重要,能够快速的了解到调用信息的变化及准确的报错情况,很好的帮助开发解决问题。下面我们直接上干货

	第一步,在消费者服务中创建一个普通http过滤器
	/**
  • @author zhuxinji

  • @date 2021-12-29-10:54

  • @DESC
    */
    @Slf4j
    @WebFilter(urlPatterns = “/**”, filterName = “logTraceHttpFilter”)
    @Order(1)
    @Component
    public class LogTraceHttpFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) {
    // TODO Auto-generated method stub
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    try {
    // 这个traceId也可以考虑从Http请求里面取,如果是null则默认用uuid
    HttpServletRequest request = (HttpServletRequest) servletRequest;
    HttpServletResponse response = (HttpServletResponse) servletResponse;
    String traceId = TraceUtil.initMDCWithHttpFilter(getTraceId(request));
    // 响应头里面添加一个traceId,方便f12debug调试;本来是每个ajax都由前端生成traceId,由于改动成本太高,就变成响应输出traceId
    response.setHeader(TraceConstants.TRACE_ID, traceId);
    if (log.isInfoEnabled()) {
    log.info(“web端接收http请求[{}]----start”, request.getRequestURI());
    }
    filterChain.doFilter(request, response);
    } finally {
    // 最后清除掉MDC内容
    TraceUtil.clearMDC();
    }
    }

    /**

    • description: 由于考虑到和其他系统对接时,可能会用其他系统传递进来参数作为traceId
    • 因此预留此方法方便后续Override
    • @param servletRequest
    • @return
      */
      public String getTraceId(ServletRequest servletRequest) {
      return null;
      }

}

第2步,创建一个消费者的过滤器,注意这个Fiter的包是dubbo的,跟第一步那个fiter不是一个包。这里我们要用到dubbo的SPI机制,此处重写了dubbo的filter方法
/**

  • @author zhuxinji
  • @date 2021-12-29-11:00
  • @DESC
    */
    @Slf4j
    @Activate(group = {Constants.CONSUMER_PROTOCOL})
    public class LogTraceConsumerFilter implements Filter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    // 消费者负责把调用链端的MDC信息传递到生产者,traceid如果不是被调用时是空的,例如调度任务,此时我们重新生成一个方便BIZ查询
    String traceId = StringUtils.isNotBlank(MDC.get(TraceConstants.TRACE_ID)) ? MDC.get(TraceConstants.TRACE_ID) : UUID.randomUUID().toString();
    String spanId = StringUtils.isNotBlank(MDC.get(TraceConstants.SPAN_ID)) ? MDC.get(TraceConstants.TRACE_ID) : UUID.randomUUID().toString();
    String logicId = MDC.get(TraceConstants.LOGIC_ID);
    if (StringUtils.isNotBlank(traceId) && StringUtils.isNotBlank(spanId) && StringUtils.isNotBlank(logicId)) {
    // 逻辑Id+1
    String newLogicId = TraceUtil.incrLogicId(logicId);
    MDC.put(TraceConstants.LOGIC_ID, newLogicId);
    // 传递traceId spanId logicId
    Map<String, String> attachments = invocation.getAttachments();
    attachments.put(TraceConstants.TRACE_ID, traceId);
    attachments.put(TraceConstants.SPAN_ID, spanId);
    attachments.put(TraceConstants.LOGIC_ID, newLogicId);
    }
    return invoker.invoke(invocation);
    }
    }

第三步,在服务这种创建如第2步一样的过滤器,代码如下

/**

  • @author zhuxinji
  • @date 2021-12-29-11:04
  • @DESC
    */
    @Slf4j
    @Activate(group = {Constants.PROVIDER_PROTOCOL})
    public class LogTraceProviderFilter implements Filter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    try {
    // 生产者负责把信息放进MDC里面
    Map<String, String> attachments = invocation.getAttachments();
    String traceId = MapUtils.getString(attachments, TraceConstants.TRACE_ID);
    String spanId = MapUtils.getString(attachments,TraceConstants.SPAN_ID);
    String logicId = MapUtils.getString(attachments,TraceConstants.LOGIC_ID);
    if (StringUtils.isNotBlank(traceId) && StringUtils.isNotBlank(spanId) && StringUtils.isNotBlank(logicId)) {
    // 生成新的spanId
    String newSpanId = TraceUtil.getNewSpanId(spanId, logicId);
    MDC.put(TraceConstants.TRACE_ID, traceId);
    MDC.put(TraceConstants.SPAN_ID, newSpanId);
    MDC.put(TraceConstants.LOGIC_ID, “0”);
    }
    if (log.isInfoEnabled()) {
    log.info(“dubbo服务执行dubbo方法{}.{}”, invoker.getInterface().getName(), invocation.getMethodName());
    }
    Result result = invoker.invoke(invocation);
    return result;
    } finally {
    TraceUtil.clearMDC();
    }
    }
    }

第4步,在服务者中resource路径下创建一个这样路径和名称的文件夹和文件
注意了哦,这里文件夹要一级一级的创建,不要图快一次写入全路径
在这里插入图片描述
内容为这样子的,路径你们自己替换成自己的
logTraceProviderFilter=LogTraceProviderFilter的路径

同第4步,在消费者中也创建一个这样的文件夹及文件,把内容对应改成消费者过滤器的名字。

然后下一步配置logBack的文件
这个要看你项目在哪里配置的,我就是在resource下面配置的XML,这是比较多的配置方式
在这里插入图片描述

日志输出格式的内容可以自定义,我这里复制一个给大家

到这里一个完整的分布式链路追踪日志就好了。
至于原理,就是使用了MDC和dubbo的SPI。MDC又是使用了threadLocal。
这里是比较简单的实现了,再复杂点还需要在子线程上加上,那样可能就要重新下threadLocal。大家可以自行百度,我这里就不写了,就是在我们现有的基础上再搞点东西,有兴趣的可以玩玩。我这里就是傻瓜式记录一下,方便笔者快速实现。

走过路过点个收藏。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值