CAT偶现NPE的问题
1、背景
我们公司的调用链系统是基于大众点评的CAT客户端改造的,服务端完全有自己设计开发的。在是用CAT客户端收集dubbo调用信息的时候,我们发现了一个CAT偶现NPE的bug,该bug隐藏的很深,排查极其困难,导致了我们公司4期线上故障,造成了很大的资产损失。
接下来让我们看一下,这个NPE的发现与解决!!
2、问题描述
该问题最先发现于营销的应用,他们的发布之后立刻一台机器出现cat-redis埋点的客户端大量抛NPE,表象是Cat.newTransaction()这行抛了NPE。当时花了1周排查无果,最终大家倾向于认为是jar包冲突。因为这个和jar包冲突很像,单机器,时有时没(jar包会被仲裁)!
第二次出现是在交易的应用中,也是应用刚发布就出现了NPE的问题,这次是表象是cat-redis埋点的客户端在Cat.newTransaction()过程中出现了NPE,又引起了一起线上故障,造成了好几万的资产损失。。。

3、问题排查过程
重启了排查过程,这次一定要打破砂锅问到底!!!
排查回到原点,依然没有头绪,因为现象很简单就是m_producer为null,为什么null,看初始化的过程就是一个SPI,而且是内存中加载的就想spring加载一样,不太可能是初始化失败;但是有可能是实例被destroy,也可能因为时序问题,先调用getProducer后去初始化。



因为不知道如何复现这个问题,只能硬着头皮在可疑的出现问题的地方打了几行日志,然后李文嘉同学写了个python脚本不断在重启应用、kill应用、然后在重启。。。因为一次start/stop周期就要好几分钟,又正好周五打完收工!!!!
马上又是忐忑的周一了,因为周末看代码还是一点头绪都没有,不过好消息是我们发现脚本不停start-stop过程中(2000多次重启)出现了24次一样的Cat.newTransaction的NPE现象,确实是个好消息。
但是问题是我和李文嘉同学的采样都是错的,我们打日志的面积又不够,所以消息是好的,线索还是没有。接下来我一鼓作气在snapshot版本中打满了日志,而且以CatNPE开头,保证grep出来都是自己想要的日志!!
打完包,脚本run起来,又等了一个晚上,第二天早上来看日志,又有新的收获发现最开始不怀疑的地方setContainer方法报了NPE,但是问题为什么throw RunTimeException 为什么没抛出来了(被哪里catch了)。不过悲剧的是,我竟然忘记在catch中打印异常堆栈了啊(智商不够用。。。)

于是又打包,脚本再run起来,又等了一个晚上,终于有一点收获了,定位到了包异常的位置,MessageIdFactory.initialize()方法中读取m_byteBuffer.getLong()的时候报错,java.nio.BufferUnderflowException 这个异常是意思是可读字节数不够,举例:buffer中的limit=cap=20, position=16,此时getLong读取8个字节,由于字节数不够(pos到limit之间只有4个字节)会出现BufferUnderflowException的异常

于是新的问题有来了,读取自己会报错,有的时候报错,多数时候正常呢???
最开始我怀疑是创建文件cat-appname.xml问题,因为创建文件可能会有权限问题,然后读取到了不完整的文件内容(当时严重怀疑这个)。于是在往这个方向找问题,但是交易的应用实在太笨重了,重启很麻烦。于是自己写了demo和脚本,打上日志不断的重启复现问题。正当我信心满满的时候,跑了2天脚本依然没有任何收获。。。所以打算放弃这条路。。。
期间和cat的作者吴其敏联系了一下,他怀疑是并发初始化问题

于是开始往线程并发方向开始找问题,期间把m_byteBuffer所有成员变量都输出来看,多次debug发现pos的每条语句结束指都不太一样(有重大线程安全问题的嫌疑);另一个重大发现是debug的时候居然有很高的概率复现问题,但是RUN的时候复现的概率低。。。。
pos limit cap
init 0 20 20
limit() 0 20 20
getInt() 4 20 20
getLong() 12 20 20

