关于TLOG在RabbitMQ收发消息时Trace不传递问题

360 阅读2分钟

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.