大家好,我是悟空。前言最近在搭建一个基础版的项目框架,基于SpringCloud微服务框架。如果把SpringCloud的框架看成1,那么现有的swagger/logback等基础组件就是0.5,然后我就在这个1.5的基础上组装完成一个微服务项目框架。为什么要打造二代轮毂?市面上现成的项目框架不好吗?因为项目组不允许使用外部现成的框架,比如若意。另外,因为我们的项目需求有自己的特点,技术选型也会选择我们熟悉的框架,所以自己造二代轮子也是一个不错的选择。核心功能需要包含以下几个核心功能:拆分多个微服务模块,提取一个demo微服务模块进行扩展,完成核心框架模块的提取,完成注册中心Eureka,完成远程调用OpenFeign,完成日志logback,包括traceId跟踪,完成SwaggerAPI文档,完成配置文件共享,完成日志检索,ELKStack,完成自定义Starter,pending集成缓存Redis,Redissentinel高可用,完成集成数据库MySQL,MySQL高可用,MyBatis的集成-Plus已完成,链路跟踪组件已完成,监控待定,工具待定,网关待开发,技术选型待定,审计日志进入ES,分布式文件系统待定,计划任务待定待定等。介绍的内容是关于日志链接跟踪。1.痛点痛点1:进程中多条日志无法追踪到一个请求调用。假设会调用后端的十几个方法,打印十几次日志。这些日志不能串联。如下图:客户端调用订单服务,方法A调用订单服务中的方法B,方法B调用方法C。方法A打印第一条日志和第五条日志,方法B打印第二条日志,以及方法C打印了第三条日志和第四条日志,但是这5条日志之间没有任何联系,唯一的联系就是时间是按时间顺序打印的,但是如果有其他并发的请求调用,会干扰到连续性日志。痛点二:如何关联跨服务日志每个微服务都会记录自己的进程日志,如何关联跨进程日志?如下图所示:订单服务和优惠券服务属于两个微服务,分别部署在两台机器上。订单服务方法A远程调用优惠券服务方法D。方法A打印日志到日志文件1,记录5条日志,方法D打印日志到日志文件2,记录5条日志。但是这10条日志不能关联。痛点三:跨线程日志如何关联主线程和子线程日志?如下图:主线程的方法A启动一个子线程,子线程执行方法E,方法A打印第一条日志,子线程E打印第二条和第三条日志。痛点四:如何追踪调用我们服务的第三方?本文要解决的核心问题是第一个和第二个问题。多线程还没有引入,目前也没有第三方调用。第三个和第四个问题后面会优化。2.解决方案1.1解决方案①使用SkywalkingtraceId进行链路跟踪②使用ElasticAPM的traceId进行链路跟踪③MDC方案:自己生成traceId放入MDC。在项目前期,不要过多引入中间件,先尝试简单可行的方案,所以这里采用第三种方案MDC。1.2MDC方案MDC(MappedDiagnosticContext)用于存储运行上下文的特定线程的上下文数据。因此,如果您使用log4j进行日志记录,则每个线程都可以拥有自己的MDC,该MDC对整个线程都是全局的。属于该线程的任何代码都可以轻松访问存在于该线程的MDC中的值。3.原理与实践2.1跟踪一个请求的多个日志我们先来看第一个痛点,如何在一个请求中连接多个日志。该方案的原理如下图所示:(1)在logback日志配置文件中的日志格式中加入%X{traceId}配置。%d{yyyy-MM-ddHH:mm:ss.SSS}[%thread]%X{traceId}%-5level%logger-%msg%n(2)自定义拦截器,获取请求头中的traceId,如果存在则放入MDC,否则直接使用UUID作为traceId,放入MDC。(3)配置拦截器。我们在打印日志的时候,会自动打印traceId,如下图,多条日志的traceId是一样的。示例代码拦截器代码:/***@authorwww.passjava.cn,公众号:WukongChatArchitecture*@date2022-07-05*/@ServicepublicclassLogInterceptorextendsHandlerInterceptorAdapter{privatestaticfinalStringTRACE_ID="traceId";@OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{StringtraceId=request.getHeader(TRACE_ID);if(StringUtils.isEmpty(traceId)){MDC.put("traceId",UUID.randomUUID().toString());}else{MDC.put(TRACE_ID,traceId);}返回真;}@OverridepublicvoidpostHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,ModelAndViewmodelAndView)throwsException{//防止内存泄漏MDC.remove("traceId");}}配置拦截器:/***@authorwww.passjava.cn,公众号:悟空聊天架构*@date2022-07-05*/@ConfigurationpublicclassInterceptorConfigimplementsWebMvcConfigurer{@ResourceprivateLogInt感受器日志拦截器;@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){registry.addInterceptor(logInterceptor).addPathPatterns("/**");}}2.2跨服务跟踪多个日志的方案示意图如下:orderserviceremote要调用优惠券服务,需要在orderservice中添加OpenFeign拦截器。拦截器做的就是在请求的header中添加traceId,这样调用优惠券服务的时候,就可以从header中获取请求。traceId代码如下:/***@authorwww.passjava.cn,公众号:WukongChatArchitecture*@date2022-07-05*/@ConfigurationpublicclassFeignInterceptorimplementsRequestInterceptor{privatestaticfinalStringTRACE_ID="跟踪标识";@Overridepublicvoidapply(RequestTemplaterequestTemplate){requestTemplate.header(TRACE_ID,(String)MDC.get(TRACE_ID));}}在两个微服务打印的日志中,两个日志的traceId是相同的。当然,这些日志会被导入到Elasticsearch中,然后通过kibana可视化界面搜索traceId,整个调用链路就可以接通了!4.小结本文通过拦截器和MDC功能,在整个链接中添加traceId,然后将traceId输出到日志中,从而可以通过日志追溯调用链接。无论是进程内的方法级调用,还是跨进程的服务调用,都可以追溯。另外,需要通过ELKStack技术将日志导入到Elasticsearch中,然后通过检索traceId来获取整个调用链路。