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

一次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

有并发症的糖尿病患者,这些忌口食物千万不要碰糖尿病饮食讲求个体化原则,合并不同的并发症,饮食各有禁忌,一起来看看吧,有用的记得分享给身边的糖友。糖尿病合并肾病严格限制食盐摄入减轻肾脏负担,饮食尽可能清淡,食盐的摄入量每日要控你知道护肝应该吃什么吗?记牢这三要三不要肝脏作为人体消化系统中最大的消化腺,地位有多重要可想而知。人体摄入的各种营养物质,基本都要通过肝脏来代谢,同时肝脏还肩负着为机体解毒的重任,所有机体接受到的毒素,都需要经过肝脏来代不好酒肉,每天吃素,怎么尿酸还是会高?不好酒肉,每天吃素,怎么尿酸还是会高?听着是有点无辜了,但也未必。很多人吃菜,喜欢一个鲜,因此特别喜欢放鸡精味精。很多爱吃的零食加工品等也是如此。这就忽略了隐藏的嘌呤制造者鸡精。Q美网郑钦文袁悦顺利晋级王欣瑜遗憾出局新华社纽约8月30日电在30日进行的2022美国网球公开赛女单第一轮第二个比赛日中,中国选手郑钦文袁悦顺利晋级第二轮,王欣瑜遗憾出局。首轮比赛,郑钦文对阵前法网冠军赛会16号种子奥宛如秘密间谍基地幸存游戏爱好者玩家展示狠男范小屋或许每一个童心不灭的男人都曾经梦想拥有一个影视游戏作品中常见的猛男小屋,各类枪械齐整摆放,以供主人随时检阅擦拭,日前日媒就采访了一位疑似狠男的秘密小屋,一起来开开眼。本次的主人公屋时隔20年回顾云和山的彼端,它依然是最好的国产游戏之一如果要说起轩辕剑游戏系列的巅峰之作,可能不少玩家会不约而同地说出轩辕剑叁云和山的彼端的名字。这款由大宇DOMO小组制作,诞生于2000年的国产单机游戏,曾经是无数玩家的历史启蒙之作RedmiGPro游戏本2022酷睿版发布,最高12代i93070Ti今日,RedmiGPro游戏本2022锐龙版正式开卖,提供R76800HRTX3060的配置,首发价格7499元。随后,Redmi还推出了RedmiGPro游戏本2022酷睿版,满抖音游戏投放半年榜最内卷游戏名单出炉本篇榜单内容是根据对2022年2月7月的抖音游戏投放行业数据的深度观察与总结。本期榜单亮点抢先看投放时间最多的和投放量最多的都在做,打造达人矩阵已成大势所趋平稳期如何寻求爆发点?海小小诺娅乐园继承者游戏评测小小诺娅,逆天改命大家好,这里是雪落,很高兴为大家带来小小诺娅的游戏评测。基本信息名称小小诺娅乐园继承者(LittleNoahScionofParadise)类型标签横板动作,roguelite,日任正非背水一战,华为不用光刻机造芯,外媒痴心妄想原创科技辣评华为是国内知名的科技公司,华为手机曾经凭借一己之力抗衡三星和苹果两大国际品牌,巅峰时期拿下国内手机市场近50的份额,受此影响,苹果在国内市场的市占率曾一度下跌到个位数,唐山打人事件女孩怎么样了?为何至今杳无音讯?人民日报唐山烧烤店打人案件引发高度关注,如此令人发指的暴行,实在让人愤怒,大家纷纷表达对施暴者的谴责和声讨,对被害者的关心与同情,在此次事件中,网友正义发声让暴行曝光,加速了事件的
末日来临?最新发现的小行星要撞击地球?人类能否成功拦截它曾老曾经预言,2035年人类将面临重大危机。已故天体物理学家霍金曾经在大问题的简要回答中断言,人类最大的威胁就是小行星,并在未来的某一天撞击地球,导致人类的灭亡。而圣经。启示录中也恐龙最后的幸存时刻当历史的岁月在洪荒变迁之中,从公元前2。3亿年前的三叠纪晚期,驶入公元前0。65亿年前白垩纪晚期之时,物种大灭绝的悲惨时代悄然而至。取而代之的是曾经生活在蔚蓝色地球上的霸主恐龙王朝国内科技新闻探索二号完成一系列海试任务01hr记者28日从中国科学院获悉,探索二号科考船携深海勇士号载人潜水器完成一系列海试任务,已于日前返回三亚。海试期间,科研人员成功在海底布设大深度原位科学实验站,将实现深海长周期恐龙之间的血腥战争古生物学家为了能够找到这两具风神翼龙死亡的真相,利用科学技术,试图还原6500万年前发生在白垩纪晚期的那场特大史前暴风雪。去见证这两位曾经的爱人同时死亡之谜。由于案发现场没有过多的变局风云之引领6G2022年11月5日,中国成功发射中星19卫星,并精确入轨。外网学者因此发博,标题是中国发射高通量高速网络宽带卫星中兴19号,这是不是预示着中国正在组建5g或者6g时代就在今天,就本质上已证明零点猜想,张益唐或再创数学史的里程碑事件对数论学家而言,存在两个宇宙。第一个宇宙中,存在朗道西格尔零点,第二个宇宙中,不存在。我们的困惑是现在到底生活在哪个宇宙里?这种类似平行宇宙的设定,让人印象深刻。这段话出自美籍华裔你知道吗?北极星并不是指固定的某一颗星北极星每隔25800年就要循环一次,例如现阶段的北极星叫做勾陈一,但是到了公元14000年,就会变成织女星。从宇宙的时间上来看,北极星不但会变化,而且变得很快。北极星,又称北辰紫微云视角两千多年一遇!今晚月全食掩天王星,云南是绝佳观赏地之一再过几个小时,将迎来2022年度最值得关注的天象(没有之一)月全食掩天王星。最最重要的是,据天文科普专家介绍,这次中国绝大部分地区都将看到带食月出,其中云南及江浙沪湖南江西广西四川显卡卖不出去了,但玩家还没等到空中飞人今年下半年,市场对半导体产业的悲观预期开始一个接一个兑现,最近的一个是英伟达。英伟达在9月发布了新一代的RTX40系列显卡,以及DRIVEThor这类自动驾驶和数据中心芯片。不仅资联想消费生态星光直播夜王一博特别款好物限量开抢11月7日晚,奔赴热爱敢想敢搏2022联想消费生态星光直播夜活动(以下简称星光夜)在线上举行,联想品牌代言人王一博出席活动。敢为系列王一博特别款产品多款联想双十一智能好物与大家见面双十一新款冰箱怎么看?TCL格物冰箱Q10测评今年的TCL我愿意称之为TCLQ10年。有关注TCL的朋友可能会知道,今年TCL相继发布了TCLQ10GMiniLED电视,TCLQ10双子舱洗衣机,没多久,又发布了一款TCL格物