LogbackMDC 2022年有变动?
序
今天本来在研究 线程传递参数的方式,ThreadLocal MDC,来一篇吧。。
MDC 是每个线程 都有一个么?
是的,百度一下 都知道,但是 怎么证明? 首先
MDC.put -》
mdcAdapter.put(key, val); -》
MDCAdapter 实现类 LogbackMDCAdapter
上面第50行 可以看到 他获取之前最后一次的 标识,可以没有么?它起到了什么作用?
//这里 它传入的是 1
private Integer getAndSetLastOperation(int op) {
Integer lastOp = (Integer)this.lastOperation.get();
this.lastOperation.set(op);
return lastOp;
}
外面
public void put(String key, String val) throws IllegalArgumentException {
if (key == null) {
throw new IllegalArgumentException("key cannot be null");
} else {
Map<String, String> oldMap = (Map)this.copyOnThreadLocal.get();
//设置为 1,并且 返回原来的旧值 代表着上次是什么操作
Integer lastOp = this.getAndSetLastOperation(1);
//上次是写操作 并且oldMap 不是null 就在oldMap 基础 上put
if (!this.wasLastOpReadOrNull(lastOp) && oldMap != null) {
oldMap.put(key, val);
} else {
Map<String, String> newMap = this.duplicateAndInsertNewMap(oldMap);
newMap.put(key, val);
}
}
}
//需要返回 false 才代表 之前有oldMap 操作,这里的判断 不是null 不是2,看了一遍代码 这不就是1么
private boolean wasLastOpReadOrNull(Integer lastOp) {
return lastOp == null || lastOp == 2;
}
这就需要去看看 什么时候 会lastOp 为2
如果是 2就是上次操作是 读呢,这个时候 有put 操作 就 新创建一个空的newMap 赋值,如果原来是 1 代表这个线程 上次就是 put ,在原来的oldMap 基础上 进行put。
上面这个看完 会不会有一个疑问,那我多次 put (a,b 属性)然后 get下,在put a属性,难道我get b就没有了?
返回 a 和 b
一debug ,调用的是另一个获取方法,那什么时候 调用这个赋值为2的方法呢?
接口中没有这个方法,留个疑问 继续往下
Thread 和 ThreadLocal的关系 是一对一么?
上面说到 Thread 中有 ThreadLocal.ThreadLocalMap
变化
上面的东西 看完,感觉应该结尾了吧。。。NO, 2022年这个put 方法有变化了。。。。
往下继续看 注释 fix LOGBACK-1684 using code from LOGBACK-620
既然你这么说了 我就分别看看 这两个
代码改动在 github.com/qos-ch/logb…
为什么改动呢 保持原版不香么
大概意思 在对我们的应用程序进行性能分析时,LogbackMDCAdapter显示为性能热点。这是因为应用程序确实经常替换MDC中的多个条目,例如 删除/写入而不使用任何中间日志语句。另外,在生产环境中,在创建LoggingEvent之前经过过滤的实际日志消息相对较少。我重做了实现,以提高性能。其主要思想是尽可能推迟克隆内部Map。该补丁在该测试中提高了应用程序的总体性能约10%
里面 为了替换原来的 flag 判断,现在多了
final ThreadLocal<Map<String, String>> readWriteThreadLocalMap = new ThreadLocal<Map<String, String>>();
final ThreadLocal<Map<String, String>> readOnlyThreadLocalMap = new ThreadLocal<Map<String, String>>();
讨论
下面评论区 Ceki 大佬提出,你现在 用了2个对象,是不是可以通过
Get操作从来不需要复制或连续的“put/remove”操作,只有Get后面跟着“put/remove”操作需要复制map
这样就可以 so on Note that 8 operations were performed by only 3 maps were created
但是 Michael 大佬说
但作为一名软件开发人员,我对你的策略感到有点不舒服:“读后写后复制”听起来与“写后复制”和“惰性复制”不太一样。使用您的方法,您将迫使开发人员关注放置和获取操作的顺序,特别是如果应用程序在写入MDC之前执行存在性检查。
Ceki 大佬 最后说 经过测试 你写的确实不错 哈哈
彩蛋
版本升级后
//这个方法 原来是 赋值为2的,现在修改了
public Map<String, String> getPropertyMap() {
Map<String, String> readOnlyMap = readOnlyThreadLocalMap.get();
if (readOnlyMap == null) {
Map<String, String> current = readWriteThreadLocalMap.get();
if (current != null) {
final Map<String, String> tempMap = new HashMap<String, String>(current);
readOnlyMap = Collections.unmodifiableMap(tempMap);
readOnlyThreadLocalMap.set(readOnlyMap);
}
}
return readOnlyMap;
}
像不像 Eureka 的多级缓存机制,都一个道理
里面一个点 是 使用了 Collections.unmodifiableMap,所有对外返回的list map等引用参数,都要重新赋值一遍 / 只读不能操作。防止 外部改变这个元素 影响我们。
不可变类 也是一个道理,返回需要 重新生成一个,不会直接返回 原来的元素,不可变类 防止的是 一个对象中有20个属性, 防止20个属性出现中间态,比如 5个属性 属于v1, 15个属性 属于v2 这种中间态情况。
转载自:https://juejin.cn/post/7159888672156811294