Flink Task 日志文件隔离
任务在启动时会先通过 MdcUtils 启动一个 slf4j 的 MDC 环境,然后将 jobId 添加到 slf4j 的 MDC 容器中,随后任务输出的日志都将附带 joid。
MDC 介绍如下:
MDC ( Mapped Diagnostic Contexts ),它是一个线程安全的存放诊断日志的容器。
Logback设计的一个目标之一是对分布式应用系统的审计和调试。在现在的分布式系统中,需要同时处理很多的请求。如何来很好的区分日志到底是那个请求输出的呢?我们可以为每一个请求生一个logger,但是这样子最产生大量的资源浪费,并且随着请求的增多这种方式会将服务器资源消耗殆尽,所以这种方式并不推荐。
一种更加轻量级的实现是使用MDC机制,在处理请求前将请求的唯一标示放到MDC容器中如sessionId,这个唯一标示会随着日志一起输出,以此来区分该条日志是属于那个请求的。并在请求处理完成之后清除MDC容器。
MDC
类仅包含静态方法。它允许开发人员将信息放置在诊断上下文中,随后可以由某些 logback 组件检索这些信息。MDC
以每个线程为基础管理上下文信息。通常,在开始服务新的客户端请求时,开发人员会将相关的上下文信息(例如客户端 ID、客户端的 IP 地址、请求参数等)插入到MDC
中。如果配置正确,Logback 组件将自动在每个日志条目中包含此信息。更具体的使用方法和原理可以查看官方手册: https://logback.qos.ch/manual/mdc.html
原理简单来说则是日志框架会将标记信息存放到 ThreadContextMap 中,而在每次打印日志时都会从 ThreadContextMap中获取 标记信息并附加到日志中。
ThreadContextMap 默认实现类为 DefaultThreadContextMap,可以看到如果要让所有线程都生效,那么还需要在 Properties 中设置 isThreadContextMapInheritable 配置。
public class DefaultThreadContextMap implements ThreadContextMap, ReadOnlyStringMap {
public static final String INHERITABLE_MAP = "isThreadContextMapInheritable";
private static boolean inheritableMap;
static {
init();
}
static ThreadLocal<Map<String, String>> createThreadLocalMap(final boolean isMapEnabled) {
if (inheritableMap) {
return new InheritableThreadLocal<Map<String, String>>() {
@Override
protected Map<String, String> childValue(final Map<String, String> parentValue) {
return parentValue != null && isMapEnabled //
? Collections.unmodifiableMap(new HashMap<>(parentValue)) //
: null;
}
};
}
return new ThreadLocal<>();
}
static void init() {
inheritableMap = PropertiesUtil.getProperties().getBooleanProperty(INHERITABLE_MAP);
}
}
了解到MDC 类只包含静态方法,而 Task 会以 “flink-job-id” 作为 key 将 jobid 存放进 MDC 中,这样的话日志隔离也就好办了。
我们只需要自定义一个 Appender 并使用该 Appender,而该 Appender 通过 “flink-job-id” key 获取 MDC 中的 jobId,然后输出到以 jobId 命名的日志中,这样即实现了作业日志文件隔离。
Flink 日志记录:https://nightlies.apache.org/flink/flink-docs-release-1.20/docs/deployment/advanced/logging/