范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

一次Dubbo线程上下文类加载器的疑难杂症分析

  问题环境dubbo:2.7.18 java:java8 复制代码问题背景
  有业务(Java)的同学反馈,在接入了 devops 的某些 javaagent 以后会极大概率出现 dubbo 调用失败,dubbo 接口中用到的业务类都提示找不到,导致反序列化失败,部分日志输出如下:[2023-02-09 19:22:58.982][][][DubboServerHandler-172.30.86.136:20880-thread-2][WARN][com.alibaba.com.caucho.hessian.io.SerializerFactory:686] Hessian/Burlap: "com.seewo.kishframework.page.PageRequest" is an unknown class in org.springframework.boot.loader.LaunchedURLClassLoader@66373f77: java.lang.ClassNotFoundException: com.seewo.kishframework.page.PageRequest 复制代码
  但是通过内存查看,com.seewo.kishframework.page.PageRequest 等失败的类都已经被org.springframework.boot.loader.LaunchedURLClassLoader@66373f77 加载。
  离了个大谱。源码初步分析
  异常日志是在 com.alibaba.com.caucho.hessian.io.SerializerFactory#getDeserializer(java.lang.String) 这个函数中
  try 代码中首先通过 loadSerializedClass 加载待反序列化的类,然后用这个 classloader 获取这个类对应的反序列化对象 deserializer。
  loadSerializedClass 函数首先调用 getClassFactory 获取单例的 _classFactory,然后使用这个单例就加载类。
  这里的 ClassFactory 创建的时候传入的 classloader 是当前线程的 ContextClassLoader。public class Hessian2SerializerFactory extends SerializerFactory {      public Hessian2SerializerFactory() {     }      @Override     public ClassLoader getClassLoader() {         return Thread.currentThread().getContextClassLoader();     }  } 复制代码
  在首次初始化以后,反序列的类都是由此 SerializerFactory 加载。通过内存分析,可以看到,ClassFactory 的 classloader 居然是 sun.misc.Launcher$AppClassLoader,这个 classloader 的 classpath 是不包含 springboot 管理的 BOOT-INF 下的包的,导致这个 AppClassLoader 自然加载不到对应的类。
  因此经过分析,之前日志里输出是误导的。Hessian/Burlap: "com.seewo.kishframework.page.PageRequest" is an unknown class in org.springframework.boot.loader.LaunchedURLClassLoader@66373f77: java.lang.ClassNotFoundException: com.seewo.kishframework.page.PageRequest 复制代码
  这里显示是用 org.springframework.boot.loader.LaunchedURLClassLoader 去加载类找不到,是完全迷惑的,压根就不是用这个类加载器去加载的,而是用 ClassFactory 中的 _loader 。
  现在就要搞清楚,为什么 ClassFactory 中的 _loader 会不对。debug 阶段
  通过修改 hessian-lite 的源码,重新替换对应的类,加入了堆栈
  看下是谁第一次调用导致了懒加载单例的初始化,第一次调用的堆栈如下:
  可以看到 DubboServerHandler-172.30.86.136-thread-2 这个线程的 ContextClassLoader 确实不知为何被设置为了sun.misc.Launcher$AppClassLoader,然后看更多日志,发现除了这个线程其它的 DubboServerHandler 线程的 ContextClassLoader 都是正常的。
  这下问题思路基本上清晰了。因为 DubboServerHandler-172.30.86.136-thread-2 这个线程的 ContextClassLoader 是 sun.misc.Launcher$AppClassLoader,这线程最早调用 com.alibaba.com.caucho.hessian.io.SerializerFactory#getClassFactory 导致 SerializerFactory 的 _loader 被赋值为了 sun.misc.Launcher$AppClassLoader,后面的线程也没有机会对单例的 SerializerFactory 重新赋值。
  接下来就是分析,最早的线程 DubboServerHandler-172.30.86.136-thread-2 是由谁创建的。又因为业务反馈他们去掉 devops 的 dubbo 健康检查 javaagent 以后,就没有再出现,于是把焦点放在了这个插件上。
  这个插件的功能也很简单,就是监听一个端口,收到健康检查的请求以后,提交一个 Echo 任务到 dubbo 的任务池里,如果任务池没有被占满,可以被执行,那么表示 dubbo 健康,否则 dubbo 线程池已经被占满,表示不健康。
  EchoTask 就是一个 Runnable,里面啥都没做
  上面的 ThreadPoolExecutor 是通过字节码注入的方式从 dubbo 框架中获取的
  问题就出在这个健康检查 javaagent 往 dubbo 的线程池提交的这个任务。为了弄清楚这个问题,需要一点点 Java ContextClassLoader 的知识。
  线程上下文类加载器由继承自父线程。如果父线程没有设置上下文类加载器,则线程将继承类加载器的默认实现。线程 Thread 创建的代码如下:private Thread(ThreadGroup g, Runnable target, String name,                long stackSize, AccessControlContext acc,                boolean inheritThreadLocals) {     this.name = name;     Thread parent = currentThread();     this.priority = parent.getPriority();     if (security == null || isCCLOverridden(parent.getClass()))         this.contextClassLoader = parent.getContextClassLoader();     else         this.contextClassLoader = parent.contextClassLoader; } 复制代码
  往线程池里执行一个 runnable 的过程,就是调用 addWorker 创建线程并执行public class ThreadPoolExecutor extends AbstractExecutorService {      public void execute(Runnable command) {         int c = ctl.get();         if (workerCountOf(c) < corePoolSize) {             if (addWorker(command, true)) // 创建并执行线程                 return;             c = ctl.get();         }     }     private boolean addWorker(Runnable firstTask, boolean core) {         Worker w = null;                  w = new Worker(firstTask); // 创建 worker         final Thread t = w.thread;         t.start(); // 启动线程         return true;   } }  private final class Worker     extends AbstractQueuedSynchronizer     implements Runnable {                  Worker(Runnable firstTask) {             setState(-1); // inhibit interrupts until runWorker             this.firstTask = firstTask;             this.thread = getThreadFactory().newThread(this); // 创建线程         } }             复制代码
  dubbo 的 ThreadFactory 是 com.alibaba.dubbo.common.utils.NamedThreadFactory,里面构造了线程名。public Thread newThread(Runnable runnable) { 	String name = mPrefix + mThreadNum.getAndIncrement();     Thread ret = new Thread(mGroup,runnable,name,0);     ret.setDaemon(mDaemo);     return ret; } 复制代码
  从上面的代码可以分析出,创建的 DubboServerHandler 线程上下文类加载器,继承调用 new Thread 的父线程上下文类加载器。
  可以用最简单的一个 demo 来看验证上面的结论。import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;  class MyRunnable implements Runnable {      public void run() {         System.out.println("TCCL:" + Thread.currentThread().getContextClassLoader());     } }  class MyClassLoader extends ClassLoader {  } public class Test01 {      public static void main(String[] args) {         Thread.currentThread().setContextClassLoader(new MyClassLoader());         ExecutorService executors = Executors.newFixedThreadPool(100);         executors.execute(new MyRunnable()) ;     } }   复制代码
  上面的代码输出输出 TCCL:MyClassLoader@48140564 复制代码
  通过注入 org.apache.dubbo.common.threadlocal.NamedInternalThreadFactory.newThread 可以看到这个方法创建了 DubboServerHandler-172.30.102.71:20880-thread-1 线程。
  于是这里的情况就很清楚了,因为 dubbo-health 这个 javaagent 是由 sun.misc.Launcher$AppClassLoader 加载的,其执行的线程上下文类加载器也是 sun.misc.Launcher$AppClassLoader,导致项目启动以后,k8s 不停发起健康检查触发了 javaagent 向 dubbo 线程池提交任务,导致了头几个 DubboServerHandler 线程(DubboServerHandler-xx-thread-1、DubboServerHandler-xx-thread-2等)的上下文成为了 sun.misc.Launcher$AppClassLoader。
  当真实流量进来,被分派到头几个 DubboServerHandler 处理,此时它第一个调用 SerializerFactory 的 getClassFactory 导致 ClassFactory 的 _loader 被设置为了 sun.misc.Launcher$AppClassLoader,这个单例开始继续祸害了。
  如果初始状态往 dubbo 线程池提交任务是一个很危险的事情,一定要保障线程的上下文是正确的,不然就悲剧了。修改方法
  这里修改方法也简单,只需要将提交任务的时候把自己 javaagent 的 classloader 切换为业务的 classloader 即可。后记
  发现旧版本的 dubbo 没有这个问题,是因为它是用 SerializerFactory 的 _loader,而不是 ClassFactory 中的 _loader
  至于为什么,我就不想知道了。
  原文链接:https://juejin.cn/post/7199870903534190651

中医科学吗?中医科学吗?中医不科学吗?有个问题我一直不明白。西医的发展历史才短短几百年。几百年前的西方国家治疗疾病难道不是用的中草药吗?人类在地球上生存了几万年。中国文明五千年生生不息。子孙繁一年四季都用身体乳的人现在怎么样了?6年在飞空姐,每天擦身体乳,最大的感受就是让我变成了黑夜里最靓丽的景色!闺蜜说我比黑夜里的萤火虫更耀眼(图左傻憨憨就是我,哈哈哈)但我其实之前从来不涂身体乳,一直觉得这玩意就是智商刺激战场现版本天降奇兵里,有哪些难用但很厉害的武器?为什么?执笔,一位在游戏圈摸爬滚打十余年的在职老兵,天天聊游戏,关注不迷路哟!感谢邀请目前刺激战场版本里,个人认为有五种最难驾驭的武器!挨个说一下S1897伤害是谜这个S1897五连喷是不LOL奥拉夫开大谁可以控制,网友调侃全联盟只有一个英雄可以,你有何看法?英雄联盟中有各式各样的英雄,也是非常多机制不同的技能,通过这些的组合,让我们有了自己喜欢的英雄,很多的玩家都认为奥拉夫的大招是无解的任何技能都无法控制他,但是其实还有一个英雄可以,如果利物浦与曼城互换主教练球队还会取得现在的骄人战绩吗?瓜秃不能,因为他是金元主帅,没钱玩不转,他不会发掘现有的球员,只会买买买。瓜迪奥拉如果执教利物浦,他的传控足球可能要大打折扣,瓜秃无论执教巴萨拜仁还是曼城都是以中场为依托主导球队的陈梦为什么总是遭受质疑?奥运会冠军不是很难拿吗?奥运会冠军难拿应该是针对外协会的说法。而对于我们国乒来说,只要不输外协会就有希望打进决赛。就拿东京奥运会来说,男女单打全都是由我们国乒队员来争夺冠军,因此,某种程度来说,奥运冠军更你们用过护肤品哪个品牌的效果好?谢谢邀请。樱桃番茄今天跟大家来分享一下。私藏多年!我的爱用护肤品推荐。一直很想写一篇爱用品推荐。正好手头有好多我很喜欢的产品。所以这次就给大家说说我的珍藏版爱用品本人肤质曾经严重痘有没有淘宝上200元以内,但牌子档次很高,别人认为很贵的,而买的人还不多的裙子?你试下去欣妈是黑市老板娘,她家晚上直播,买了条专柜的裙子,吊牌价1099,她家99,我不知道是怎么做到的,但确实是正品,因为平时自己经常去专柜买这个牌子。还有她家日本专柜的衣服也很法院检察院和公安局,三者的行政规格相同吗?法院检察院和公安局三者的行政规格是不相同的。其中法院检察院是一样的,省级高级人民法院省级人民检察院的行政规格是法定副省级,常务副职是正厅级市级中级人民法院市级中级人民检察院的行政规现在农村50岁以上还在外面打工的多吗?2020年马上就要过去了,现在50多岁的农民大多是1960年到1970年间出生的人,他们面临的生活压力是非常大的,上面有年迈的父母下面,还有刚刚毕业准备结婚的子女。所以他们不仅要为新车买了三个月,才跑了1500公里,要不要去首保?为什么?首先表明我的观点新车最好是在三个月之内磨合完毕并做首保,这个过程越短越好,不要拖的时间过长。如果在三个月之内没有跑够规定的磨合里程,需要看一下汽车的出厂日期,如果已经超过了六个月,
05年拿下超女总冠军,出道爆红却饱受争议,如今患重病靠轮椅出行李宇春靠超女出道火遍全国,她成为了国人心中的国民偶像,登上了美国时代周刊,她是中国最受欢迎的女歌手之一,也是一代人心中难以忘怀的记忆。然而前段时间却爆出她患上了强直性脊柱炎,她的职国羽2胜3负!名将1912领先被逆转,男双世界冠军爆冷出局北京时间2022年10月26日,法国羽毛球公开赛结束首日争夺,国羽5组选手先后登场,结果取得2胜3负的成绩。男单赛场,赵俊鹏再次遭遇一轮游。女单赛场,张艺曼02不敌奥运冠军马林,比邪教全能神污染韩国水源遭市民举报信徒施暴调查记者中国反邪教网2022年10月25日消息,通讯员子舒韩国网站宗教与真理2022年10月12日报道,全能神邪教组织今年在韩国多次因污染环境引发民怨,10月宗教与真理网站再次接到市民举报路拾秋韵路赏云天白絮飞,风摇阔叶彩枝随。祥欢喜鹊蓝空舞,郁柳拨弦秀水吟。秋韵之美,美在不拘一格。多变的云天,渐变的花草树木,都在秋季里,谱写着自己的诗篇。与喜鹊偶遇,撩起我心海中幻想的涟漪日子将就着过的伤感说说,日子过的很累,触动人心的情感文案走心高质量5万条经典文案资料库,关注我,让发朋友圈配文案,写动态,写心情,上热门更简单。如果有那句话触动你,就复制下来,粘贴到评论区,释放你的心声吧,任何尝试勇敢迈出第一步都是艰难的,思想要深思想是一个人的灵魂,有思想的人才能独立思考而不被外界纷繁事务打扰,遇事沉着冷静应对,有自己的判断逻辑和内在定力。个人在茫茫宇宙中显得无比渺小,但一个人若有思想,内心便会坚韧,行事方有缘无份也是一种温暖头条创作挑战赛深秋的雨带着思念缱绻着缠绵一场秋雨一场寒季节的轮转将逝去的光阴散落在天涯路远静候着明天也许是深秋的雨重温了甜美的梦幻也许是秋风陪伴着雨慢慢地读懂了秋天秋风秋雨陪伴着秋西安新增08,发现社会面确诊,多所高校已实施封闭管理西安的疫情出现反弹。自11日多所学校停止线下教学以来,西安多个景区关闭,全市暂停堂食。一系列动作让不少西安市民不安,据最新得到的消息,陕西昨日新增25例确诊,其中西安确诊的8例人员2025年封关后,对岛内居民的好处有哪些?2025年封关后,对岛内居民的好处有首先,第一点就是咱们海南人民享受了独有的一个身份。海南岛目前已经确立了海南自贸港的一个单独立法,这就意味着海南自贸港将会根据自身的发展及时调整了福建省漳州市云霄县泮坑茶业拥抱互联网共享一杯致富好茶中国经济周刊经济网讯连日来,福建省漳州市云霄县本土网红主播们追逐茶香走进和平乡泮坑社区,将直播间搬入生态茶山搬到制茶一线,与各地茶客们线上品评泮坑茶。茶业拥抱互联网,这是泮坑社区茶入境航班激增10倍,卡塔尔世界杯目标200亿美元收入记者杨弋距离2022年卡塔尔世界杯11月中下旬开赛还有不到六周,早早在手里攥好球票的游客们也开始订起了启程的机票。据旅游分析集团ForwardKeys的数据,从包括阿联酋墨西哥阿根