这个时候我严重怀疑是m_byteBuffer对象被多个线程操作了,于是找到了saveMask()有修改m_byteBuffer的(因为write方法会修改pos)。马上Find Usages 立刻发现有个异步线程在死循环的调用saveMask方法

这就能解释了为什么debug的情况下有很大的概率能复现问题,因为main线程被断点了,异步run方法还能继续执行,调用saveMask方法能修改m_byteBuffer的position变量,然后主线程main初始化的时候pos被修改成了16,getLong读取8个字节接报错了!!!
不在debug环境下的时候,Main的初始化速度一般是能早于异步线程ChannelManager.run的执行完成,所以这就能解释大部分情况下是没有问题了!!!!
至此真相大白,所有的问题和现象都能解释的通了!!!!!!
2.2源码分析
MessageIdFactory.initialize()
public void initialize(String domain) throws IOException {
...... 省略其他代码
m_markFile = new RandomAccessFile(mark, "rw");
m_byteBuffer = m_markFile.getChannel().map(MapMode.READ_WRITE, 0, 20);
if (m_byteBuffer.limit() > 0) {
// 断点在此处,position变量很容易被异步线程修改掉,导致pos=16的时候,getLong就会报错BufferUnderflowException
int index = m_byteBuffer.getInt();
long lastTimestamp = m_byteBuffer.getLong();
....
}
saveMark();
}
ChannelManager.run
@Override
public void run() {
while (m_active) {
// 异步线程执行saveMark,会向m_byteBuffer中write值
m_idfactory.saveMark();
.....省略其他代码
}
}
3、问题解决
3.1修改
这个bug 我觉得并不是线程安全问题,而是main线程初始化一定要先于异步线程。所以增加一个volatile init变量,在初始化完成之后修改init=true
m_markFile = new RandomAccessFile(mark, "rw");
m_byteBuffer = m_markFile.getChannel().map(MapMode.READ_WRITE, 0, 20);
if (m_byteBuffer.limit() > 0) {
int index = m_byteBuffer.getInt();
long lastTimestamp = m_byteBuffer.getLong();
}
saveMark();
m_init = true;
异步线程ChannelManager增加一个是否初始化完成的判断
public void run() {
while (m_active) {
// 增加是否初始的判断
if(m_idfactory.isInit()){
m_idfactory.saveMark();
.....
}
}
}
4、开源回馈
本问题已经提交到cat官方issue和pr
CAT偶现NPE的问题的更多相关文章
- 偶现bug如何处理?
请先允许我对此类bug进行吐槽,相信做测试的同学都碰见过这种bug! 我们在测试过程中经常会碰见一类很头疼的bug,就是偶现性的bug,所谓偶现性,是相对于必现而言,这类bug有些可以有重现路径,但是 ...
- Ubuntu16.04下写的Qt程序,调试时没问题,运行时偶现崩溃 (需要在运行时生成core dump文件,QMAKE_CC += -g)
记录一下 Ubuntu16.04下写的Qt程序,调试时没问题,运行时偶现崩溃 需要在运行时生成core dump文件 首先在pro结尾里加入 QMAKE_CC += -g QMAKE_CXX += - ...
- 【Golang】嗅探抓包,解决线上偶现问题来不及抓包的情况
背景 测试群里经常看到客户端的同学反馈发现了偶现Bug,但是来不及抓包,最后不了了之,最近出现得比较频繁,所以写个小脚本解决这个问题. 实现思路 实现的思路比较简单: 抓包 存日志 做日志管理 具体实 ...
- 程序员的踩坑经验总结(一):如何把Bug的偶现变必现
程序员的踩过的坑也是可以分类的,很常见又很难解决的一类是偶然的现象,表现起来比较怪异. 而把一个问题Bug的偶现变成必现,是开发人员的一种能力.我认为也应该是测试人员的一种能力,但是各个公司要求不一样 ...
- 小程序部分机型上一个诡异的偶现bug
如上图所示:开始的时候进到下单页面,价格是0,当选中了商品产生价格的时候,生成的价格如 ¥150,这个时候会只露出¥1以及一小半的5,后面的都被遮挡住了. wxml里是这样的写的 <view w ...
- AF引起的camera偶现卡顿问题
相关log如下: 01-01 08:04:26.299 867 3220 E Camera2Client: syncWithDevice: Camera 0: Timed out waiting sy ...
- WEB端线上偶现问题如何复现?
1.抓取出现问题的日志,还原操作过程,分析 每个过程中数据是否正常?是否有重复请求 2.询问当时操作员执行了哪些操作,尽可能多的了解事发经过 3.通过查看日志,数据库等信息,找到发生问题的节点, 比如 ...
- 网页偶现性崩溃-chrome
简介: 项目前台框架:Angular2 + Bootstrap(日期等组件) + Echarts + 响应式(包括页面.字体缩放:rem) chrome版本:多个版本测试均有此问题. 表现: 订单详情 ...
- iOS开发之使用UICollectionView实现美团App的分类功能【偶现大众点评App的一个小bug】
郝萌主倾心贡献,尊重作者的劳动成果,请勿转载. 假设文章对您有所帮助,欢迎给作者捐赠,支持郝萌主,捐赠数额任意,重在心意^_^ 我要捐赠: 点击捐赠 Cocos2d-X源代码下载:点我传送 游戏官方下 ...
随机推荐
- 自动构建工具Grunt
摘要: 大部分项目在部署之前都需要做的就是js.css文件的压缩.合并,以及一些文件的错误检查,甚至是将LESS文件转换成css文件,coffeescript文件转化成js文件等等.但是项目开发是分迭 ...
- 使用monkey技术修改python requests模块
例如请求前和请求后各来一条日志,这样就不需要在自己的每个代码都去加日志了. 其实也可以直接记录'urllib3.connectionpool' logger name的日志. 修改了requests ...
- [Algorithm] Beating the Binary Search algorithm – Interpolation Search, Galloping Search
From: http://blog.jobbole.com/73517/ 二分检索是查找有序数组最简单然而最有效的算法之一.现在的问题是,更复杂的算法能不能做的更好?我们先看一下其他方法. 有些情况下 ...
- IE8兼容性调试及IE 8 css hack
做网站开发,一提到IE,就会让人头大,有一肚子的牢骚要发:微软为什么不跟着国际标准走呢,总是独树一帜,搞出那么多问题来.IE的firebug调试工具也不太好用,尤其是低版本的IE,更是让人头疼. 最近 ...
- iOS开发-NSDictionary
判断一个字典中是否存在某个key,有两种方法: 方法一: if ([dictionary allKeys] containsObject: key]){ // cotains key operatio ...
- 使用Bind搭建DNS服务
DNS域名解析服务(Domain Name System)是用于解析域名与IP地址对应关系的服务,功能上可以实现正向解析与反向解析: 正向解析:根据主机名(域名)查找对应的IP地址. 反向解析:根据I ...
- AESDK开发之UI消息响应
UI创建: 在该入口下 case PF_Cmd_PARAMS_SETUP: //.... break; 必须在末尾指定UI数目,UI数目一般是枚举,如果和枚举长度不一致也会报错.所以最好是直接修改枚举 ...
- setTag,getTage复用
radioButtons = new RadioButton[rgMain.getChildCount()]; //遍历RadioGroupfor (int i = 0; i < radioBu ...
- MyBatis中Like语句使用总结
原生写法 eg: select * from user where username like '%${value}%' 注意: ${value}里面必须要写value,不然会报错 oracl ...
- php pear包打包方法
一)首先下载工具onion 浏览器打开,服务器上wget测试无法正常下载 地址:https://raw.github.com/c9s/Onion/master/onion 二)在临时目录下,建立相关目 ...