参考
阿里TTL:github.com/alibaba/tra...
TLog:tlog.yomahub.com/
背景
推荐阅读:tlog.yomahub.com/pages/5b7bd...,本篇文章也是看了TLog的官方文档和相关源码而产生的。
在微服务架构中,由于线程池复用、异步调用等机制的存在,传统的线程级日志标识(如ThreadLocal
)会导致请求链路断裂。例如,当主线程将任务提交到线程池时,子线程无法自动继承主线程的上下文信息,使得日志中的traceId
丢失。
核心组件原理
MDC机制
Log4j2
的Mapped Diagnostic Context(MDC)
通过ThreadLocal
存储线程级上下文数据。例如,在HTTP
请求进入时,通过拦截器将traceId
存入MDC
,日志模板中通过%X{traceId}
动态替换值。
TransmittableThreadLocal(TTL)
阿里的TTL
组件解决了线程池场景下的上下文传递问题。通过装饰线程池,TTL
在任务提交时自动拷贝父线程的上下文到子线程,并在任务结束后清理副本,确保多级线程池调用链路完整。
日志模板配置
在log4j2.xml
中配置PatternLayout
,添加%X{traceId}
占位符即可实现日志标识嵌入。
xml
<Property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId}] [%t] %-5level %logger{36} - %msg%n"/>
实现方案详解
1、TraceId生成
- 简单场景:使用
UUID
生成唯一标识(代码示例) - 分布式场景:建议采用雪花算法(
Snowflake
),结合机器ID和时间戳生成全局唯一ID
2、组件集成
xml
<!-- Log4j2核心依赖 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.1</version>
</dependency>
<!-- 阿里TTL -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.2</version>
</dependency>
如果是Springboot
使用Log4j2
要排除默认日志
xml
<!-- 排除默认日志 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!--排除默认log-->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- log4j2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
3、链路上下文
java
/**
* 基于TransmittableThreadLocal实现线程池安全的TraceID传递
*
* @author wnhyang
* @date 2025/3/3
**/
public class TraceContext {
private static final TransmittableThreadLocal<String> TRACE_ID = new TransmittableThreadLocal<>();
/**
* 设置TraceID,并同步到Log4j2的MDC
*/
public static void setTraceId(String traceId) {
TRACE_ID.set(traceId);
}
public static String getTraceId() {
return TRACE_ID.get();
}
public static void clear() {
TRACE_ID.remove();
}
public static String generateTraceId() {
return IdUtil.simpleUUID();
}
}
4、生成并传递TraceId
需要注意这里只是写了作为链路源头生成TraceId
和向指定的下游传递的示例。
然而在实际情况下通常会比较复杂,因为源头通常是不可知的(或是说不是那么清楚的),所以要包含几个步骤:1、检查上游是否有TraceId
传递;2、生成唯一TraceId
;3、传递TraceId
。
这些在TLog
中都以插件的形式实现,可以参考,不过TLog
好像没有兼容JDK17
,可以参考自己实现。另外正如TLog
官方所讲:"当然分布式追踪系统是一个最终的解决方案,如果您的公司已经上了分布式追踪系统,那TLog
并不适用。"
java
/**
* @author wnhyang
* @date 2025/3/3
**/
@Slf4j
public class TraceFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
// 生成并设置TraceID
String traceId = TraceContext.generateTraceId();
TraceContext.setTraceId(traceId);
MDC.put(TraceConstants.TRACE_KEY, traceId);
// 透传TraceID到下游(可选)
response.setHeader(TraceConstants.TRACE_HEADER_KEY, traceId);
filterChain.doFilter(request, response);
} finally {
// 必须清理上下文
TraceContext.clear();
MDC.remove(TraceConstants.TRACE_KEY);
}
}
}
5、装饰线程池
链路上下文中已经使用了TTL
存放TraceId
, 所以这里就是装饰线程池。
当然方式也不至这里的一种,还有如使用TtlExecutors
直接包装线程池等方式。
java
/**
* @author wnhyang
* @date 2025/3/3
**/
public class ThreadPoolFactory {
public static ThreadPoolTaskExecutor createExecutor(int coreSize,
int maxSize,
int queueCapacity,
String threadNamePrefix,
RejectedExecutionHandler rejectedExecutionHandler) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(coreSize);
executor.setMaxPoolSize(maxSize);
executor.setQueueCapacity(queueCapacity);
executor.setThreadNamePrefix(threadNamePrefix);
executor.setRejectedExecutionHandler(rejectedExecutionHandler);
executor.setTaskDecorator(getTraceContextDecorator());
executor.initialize();
return executor;
}
private static TaskDecorator getTraceContextDecorator() {
return runnable -> TtlRunnable.get(() -> {
try {
MDC.put(TraceConstants.TRACE_KEY, TraceContext.getTraceId());
runnable.run();
} finally {
MDC.clear();
}
});
}
}
6、Log4j2配置
配置上前面MDC.put
的traceId
即可,如:[%X{traceId}]
。
xml
<Property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId}] [%t] %-5level %logger{36} - %msg%n"/>
效果展示
这里只有复用线程池的效果展示。。。
可以看到链路号在多线程下仍然是一致的,而且多个链路下不会污染。
关于项目
进度
最近主要有两点更新:1、决策流程优化,之前太过依赖LiteFlow
了,最近的更新对这块进行了改善;2、项目组织结构,这个一直是我不满意的地方,而且历史提交中这样的大改已经不是一次两次的了。
之后应该要考虑分支管理了,在开源项目完善的同时新增Pro
版,增加一些更丰富的内容。
当然上面的测试不是很严谨,不过也能说明一些问题。而且项目开发期间一直有向性能、项目架构等等方面考虑。
推荐文章
规则引擎可以应用于哪些系统,用户画像、触达、风控、推荐、监控...
策略/规则篇
指标篇
风控系统指标计算/特征提取分析与实现01,Redis、Zset、模版方法
数据篇