SpringCloud通过MDC实现分布式链路追踪
作者:YB_account
在DDD领域驱动设计中,我们使用SpringCloud来去实现,但排查错误的时候,通常会想到Skywalking,但是引入一个新的服务,增加了系统消耗和管理学习成本,对于大型项目比较适合,但是小的项目显得太过臃肿了,所以本文介绍了SpringCloud通过MDC实现分布式链路追踪
引言
在DDD领域驱动设计中,我们使用SpringCloud来去实现,但排查错误的时候,通常会想到Skywalking,但是引入一个新的服务,增加了系统消耗和管理学习成本,对于大型项目比较适合,但是小的项目显得太过臃肿了,我们此时就可以使用TraceId,将其存放到MDC中,返回的时候参数带上它,访问的时候日志打印出来,每次访问生成的TraceId不同,这样可以实现分布式链路追踪的问题。
通用部分
封装TraceIdUtil工具类
import org.apache.commons.lang3.StringUtils; import org.slf4j.MDC; import cn.hutool.core.util.IdUtil; public class TraceIdUtil { public static final String TRACE_ID_KEY = "TraceId"; /** * 生成TraceId * @return */ public static String generateTraceId(){ String traceId = IdUtil.fastSimpleUUID().toUpperCase(); MDC.put(TRACE_ID_KEY,traceId); return traceId; } /** * 生成TraceId * @return */ public static String generateTraceId(String traceId){ if(StringUtils.isBlank(traceId)){ return generateTraceId(); } MDC.put(TRACE_ID_KEY,traceId); return traceId; } /** * 获取TraceId * @return */ public static String getTraceId(){ return MDC.get(TRACE_ID_KEY); } /** * 移除TraceId * @return */ public static void removeTraceId(){ MDC.remove(TRACE_ID_KEY); } }
logback.xml日志文件的修改
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [TRACEID:%X{TraceId}] [%thread] %-5level %logger{36} -%msg%n</Pattern>
需注意:
biff 模块
创建过滤器
import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import com.karry.admin.bff.common.util.TraceIdUtil; import cn.hutool.core.util.IdUtil; import lombok.extern.slf4j.Slf4j; @Slf4j @WebFilter public class TraceFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { log.info("Init Trace filter init......."); System.out.println("Init Trace filter init......."); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { try { HttpServletRequest servletRequest = (HttpServletRequest) request; String gateWayTraceId = ((HttpServletRequest) request).getHeader(TraceIdUtil.TRACE_ID_KEY); String traceId = TraceIdUtil.generateTraceId(StringUtils.isEmpty(gateWayTraceId) ? IdUtil.fastSimpleUUID().toUpperCase() : gateWayTraceId ); // 创建新的请求包装器 log.info("TraceIdUtil.getTraceId():"+TraceIdUtil.getTraceId()); //将请求和应答交给下一个处理器处理 filterChain.doFilter(request,response); }catch (Exception e){ e.printStackTrace(); }finally { //最后移除,不然有可能造成内存泄漏 TraceIdUtil.removeTraceId(); } } @Override public void destroy() { log.info("Init Trace filter destroy......."); } }
配置过滤器生效
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import com.karry.admin.bff.common.filter.TraceFilter; import lombok.extern.slf4j.Slf4j; @Slf4j @Configuration public class WebConfiguration { @Bean @ConditionalOnMissingBean({TraceFilter.class}) @Order(Ordered.HIGHEST_PRECEDENCE + 100) public FilterRegistrationBean<TraceFilter> traceFilterBean(){ FilterRegistrationBean<TraceFilter> bean = new FilterRegistrationBean<>(); bean.setFilter(new TraceFilter()); bean.addUrlPatterns("/*"); return bean; } }
figen接口发送的head修改
此处修改了发送的请求的header,在其他模块就可以获取从biff层生成的traceId了。
import org.springframework.context.annotation.Configuration; import com.karry.admin.bff.common.util.TraceIdUtil; import feign.RequestInterceptor; import feign.RequestTemplate; @Configuration public class FeignRequestInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template){ String traceId = TraceIdUtil.getTraceId(); //当前线程调用中有traceId,则将该traceId进行透传 if (traceId != null) { template.header(TraceIdUtil.TRACE_ID_KEY,TraceIdUtil.getTraceId()); } } }
统一返回处理
此种情况时针对BaseResult,,这种统一返回的对象无法直接修改的情况下使用的,如果可以直接修改:
/** * 链路追踪TraceId */ public String traceId = TraceIdUtil.getTraceId();
不可以直接修改就用响应拦截器进行处理:
import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; import com.karry.app.common.utils.TraceIdUtil; import com.karry.order.sdk.utils.BeanCopyUtils; import com.souche.platform.common.model.base.BaseResult; import lombok.SneakyThrows; @ControllerAdvice public class ResponseAdvice implements ResponseBodyAdvice { /** * 开关,如果是true才会调用beforeBodyWrite */ @Override public boolean supports(MethodParameter returnType, Class converterType) { return true; } @SneakyThrows//异常抛出,相当于方法上throw一个异常 @Override public Object beforeBodyWrite(Object object, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { BaseResult result = BeanCopyUtils.copy(object, BaseResult.class); result.setTraceId(TraceIdUtil.getTraceId()); return result; } }
非biff模块
创建过滤器
import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import com.karry.app.common.utils.TraceIdUtil; import cn.hutool.core.util.IdUtil; import lombok.extern.slf4j.Slf4j; @Slf4j @WebFilter public class TraceFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { log.info("Init Trace filter init......."); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { try { HttpServletRequest servletRequest = (HttpServletRequest) request; String gateWayTraceId = ((HttpServletRequest) request).getHeader(TraceIdUtil.TRACE_ID_KEY); String traceId = TraceIdUtil.generateTraceId(StringUtils.isEmpty(gateWayTraceId) ? IdUtil.fastSimpleUUID().toUpperCase() : gateWayTraceId ); //将请求和应答交给下一个处理器处理 filterChain.doFilter(request,response); }catch (Exception e){ e.printStackTrace(); }finally { //最后移除,不然有可能造成内存泄漏 TraceIdUtil.removeTraceId(); } } @Override public void destroy() { log.info("Init Trace filter destroy......."); } }
配置过滤器生效
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import com.karry.admin.bff.common.filter.TraceFilter; import lombok.extern.slf4j.Slf4j; @Slf4j @Configuration public class WebConfiguration { @Bean @ConditionalOnMissingBean({TraceFilter.class}) @Order(Ordered.HIGHEST_PRECEDENCE + 100) public FilterRegistrationBean<TraceFilter> traceFilterBean(){ FilterRegistrationBean<TraceFilter> bean = new FilterRegistrationBean<>(); bean.setFilter(new TraceFilter()); bean.addUrlPatterns("/*"); return bean; } }
线程池
上面对于单线程的情况可以进行解决,traceId和Threadlocal很像,是键值对模式,会有内存溢出问题,还是线程私有的。 所以在多线程的情况下就不能获取主线程的traceId了。我们就需要设置线程工厂包装 Runnable 来解决这个问题。
import org.slf4j.MDC; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @Configuration public class MyThreadPoolConfig { @Bean public ThreadPoolExecutor threadPoolExecutor() { // 自定义 ThreadFactory ThreadFactory threadFactory = new ThreadFactory() { private final ThreadFactory defaultFactory = Executors.defaultThreadFactory(); private final String namePrefix = "Async---"; @Override public Thread newThread(Runnable r) { // 获取主线程的 MDC 上下文 Map<String, String> contextMap = MDC.getCopyOfContextMap(); // 包装 Runnable 以设置 MDC 上下文 Runnable wrappedRunnable = () -> { try { // 设置 MDC 上下文 MDC.setContextMap(contextMap); // 执行任务 r.run(); } finally { // 清除 MDC 上下文 MDC.clear(); } }; Thread thread = defaultFactory.newThread(wrappedRunnable); thread.setName(namePrefix + thread.getName()); return thread; } }; ThreadPoolExecutor executor = new ThreadPoolExecutor( 5, 10, 30L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(500), threadFactory, new ThreadPoolExecutor.CallerRunsPolicy()); return executor; } }
以上就是SpringCloud通过MDC实现分布式链路追踪的详细内容,更多关于SpringCloud MDC链路追踪的资料请关注脚本之家其它相关文章!