在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;
}
}