参考
阿里TTL:https://github.com/alibaba/transmittable-thread-local
TLog:https://tlog.yomahub.com/
背景
image
推荐阅读:https://tlog.yomahub.com/pages/5b7bd2/,本篇文章也是看了TLog的官方文档和相关源码而产生的。
在微服务架构中,由于线程池复用、异步调用等机制的存在,传统的线程级日志标识(如ThreadLocal
)会导致请求链路断裂。例如,当主线程将任务提交到线程池时,子线程无法自动继承主线程的上下文信息,使得日志中的traceId
丢失。
核心组件原理
MDC机制
Log4j2
的Mapped Diagnostic Context(MDC)
通过ThreadLocal
存储线程级上下文数据。例如,在HTTP
请求进入时,通过拦截器将traceId
存入MDC
,日志模板中通过%X{traceId}
动态替换值。
TransmittableThreadLocal(TTL)
阿里的TTL
组件解决了线程池场景下的上下文传递问题。通过装饰线程池,TTL
在任务提交时自动拷贝父线程的上下文到子线程,并在任务结束后清理副本,确保多级线程池调用链路完整。
日志模板配置
在log4j2.xml
中配置PatternLayout
,添加%X{traceId}
占位符即可实现日志标识嵌入。
1
| <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、组件集成
1 2 3 4 5 6 7 8 9 10 11 12 13
| <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.17.1</version> </dependency>
<dependency> <groupId>com.alibaba</groupId> <artifactId>transmittable-thread-local</artifactId> <version>2.14.2</version> </dependency>
|
如果是Springboot
使用Log4j2
要排除默认日志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency>
|
3、链路上下文
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
public class TraceContext {
private static final TransmittableThreadLocal<String> TRACE_ID = new TransmittableThreadLocal<>();
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
并不适用。”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
@Slf4j public class TraceFilter extends OncePerRequestFilter {
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { String traceId = TraceContext.generateTraceId(); TraceContext.setTraceId(traceId); MDC.put(TraceConstants.TRACE_KEY, traceId);
response.setHeader(TraceConstants.TRACE_HEADER_KEY, traceId);
filterChain.doFilter(request, response); } finally { TraceContext.clear(); MDC.remove(TraceConstants.TRACE_KEY); } }
}
|
5、装饰线程池
链路上下文中已经使用了TTL
存放TraceId
,
所以这里就是装饰线程池。
当然方式也不至这里的一种,还有如使用TtlExecutors
直接包装线程池等方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
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}]
。
1
| <Property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId}] [%t] %-5level %logger{36} - %msg%n"/>
|
效果展示
这里只有复用线程池的效果展示。。。
image
可以看到链路号在多线程下仍然是一致的,而且多个链路下不会污染。
关于项目
https://github.com/wnhyang/coolGuard
进度
最近主要有两点更新:1、决策流程优化,之前太过依赖LiteFlow
了,最近的更新对这块进行了改善;2、项目组织结构,这个一直是我不满意的地方,而且历史提交中这样的大改已经不是一次两次的了。
之后应该要考虑分支管理了,在开源项目完善的同时新增Pro
版,增加一些更丰富的内容。
image
当然上面的测试不是很严谨,不过也能说明一些问题。而且项目开发期间一直有向性能、项目架构等等方面考虑。
image
推荐文章
规则引擎可以应用于哪些系统,用户画像、触达、风控、推荐、监控...
基于规则引擎的风控决策系统介绍与演示
风控系统之规则重复触发
LiteFlow上下文与组件设计,数据依赖梳理
交易事件的生命周期,事前事中事后风控,结果通知/回调
策略/规则篇
风控系统之普通规则条件,使用LiteFlow实现
风控系统之通用规则条件设计,算术单元/逻辑单元/函数式接口
LiteFlow决策系统的策略模式,顺序、最坏、投票、权重
指标篇
风控系统指标计算/特征提取分析与实现01,Redis、Zset、模版方法
基于LiteFlow的风控系统指标版本控制
风控系统指标版本管理,前端实现
风控系统之指标回溯,历史数据重跑
业务链指标,用户行为模式识别,埋点系统
数据篇
风控系统之数据服务,名单、标签、IP、设备、地理信息、征信等
GeoHash处理经纬度,降维,空间填充曲线