前言
现在分布式系统中一次接口调用都需要多个服务协同完成,其中一个服务出现问题,都会导致最终失败,而查询起来非常不方便。如果在整个链路中,可以通过一个唯一 ID(traceId)跟踪本次服务调用,方便查询问题。
代码目录
实现
TraceIdUtil.java 用来生产 traceId 和存储
public class TraceIdUtil {private static final ThreadLocal<String> TRACE_ID = new ThreadLocal<>();
private static final String HTTP_TYPE = "HTTP-";
private static final String RPC_TYPE = "RPC-";
public static String getTraceId() {return TRACE_ID.get();
}
/**
* 获取
*
* @return traceid
*/
public static String getTraceId(String type) {if (TRACE_ID.get() == null) {setTraceId(type + UUID.randomUUID().toString());
}
return Optional.ofNullable(TRACE_ID.get()).orElse("");
}
public static String getHttpTraceId() {return TraceIdUtil.getTraceId(HTTP_TYPE);
}
public static String getRpcTraceId() {return TraceIdUtil.getTraceId(RPC_TYPE);
}
public static void setTraceId(String traceId) {TRACE_ID.set(traceId);
}
}
dubbo 调用使用 spi 实现检测是否有 traceId
消费者:GlobalTraceConsumerFilter
@Slf4j
@Activate(group = {Constants.CONSUMER}, order = -9999)
public class GlobalTraceConsumerFilter implements Filter {
private static final String TRACE_ID = "traceId";
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {String traceId = TraceIdUtil.getRpcTraceId();
if (StringUtils.isNotEmpty(traceId)) {log.info("consumer 当前 traceId:{}", traceId);
RpcContext.getContext().setAttachment(TRACE_ID, traceId);
} else {
// 调用无 traceID
RpcContext.getContext().setAttachment(TRACE_ID, TraceIdUtil.getRpcTraceId());
}
return invoker.invoke(invocation);
}
}
生产者:GlobalTraceProviderFilter
@Slf4j
@Activate(group = {Constants.PROVIDER}, order = -9999)
public class GlobalTraceProviderFilter implements Filter {
private static final String TRACE_ID = "traceId";
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {String traceId = invocation.getAttachment(TRACE_ID);
if (!StringUtils.isBlank(traceId)) {log.info("当前 traceId:{}", traceId);
RpcContext.getContext().setAttachment(TRACE_ID, traceId);
TraceIdUtil.setTraceId(traceId);
} else {log.warn("当前 traceId 无");
// 调用无 traceID
RpcContext.getContext().setAttachment(TRACE_ID, TraceIdUtil.getRpcTraceId());
TraceIdUtil.setTraceId(traceId);
}
return invoker.invoke(invocation);
}
}
对于 http 请求统一拦截
WebTraceIdFilter.java
@Slf4j
@WebFilter(urlPatterns = "/*")
@Order(-9999)
public class WebTraceIdFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {String httpTraceId = TraceIdUtil.getHttpTraceId();
log.info("接受 http 请求添加 traceId:{}", httpTraceId);
}
}
为了让 dubbo 的 spi 机制生效还要在 resoutces 下 META-INF/dubbo 目录新建文件
com.alibaba.dubbo.rpc.Filter
内容:
gCtrace=com.example.common.traceid.GlobalTraceConsumerFilter
gPtrace=com.example.common.traceid.GlobalTraceProviderFilter
http 调用日志:
2019-11-08 18:45:35.175 [DubboServerHandler-10.112.206.222:20990-thread-2] INFO c.example.common.traceid.GlobalTraceProviderFilter - 当
前 traceId:RPC-445e493a-914e-4614-891f-dd9aa248e1c7
可以通过 taceId 判断请求来源 http 还是 rpc 请求。