Talk
项目使用TLOG用于链路追踪, 最近在一些场景上发现了一些问题 于是将这些问题和解决方案分享出来.
问题描述
我们项目使用了RabbitMQ, 在消费时出现了在调用下游时由于消息消费未插入Tracing参数导致到下游的链路断裂的情况.
TLOG运行原理源码分析
TLog在进程内是通过ali的 TransmittableThreadLocal来实现多线程数据传递的. 进程间则是通过Header来进行数据传递的.
线程间传递的核心类
public class TLogContext {
private static boolean enableInvokeTimePrint = false;
private static boolean hasTLogMDC;
private static boolean hasLogstash;
private static final TransmittableThreadLocal<String> traceIdTL = new TransmittableThreadLocal<>();
private static final TransmittableThreadLocal<String> preIvkAppTL = new TransmittableThreadLocal<>();
private static final TransmittableThreadLocal<String> preIvkHostTL = new TransmittableThreadLocal<>();
private static final TransmittableThreadLocal<String> preIpTL = new TransmittableThreadLocal<>();
private static final TransmittableThreadLocal<String> currIpTL = new TransmittableThreadLocal<>();
......
}
处理Web请求
com.yomahub.tlog.web.common.TLogWebCommon
public class TLogWebCommon extends TLogRPCHandler {
private final static Logger log = LoggerFactory.getLogger(TLogWebCommon.class);
private static volatile TLogWebCommon tLogWebCommon;
public static TLogWebCommon loadInstance() {
if (tLogWebCommon == null) {
synchronized (TLogWebCommon.class) {
if (tLogWebCommon == null) {
tLogWebCommon = new TLogWebCommon();
}
}
}
return tLogWebCommon;
}
public void preHandle(HttpServletRequest request) {
// web请求统一从请求头中尝试获取Tracing参数
String traceId = request.getHeader(TLogConstants.TLOG_TRACE_KEY);
String spanId = request.getHeader(TLogConstants.TLOG_SPANID_KEY);
String preIvkApp = request.getHeader(TLogConstants.PRE_IVK_APP_KEY);
String preIvkHost = request.getHeader(TLogConstants.PRE_IVK_APP_HOST);
String preIp = request.getHeader(TLogConstants.PRE_IP_KEY);
// 转为统一处理的bean
TLogLabelBean labelBean = new TLogLabelBean(preIvkApp, preIvkHost, preIp, traceId, spanId);
// 将Tracing参数填充到上下文中, 若为空则填充相应的默认值
// 详见 com.yomahub.tlog.core.rpc.TLogRPCndler
processProviderSide(labelBean);
}
public void afterCompletion() {
cleanThreadLocal();
}
}
解决方案
所以对于 消息消费/定时任务 这类业务, 可以在执行业务前构建一个TLogLabelBean, 并使用源码中的 processProviderSide 即可.
对于这类需求, 可以通过AOP或在执行消费/任务的线程池上扩展, 我是使用AOP进行增强的.
@Aspect
@Component
public class ListenerLogRegister {
public static final TLogRPCHandler T_LOG_RPC_HANDLER = new TLogRPCHandler();
@Pointcut("@annotation(org.springframework.amqp.rabbit.annotation.RabbitListener)")
public void registryTarget(){}
@Before("registryTarget()")
public void processLog(){
TLogLabelBean labelBean = new TLogLabelBean(null, null, null, TID_GENERATOR.generateTraceId(), null);
T_LOG_RPC_HANDLER.processProviderSide(labelBean);
}
}
改进方案
对于多维度的业务节点生产的消息, 在这里做的Tracing可能并不够, 需要能知道是从哪个业务服务生产的消息 保证链路的可追溯.
对于这一点, 我们可以在消息的header上进行拓展, 通过消息的header传递tracing值.
尾述
在这个问题解决的同时在我们项目中TLog有个比较底层的bug: 使用Hystrix后调用下游TLog中的参数值不会清空 并会一直累加, 这个问题将会在下一章整理并发出来 同时如果可能的话会提交PR.