纠纷奇闻社交美文家庭
投稿投诉
家庭城市
爱好生活
创业男女
能力餐饮
美文职业
心理周易
母婴奇趣
两性技能
社交传统
新闻范文
工作个人
思考社会
作文职场
家居中考
兴趣安全
解密魅力
奇闻笑话
写作笔记
阅读企业
饮食时事
纠纷案例
初中历史
说说童话
乐趣治疗

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

5月17日 虎狼旗投稿
  问题环境dubbo:2。7。18java:java8复制代码问题背景
  有业务(Java)的同学反馈,在接入了devops的某些javaagent以后会极大概率出现dubbo调用失败,dubbo接口中用到的业务类都提示找不到,导致反序列化失败,部分日志输出如下:〔2023020919:22:58。982〕〔〕〔〕〔DubboServerHandler172。30。86。136:20880thread2〕〔WARN〕〔com。alibaba。com。caucho。hessian。io。SerializerFactory:686〕HessianBurlap:com。seewo。kishframework。page。PageRequestisanunknownclassinorg。springframework。boot。loader。LaunchedURLClassLoader66373f77:java。lang。ClassNotFoundException:com。seewo。kishframework。page。PageRequest复制代码
  但是通过内存查看,com。seewo。kishframework。page。PageRequest等失败的类都已经被org。springframework。boot。loader。LaunchedURLClassLoader66373f77加载。
  离了个大谱。源码初步分析
  异常日志是在com。alibaba。com。caucho。hessian。io。SerializerFactorygetDeserializer(java。lang。String)这个函数中
  try代码中首先通过loadSerializedClass加载待反序列化的类,然后用这个classloader获取这个类对应的反序列化对象deserializer。
  loadSerializedClass函数首先调用getClassFactory获取单例的classFactory,然后使用这个单例就加载类。
  这里的ClassFactory创建的时候传入的classloader是当前线程的ContextClassLoader。publicclassHessian2SerializerFactoryextendsSerializerFactory{publicHessian2SerializerFactory(){}OverridepublicClassLoadergetClassLoader(){returnThread。currentThread()。getContextClassLoader();}}复制代码
  在首次初始化以后,反序列的类都是由此SerializerFactory加载。通过内存分析,可以看到,ClassFactory的classloader居然是sun。misc。LauncherAppClassLoader,这个classloader的classpath是不包含springboot管理的BOOTINF下的包的,导致这个AppClassLoader自然加载不到对应的类。
  因此经过分析,之前日志里输出是误导的。HessianBurlap:com。seewo。kishframework。page。PageRequestisanunknownclassinorg。springframework。boot。loader。LaunchedURLClassLoader66373f77:java。lang。ClassNotFoundException:com。seewo。kishframework。page。PageRequest复制代码
  这里显示是用org。springframework。boot。loader。LaunchedURLClassLoader去加载类找不到,是完全迷惑的,压根就不是用这个类加载器去加载的,而是用ClassFactory中的loader。
  现在就要搞清楚,为什么ClassFactory中的loader会不对。debug阶段
  通过修改hessianlite的源码,重新替换对应的类,加入了堆栈
  看下是谁第一次调用导致了懒加载单例的初始化,第一次调用的堆栈如下:
  可以看到DubboServerHandler172。30。86。136thread2这个线程的ContextClassLoader确实不知为何被设置为了sun。misc。LauncherAppClassLoader,然后看更多日志,发现除了这个线程其它的DubboServerHandler线程的ContextClassLoader都是正常的。
  这下问题思路基本上清晰了。因为DubboServerHandler172。30。86。136thread2这个线程的ContextClassLoader是sun。misc。LauncherAppClassLoader,这线程最早调用com。alibaba。com。caucho。hessian。io。SerializerFactorygetClassFactory导致SerializerFactory的loader被赋值为了sun。misc。LauncherAppClassLoader,后面的线程也没有机会对单例的SerializerFactory重新赋值。
  接下来就是分析,最早的线程DubboServerHandler172。30。86。136thread2是由谁创建的。又因为业务反馈他们去掉devops的dubbo健康检查javaagent以后,就没有再出现,于是把焦点放在了这个插件上。
  这个插件的功能也很简单,就是监听一个端口,收到健康检查的请求以后,提交一个Echo任务到dubbo的任务池里,如果任务池没有被占满,可以被执行,那么表示dubbo健康,否则dubbo线程池已经被占满,表示不健康。
  EchoTask就是一个Runnable,里面啥都没做
  上面的ThreadPoolExecutor是通过字节码注入的方式从dubbo框架中获取的
  问题就出在这个健康检查javaagent往dubbo的线程池提交的这个任务。为了弄清楚这个问题,需要一点点JavaContextClassLoader的知识。
  线程上下文类加载器由继承自父线程。如果父线程没有设置上下文类加载器,则线程将继承类加载器的默认实现。线程Thread创建的代码如下:privateThread(ThreadGroupg,Runnabletarget,Stringname,longstackSize,AccessControlContextacc,booleaninheritThreadLocals){this。ThreadparentcurrentThread();this。priorityparent。getPriority();if(securitynullisCCLOverridden(parent。getClass()))this。contextClassLoaderparent。getContextClassLoader();elsethis。contextClassLoaderparent。contextClassL}复制代码
  往线程池里执行一个runnable的过程,就是调用addWorker创建线程并执行publicclassThreadPoolExecutorextendsAbstractExecutorService{publicvoidexecute(Runnablecommand){intcctl。get();if(workerCountOf(c)corePoolSize){if(addWorker(command,true))创建并执行线程cctl。get();}}privatebooleanaddWorker(RunnablefirstTask,booleancore){WwnewWorker(firstTask);创建workerfinalThreadtw。t。start();启动线程}}privatefinalclassWorkerextendsAbstractQueuedSynchronizerimplementsRunnable{Worker(RunnablefirstTask){setState(1);inhibitinterruptsuntilrunWorkerthis。firstTaskfirstTthis。threadgetThreadFactory()。newThread(this);创建线程}}复制代码
  dubbo的ThreadFactory是com。alibaba。dubbo。common。utils。NamedThreadFactory,里面构造了线程名。publicThreadnewThread(Runnablerunnable){StringnamemPrefixmThreadNum。getAndIncrement();ThreadretnewThread(mGroup,runnable,name,0);ret。setDaemon(mDaemo);}复制代码
  从上面的代码可以分析出,创建的DubboServerHandler线程上下文类加载器,继承调用newThread的父线程上下文类加载器。
  可以用最简单的一个demo来看验证上面的结论。importjava。util。concurrent。ExecutorSimportjava。util。concurrent。EclassMyRunnableimplementsRunnable{publicvoidrun(){System。out。println(TCCL:Thread。currentThread()。getContextClassLoader());}}classMyClassLoaderextendsClassLoader{}publicclassTest01{publicstaticvoidmain(String〔〕args){Thread。currentThread()。setContextClassLoader(newMyClassLoader());ExecutorServiceexecutorsExecutors。newFixedThreadPool(100);executors。execute(newMyRunnable());}}复制代码
  上面的代码输出输出TCCL:MyClassLoader48140564复制代码
  通过注入org。apache。dubbo。common。threadlocal。NamedInternalThreadFactory。newThread可以看到这个方法创建了DubboServerHandler172。30。102。71:20880thread1线程。
  于是这里的情况就很清楚了,因为dubbohealth这个javaagent是由sun。misc。LauncherAppClassLoader加载的,其执行的线程上下文类加载器也是sun。misc。LauncherAppClassLoader,导致项目启动以后,k8s不停发起健康检查触发了javaagent向dubbo线程池提交任务,导致了头几个DubboServerHandler线程(DubboServerHandlerxxthread1、DubboServerHandlerxxthread2等)的上下文成为了sun。misc。LauncherAppClassLoader。
  当真实流量进来,被分派到头几个DubboServerHandler处理,此时它第一个调用SerializerFactory的getClassFactory导致ClassFactory的loader被设置为了sun。misc。LauncherAppClassLoader,这个单例开始继续祸害了。
  如果初始状态往dubbo线程池提交任务是一个很危险的事情,一定要保障线程的上下文是正确的,不然就悲剧了。修改方法
  这里修改方法也简单,只需要将提交任务的时候把自己javaagent的classloader切换为业务的classloader即可。后记
  发现旧版本的dubbo没有这个问题,是因为它是用SerializerFactory的loader,而不是ClassFactory中的loader
  至于为什么,我就不想知道了。
  原文链接:https:juejin。cnpost7199870903534190651
投诉 评论

一次Dubbo线程上下文类加载器的疑难杂症分析问题环境dubbo:2。7。18java:java8复制代码问题背景有业务(Java)的同学反馈,在接入了devops的某些javaagent以后会极大概率出现dubbo……或许是电视的未来LeicaCine1激光投影机近来,Leica推出了LeicaCine1,这是携手Hisense共创的一款短焦投影机。首波消息出现于2022年度的IFA工业与电子博览会中,Leica宣布携手Hisen……图惊艳至极全新galaxys8超大亮点吸引眼球智能手机已经占据了我们的生活,人们轻易不能离开现代智能手机。当然,新的智能手机越做越好,让我们来了解一下三星新款galaxys8到底怎么样吧三星是一家高级的手机厂商,对于……图泸沽湖旅游体验少数民族特有的风情泸沽湖位于四川省与云南省的交界处,又叫做左所海、鲁枯湖等,它是云南省海拔比较高的湖泊,泸沽湖是中国排名第三深的淡水湖泊,吸引力国内外无数游客前来观光游览。泸沽湖的景色非常……图iphone8九月即将上市因屏幕产能几次延缓上市时间外媒表示iphone8上市的时间一改再改,十分不顺畅。起初因为技术问题延迟,后来因为三星公司的OLED产能跟不上,不能及时交货,首批量仅为400万台。iPhone8即将上……NBA历史最胖的球员你知道是谁吗?胡吃海喝毁了自己的职业生涯被球迷戏称为人类五花肉精华的约基奇正在角逐自己职业生涯的第二座MVP奖杯。这位篮球智商历史级别的胖子让我们彻底改变了关于篮球运动员的认知,原来一身肥肉膘的人依然可以在NBA的赛……2022年新能源重大新闻我国首个氢能中长期规划出炉2022年3月23日,国家发改委、国家能源局联合印发《氢能产业发展中长期规划(20212035年)》,明确了氢能的三大战略定位,指出氢能是未来国……图武侯祠横街带你走在历史的长廊说起四川,这里有九寨沟这样的自然美景,也有乐山大佛这样伟大的文化遗产,还有成都这个美食天堂,说起成都,这里还有许多的著名景点,今天我们就去看看武侯祠。四川真的是被上天眷顾……图inteli9小评性能指数无人能敌在现在电脑如此高度发展的信息时代,拥有一块好的处理器是非常有必要的。而英特尔就是几大处理器生产商中技术最为成熟的一家,也是历史最为悠久的一家。作为全球第一的CPU生产厂家……图莫高窟45窟带你走进彩塑的精彩世界敦煌莫高窟位于甘肃省敦煌市,与龙门石窟、麦积山石窟、云冈石窟并称为四大石窟,存有大量的佛教艺术瑰宝,收到很多信仰佛教的人的喜爱,是我国著名的宗教旅游景点之一。敦煌莫高窟历……图红米note4x浅蓝色评测外表小清新的实力派红米Note4X上周新推出了高配版,走小清新路线的初音绿和浅蓝色,其中浅蓝色大受欢迎,多次卖到断货。今天就来看看这款手机除了颜值还有什么亮点。虽然小米从一开始就习惯了使用……图尧山大峡谷旅行险要与刺激的冒险大峡谷,给人一种险峻幽深的感觉。当你来到大峡谷,就意味着你将会面临一场险要与刺激的冒险,尧山大峡谷也是这样。去到这里,挑战自己的勇气,让自己爱上冒险。尧山大峡谷位于河南省……
湿疹的最佳治疗方法?湿疹怎么治疗能去根上证50跌麻了2022。10。23)ChatGPT火爆全网!关于常州,它这样回答痔疮的最佳治疗方法(建议收藏)大写数字一到十大写数字的意义和来源吃太咸或患痴呆症这是真的吗到底怎么回事?中国最美公路治疗便秘最快的方法分享缓解便秘及调理的方法把早餐换成它,便秘没了,三高降了(图)便秘对身体有什么危害便秘了如何调理经常便秘是什么原因引起的便秘要吃什么便秘怎么快速排便长期便秘的原因及该怎么调理这就是命运吧游客照系列NO。15!新年出游还得是龙虎山!2023换机攻略iPhone14大跳水成果粉首选,安卓优先考每次看毛片我情不自禁的自慰腾讯战老干妈,狭路相逢,“勇者”败!台湾选举《冰雨火》2021年什么时候播出,暂定1月湖南卫视和优酷播出绝不放弃六年级作文Google对话式交互规范指南(三):设计原则与方法肚围对宝宝有副作用吗我爱你一生一世的话语男朋友说性格不合分手能挽回吗

友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找美丽时装彩妆资讯历史明星乐活安卓数码常识驾车健康苹果问答网络发型电视车载室内电影游戏科学音乐整形