很久没更新博客了。 这几天做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 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 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。大家可以自行百度,我这里就不写了,就是在我们现有的基础上再搞点东西,有兴趣的可以玩玩。我这里就是傻瓜式记录一下,方便笔者快速实现。

走过路过点个收藏。

精彩内容

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: