记一次Nacos的issue修复之并发导致的NPE异常
ISSUE
Spring boot 应用启动被终止 #21
错误分析
DeferredApplicationEventPublisher
的继承关系
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;public class DeferredApplicationEventPublisher implements ApplicationEventPublisher, ApplicationListener<ContextRefreshedEvent> {...
}
复制代码
DeferredApplicationEventPublisher
的依赖图
现在来分析具体出现NPE
错误的原因
先看EventPublishingConfigService
中的addListener
@Override
public void addListener(String dataId, String group, Listener listener) throws NacosException {Listener listenerAdapter = new DelegatingEventPublishingListener(configService, dataId, group, applicationEventPublisher, executor, listener);configService.addListener(dataId, group, listenerAdapter);publishEvent(new NacosConfigListenerRegisteredEvent(configService, dataId, group, listener, true));
}
复制代码
然后看DelegatingEventPublishingListener
代码的继承关系
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import org.springframework.context.ApplicationEventPublisher;import java.util.concurrent.Executor;final class DelegatingEventPublishingListener implements Listener {DelegatingEventPublishingListener(ConfigService configService, String dataId, String groupId, ApplicationEventPublisher applicationEventPublisher, Executor executor, Listener delegate) {this.configService = configService;this.dataId = dataId;this.groupId = groupId;this.applicationEventPublisher = applicationEventPublisher;this.executor = executor;this.delegate = delegate;}
}
复制代码
可以看到,在创建DelegatingEventPublishingListener
对象的时候,会传入一个线程池Executor
,以及一个ApplicationEventPublisher
(其实就是DeferredApplicationEventPublisher
)
然后再看看CacheData.safeNotifyListener()
方法做了什么操作
private void safeNotifyListener(final String dataId, final String group, final String content, final String md5, final ManagerListenerWrap listenerWrap) {final Listener listener = listenerWrap.listener;Runnable job = new Runnable() {public void run() {ClassLoader myClassLoader = Thread.currentThread().getContextClassLoader();ClassLoader appClassLoader = listener.getClass().getClassLoader();try {if (listener instanceof AbstractSharedListener) {AbstractSharedListener adapter = (AbstractSharedListener)listener;adapter.fillContext(dataId, group);LOGGER.info("[{}] [notify-context] dataId={}, group={}, md5={}", name, dataId, group, md5);}// 执行回调之前先将线程classloader设置为具体webapp的classloader,以免回调方法中调用spi接口是出现异常或错用(多应用部署才会有该问题)。Thread.currentThread().setContextClassLoader(appClassLoader);ConfigResponse cr = new ConfigResponse();cr.setDataId(dataId);cr.setGroup(group);cr.setContent(content);configFilterChainManager.doFilter(null, cr);String contentTmp = cr.getContent();listener.receiveConfigInfo(contentTmp);listenerWrap.lastCallMd5 = md5;LOGGER.info("[{}] [notify-ok] dataId={}, group={}, md5={}, listener={} ", name, dataId, group, md5,listener);} catch (NacosException de) {LOGGER.error("[{}] [notify-error] dataId={}, group={}, md5={}, listener={} errCode={} errMsg={}", name,dataId, group, md5, listener, de.getErrCode(), de.getErrMsg());} catch (Throwable t) {LOGGER.error("[{}] [notify-error] dataId={}, group={}, md5={}, listener={} tx={}", name, dataId, group,md5, listener, t.getCause());} finally {Thread.currentThread().setContextClassLoader(myClassLoader);}}};final long startNotify = System.currentTimeMillis();try {if (null != listener.getExecutor()) {listener.getExecutor().execute(job);} else {job.run();}}...
}
复制代码
这里看到,safeNotifyListener
是将事件广播给所有的Listener
,然后有一段及其重要的代码段,它就是导致LinkedList
出现并发使用的原因
listener.getExecutor().execute(job);
复制代码
这里还记得刚刚说过的DelegatingEventPublishingListener
对象在创建之初有传入Executor
参数吗?这里Listener
调用Executor
将上述的任务调入线程池中进行调度,因此,导致了DeferredApplicationEventPublisher
可能存在并发的使用
错误复现
public class DeferrNPE {private static LinkedList<String> list = new LinkedList<>();private static CountDownLatch latch = new CountDownLatch(3);private static CountDownLatch start = new CountDownLatch(3);private static class MyListener implements Runnable {@Overridepublic void run() {start.countDown();try {start.await();} catch (InterruptedException e) {e.printStackTrace();}list.add(String.valueOf(System.currentTimeMillis()));latch.countDown();}}public static void main(String[] args) {MyListener l1 = new MyListener();MyListener l2 = new MyListener();MyListener l3 = new MyListener();new Thread(l1).start();new Thread(l2).start();new Thread(l3).start();try {latch.await();Iterator<String> iterator = list.iterator();while (iterator.hasNext()) {System.out.println(iterator.next());iterator.remove();}} catch (InterruptedException e) {e.printStackTrace();}}}
复制代码
最终修正
由于是非线程安全使用在并发的场景下,因此只能更改上层nacos-spring-context
的容器使用,将原先的非线程安全的LinkedList
转为线程安全的ConcurrentLinkedQueue
转载于:https://juejin.im/post/5cee66f66fb9a07eeb138b55
记一次Nacos的issue修复之并发导致的NPE异常相关推荐
- 记一次触发器定义者不同导致的sql异常TRIGGER command denied to user 'XXX' @'%' for table '...
记一次触发器定义者不同导致的sql异常 触发器:在执行某一类型的sql后触发其他已经提前写好的sql 先上图 这是打印出的错误日志信息 图中weidong_rwser_test_temp为数据库连接账 ...
- 记一次失败的MySQL修复经历,报错信息:Tablespace X was not found at X;Set innodb_force_recovery=1 to ignore this
事情的起因 首先前一天晚上我跑了一个not in语法的SQL语句,因为正好是下班了就让它自己跑去了,结果一不小心造成了笛卡尔积,第二天发现空间爆了,于是开始正常标准操作:暂停,查看进程,发现InnoD ...
- 阿里的nacos+springboot+dubbo2.7.3集成以及统一处理异常的两种方式
在网上很多关于dubbo异常统一处理的博文,90%都是抄来抄去.大多都是先上一段dubbo中对于异常的统一处理的原码,然后说一堆的(甚至有12345,五种)不靠谱方案,最后再说"本篇使用的是 ...
- 360修复导致服务器,桌面安装360软件修复漏洞补丁导致桌面TC端无法登陆,FC端VNC登陆一键修复显示HDC不可达...
问题描述 桌面虚机安装360软件以后打补丁,触发桌面虚机重启以后TC端无法连接到桌面,FC登陆VNC相应的虚拟机通过桌面云修复工具一键修复到33%,提示HDC不可达. 告警信息 处理过程 在360服务 ...
- 使用Nacos项目jar包启动抛出的yml异常
记录一下项目jar包启动时一直抛出nacos yml编译错误的问题 一开始抛出yml的问题,是编码问题,但是又不知道Nacos里怎么配置编码格式. 所以我只能把nacos中配置文件里的注释含泪删除调试 ...
- 修复Jscript(IE浏览器脚本引擎)异常
今天IE出现不能处理JS脚本的问题,一开始怀疑IE坏了,但不知道具体坏在哪. 想到了重装IE,上网搜"重装IE",结果给出的方法非常复杂,手头又没有Windows XP安装盘 想想 ...
- 修复iPhone系统故障导致的黑屏
iPhone手机屏幕黑屏,如果没有硬件问题的话,那就是手机系统出现了问题,需要对iOS系统进行修复,我们可以使用iTunes,打开软件,将手机接入电脑后,选择"更新"或者" ...
- java response下载docx,报文件损坏是否修复,ContentLength导致的
使用http response编写附件下载功能,当附件是docx时,用office的word打开,会出现文件损坏,是否修复的错误,点击修复又能正常打开. 首先,仔细对比文件发现,下载下来的文件比服务器 ...
- 装完nvme固态经常蓝屏_Win10 KB4586853修复NVMe SSD导致蓝屏死机问题
微软此前发布支持公告确认当Windows 10连接NVMe协议的固态硬盘时,有可能会因为渠道问题引发蓝屏死机.该问题理论上主要影响的是附带Thunderbolt接口的设备,当用户将固态盘通过该接口连接 ...
最新文章
- Complex Instance Placement
- 面试投行的20个Java问题
- WCF扩展:行为扩展Behavior Extension一
- 安装ubuntu20.4+gtx1050+cuda11.3
- Facebook上的一道题,超过50万的评论和1万3500次分享
- git merge 冲突_卧槽!小姐姐用动画图解 Git 命令,这也太秀了吧?!
- 如果我用你待我的方式来待你 恐怕你早已离去
- WebAPI Get
- 服务器放在机柜_机架式服务器和塔式服务器有区别吗
- 是的,我开始做这么一件事了
- centos Linux 上 怎么命令行安装和卸载QQ
- 《Git与Github使用笔记》分享3款Git可视化工具
- matlab如何从视频中分离音频文件,如何从视频中分离音频文件 值得收藏
- windows便签快捷键_超级实用的Windows快捷键
- 常见js针对浏览器之间的兼容问题
- background-size属性详解
- android 调取数字键盘,Android自定义键盘的实现(数字键盘和字母键盘)
- python自动排版公众号_自制微信公众号一键排版工具
- 【技术文档】《算法设计与分析导论》R.C.T.Lee等·第6章 剪枝搜索方法
- RoboCup Rescue Simulator Tutorial core
热门文章
- el-table-column中格式化判断数据为空则显示指定内容
- MyBatis中提示:Invalid Bound statemnet(not found )com.
- 赶紧下载SublimeText并快速设置代码自动补全,效率大大提高
- Vue实现仿音乐播放器项目总述以及阶段目录
- BJUI实现点击按钮弹窗,提交到后台action后回显数据流程整理
- 代码夹带是洪水猛兽吗?
- 4、SpringBoot 配置和使用定时任务
- java 序列化 例子_Java序列化和反序列化例子
- 神策数据正式成为国家级信创工委会成员单位
- 如何使用Java代码给图片增加倒影效果