一个历时五天的 Bug
一个程序员在没有成长成为架构师之前,几乎都要跟 Bug为伴,程序员有很多时间都是花在了查找各种 Bug上。
我印象深刻的一个Bug, 是一个服务器网络框架无锁队列的 Bug 。那个 Bug 连续查找了五天的时间,才最后定位出来。
当时我们的分布式存储系统出现了性能瓶颈,定位后发现瓶颈是在服务器网络框架上,所以我们决定为此替换一个最新研发的网络框架。这个新的网络框架为了追求高的性能,采用了无锁队列的设计。
第一天编码测试完成后。测试环境跑,完全正常,特地搞了一堆 log 来重放请求,程序跑得特别欢快。解决了当时的性能瓶颈,感觉特别的开心,但好景不长,服务部署到现网环境,跑不到一个小时,就 coredump 了。
尝试上线了几次,每次都是跑半个多小时,就 coredump 。
当时的第一反应是新网络框架的问题,这是很直觉的反应,但很快就产生了怀疑,因为这个框架也有其它模块在用,也没产生问题,当时觉得诡异了。
仔细查看了代码的修改记录,特定检查了版本管理系统的 log, 做了代码 diff, 确定确实只有这部分的修改。凭着经验,我说服自己,这个时候应该坚信最明确的逻辑,不要走到其他歪路上去。
第二天,我把这个框架单独拎了出来,特地写了一个测试的模块,并用测试代码生成了一堆的请求数据,发送给测试模块。它疯狂的运转起来,跑了一个多小时,跑得很欢快,一切正常,啥 Bug 都没有。
开大了并发,继续压,依然没有问题。懵逼了!不知咋回事。再次小心得灰度系统上线,跑不到一个小时还是 coredump 了, 这个时候,我开始怀疑人生了,这个是咋回事了,都想砸键盘了都。
然后我冷静了下来,经验告诉我,这时应该按照正常流程完整地跑一遍测试模块。于是我把那个测试模块打包成了现网模块,切走了一台现网机器的流量,把测试模块给上线到了一台现网机器。
之后用工具往现网机器发送数据,不到一个小时,coredump 了。终于复现了这个 cordedump, 那一刻犹如哥伦布发现了新大陆,简直欣喜若狂啊。这个时候已经是第三天了。
我复现了 Bug,但依然没办法定位出具体的原因。coredump 出来的栈是全乱的,没有任何价值,接下来,就开始用 log 跟踪法了。
我依据数据的流转过程,在每个关键点,都打上 log,log 包含了所在的函数,行数,程序逻辑的编号,全部的关键数据等等。我仔细地设计了这个log,争取打得不多不少,太多容易看晕头,而且太多无效的信息,会掩盖了真正的问题;太少,信息不足,又不足以判断,所以这种情况下打 log 也是个艺术活。
通过精心设计的 log, 终于发现数据在一个特定的环节混乱后,程序就一定会 coredump。分析log , 发现数据包在最后时刻是完整的,但包似乎出现了乱序和重复。这个时候,才开始意识到可能是无锁队列的问题,因为只有队列出问题,包的进出顺序才会乱掉。
然后又花了半天的时间,专门为无锁队列写了测试用例,用数据疯狂地怼。在测试环境,依然一切安好,但上线到正式环境,压测半小后,终于挂了。终于看到了胜利的曙光!这个时候已经是第四天了!
到现在已经很明确是无锁队列的问题了。但这个数据结构的代码不到 200行。我拉了两个同事一起 review,都没发现问题。但就是 coredump 了。
奇葩了,又陷入了人生怀疑,开始怀疑内存,怀疑 cpu,结果换了机器,还是一样。
后来,仔细地对比了现网环境和测试环境的区别,机器类型,操作系统版本都一样。然后编译器?咦!编译器?上去看了一下,结果发现编译器的版本不一样!这段时间我所使用的现网编译环境升级了新的 gcc 版本,但测试编译环境,还是旧的版本的。(这个也比较坑)
当时的直觉是肯定跟编译器相关,但代码都一样,难道是编译器bug?不可能吧 ?! 后来想,不如将它们转换成汇编看看吧。于是用两个版本的编译器将C的代码各自转换成了汇编。然后diff 汇编代码,哇!发现真的有一行是不同的!
后来自己分析对比,发现是因为我们开启了gcc最高级别的代码性能优化,不同版本的 gcc 在一些没有特定依赖的语句上的优化是不同的。
说人话,就是有一段代码,如果加了锁,两个版本的编译器下,都会产生一样的汇编,如果没有加锁,代码有一行的顺序被调整了,当然,从编译器优化的角度讲是对的,是我们使用姿势不对。
但无论怎么样,终于找出了这个问题。苍天啊! 找了五天呀! 最后当然是开开心心地上线了。
查这个bug 确实花费了很多的时间,不过也是没办法,你不解决bug,就不能上线,但中间也收获不少,特别是对编译器优化有了很深的印象,也算是为自己的查 Bug 能力,又贡献了一波经验吧。
对于 Bug , 我分享下自己的一些认识和建议。
面对 Bug 的态度
只要你持续地写代码,就一定会持续的产生 Bug,所以第一个事情是要摆正对 Bug 的心态。我遇到过两个极端。
第一个极端。
遇到过一个leader,对系统质量相当重视,对我们写的代码要求很高,每次设计并写完一个新的系统,他喜欢跟你算这次的系统上线,产生了多少次故障,这半年时间产生了多少个 Bug,每个 Bug 的影响范围如何。
我们一堆人被搞得特别累,战战兢兢,到后面,大家都比较排斥去做优化,去重构代码,只求无过,不求有功了。所以,我觉得这种方式不好,对 Bug 带着一种比较包容的态度去看待,可以减少不少的心理负担。
第二个极端。
后来去了一个新的团队。新的团队很重视业务和工程迭代的速度,所以对代码质量和 Bug 的容忍度很高。如果是一个新上线的业务,是默许 Bug 存在的。
这种对质量过于松散的要求,在后面也带来不好的影响。大家对 Bug 太免疫了,以至于出现 Bug 和 故障的时候,大家都不够紧张。系统质量有一段时间出现比较大的问题,还因此被部门经理特训了一番,后面通过各种措施,才慢慢提高了整体的系统质量。
上面两种极端都不可取,应该很重视 Bug,尽量避免 Bug,但也不应该唯 Bug 多少论业绩。
具体到 Bug 的查找上,我说说我的一些经验。
Bug 的复现
我把 Bug 分为可重现的 Bug 和不可复现的 Bug 。
对于可重现的 Bug , 查找起来比较容易,比如可以用”二分查找“的方式,从模块层面开始定位起,每次折半,每次折半地缩小范围,一直到代码层面。
在代码层面,遵循一些常用的原则:比如看到内存拷贝,直觉上要想到内存越界;看到数组,就要考虑是否索引越界;看到指针,就要考虑是否正确解构;看到多线程,就要考虑是否线程安全;
对于不可重现的 Bug , 第一步就是要把它重现出来。有时候特别的难,特别是并发形态下产生的 Bug ,出现的时机和触发条件都不清楚。对于这种 Bug ,只能通过各种尝试去复现它。
比如将多并发调整为单并发的方式,看能否复现,如果可以复现,就可以转化为可复现的 Bug ,用”二分查找“的方式去排查。
如果不能复现,那极大概率是并发问题。这个时候最好先停止排查,仔细分析程序在并发状态下可能出问题的点。大部分并发问题的根源,是互斥数据没有被正确读写,或者一些共享状态被错误修改。
静态代码检查
利用coverity等代码检查工具进行代码的静态检查可以发现很多潜在的问题,而且修复的成本很低。团队后来引入了这个检查工具,确实带来了不错的效果。类似变量未初始化,疑似的内存越界等都有可能被检查出来。
编译器的 warning。有些同学一开始的时候对 warning 不重视。我们团队早期也遇到过这个情况。
那时候产品迭代的速度很快,所以大家写完代码,能够编译通过,就进行各种测试,然后准备上线了。一开始的一两个warning, 不太理会。后面发现越积越多,到最后终于成为一个不得不解决的问题。
部门还为此特地立项,来消除warning。先是在内部多次强调了这个理念,然后从基础库,基础模块开始实施,基础代码部分统一 fix warning, 然后开启编译器把 warning 当 error 的开关。完成之后,再逐步地推业务模块进行修改。反正折腾了好一段时间。
工欲善其事必先利其器
代码 Bug 出现的时候,善用一些排查工具可以极大得提升效率。比如对于C/C++ 的 gdb 调试,内存泄漏时候 valgrind 的检测,linux 下面用 perf top 来析 cpu 的消耗等。
一开始的时候,我对这些工具不重视,老是觉得真正使用的时候,去查文档就行。后面才发现,用工具查着问题时候,遇到不会用的命令或功能,再去查文档,是个痛苦的事情,来回切换的开销也使得效率低下。
后面就对这些辅助工具的使用重视了起来。,专门花时间去学习和练习使用,反而提升了不少的效率
打 log 的艺术
很多时候,出现一个 Bug,未能定位出来,需要打上更多额外的 log 来辅助排查。一开始的时候,是想到一点,打一个 log,后面发现这么做没有章法,逻辑不清晰, 排查效率低下。
后来学会了,遇到 Bug 后,先在脑子里面分析一番,然后花一两个小时详细地设计 log 的格式和打 log 的位置。发现这种方式对查问题的效率提升很大。
所以遇到 Bug 的时候不要急躁,先静下来心来分析,在脑海里尽力重现出完整的运行逻辑,然后仔细地进行 log 设计,包括 log 包含的字段,打 log 的点等。这样能极大的提升排查问题的效率。
结语
Bug 是程序员最不愿意用面对,但又经常出现的一个 “诡异生物“。对待Bug 要有正确的心态,最好不要跟业绩强绑定,也不能太过于疏忽。Bug 的排查是个很复杂的事情,每个人都有自己的方式和做法,如果你有好的做法和建议,也欢迎在留言区分享给大家!
一个历时五天的 Bug的更多相关文章
- Spring 循环引用(一)一个循环依赖引发的 BUG
Spring 循环引用(一)一个循环依赖引发的 BUG Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Spring 循环 ...
- android一个下拉放大库bug的解决过程及思考
android一个下拉放大库bug的解决过程及思考 起因 项目中要做一个下拉缩放图片的效果,搜索了下github上面,找到了两个方案. https://github.com/Frank-Zhu/Pul ...
- 移动端H5页面开发,碰到一个字体变大的BUG
移动端H5页面开发,碰到一个字体变大的BUG webkit内核下,对不定高宽的元素可能会放大其字体.那么,就可以设置一个max-width:或者使用-webkit-text-size-adjust: ...
- 一个暂时无法理解的bug
BUG bug的存在是不可避免的 一个静态变量,改成绝对地址之后可以生成静态页面.但是主页有一个用了静态变量的超链接就不能用, 最后我加了一个相对地址的静态变量,可以解决这个问题.
- 记一个界面刷新相关的Bug
今天遇到一个比较有意思的bug, 这里简单记录下. Bug的症状是通过拖拉边框把我们客户端主窗口拖小之后,再最大化,会发现窗口显示有问题, 看起来像是刷新问题, 有些地方显示的不对了. 这里要说明的是 ...
- 一个int类型引发的bug
一.引言 今天我在项目开发中,遭遇了一个莫名其妙的问题,概括加抽象后形成如下问题:在使用MyBatis的XML语句实现Dao层接口 List<Person> selectBySome(@P ...
- 一个Dotnet数据框架的bug
好久没写C#代码了,今天在维护公司老项目时,偶然发现一个BUG.记录一下,后面的同学就不要踩坑啦. -------------------------------------------------- ...
- spark 2.3 导致driver OOM的一个SparkPlanGraphWrapper源码的bug
背景 长话短说,我们部门一个同事找到我,说他的spark 2.3 structured streaming程序频繁报OOM,从来没有坚持过超过三四天的,叫帮看一下. 这种事情一般我是不愿意看的,因为大 ...
- 记录一个chrome 65渲染的bug
前段时间发现一个chrome 65+的BUG(chrome已更新到66,BUG仍然存在),一个元素同时使用了以下样式(失去焦点和css3的Z轴平移0deg),渲染异常 /*bug style*/ fi ...
随机推荐
- 如何删除发布服务器distribution
在建立发布服务器后自动生成distribution数据库为系统数据库,drop无法删除,实际删除方法如下:在“对象资源管理器”-“复制”上点击右键,选择“禁用发布和分发”,依次执行即可完成该系统数据库 ...
- Python_004(列表和元组)
一.列表 1. 列表: 列表的创建:li = [],列表中可以放置字符串,元组,列表,字典,列表等各种数据类型,32位的Python可以存放2^32个数据 2. 列表的索引和切片 列表的索引:格式ls ...
- Android 一键分享功能简单实现
import java.io.File;import java.util.ArrayList;import java.util.List; import android.content.Context ...
- iOS逆向一个APP指令集
一.脱壳获取.app文件 1.查询壳有没加密 otool -l mac-o文件 | grep crypt 2.Clutch -i Clutch -d num 3.脱壳后的位置 /private/v ...
- isset、empty 误区
isset() 常用来判定变量是否存在 但是当变量赋值为NUll时候,返回值一样是FALSE 而实际上变量是存在的,与实际情况不符,判定错误. empty() 用来判定变量或者类的属性值是否为等 ...
- Js DOM 修改 css Style
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- Delphi 文件转换Base64
uses EncdDecd; function FileToBase64(FileName: string): string; var MemoryStream: TMemoryStream;beg ...
- ag-grid 表格中添加图片
ag-grid是一种非常好用的表格,网上搜索会有各种各样的基本用法,不过对于在ag-grid 表格中添加图片我没有找到方法,看了官方的文档,当然英文的自己也是靠网页翻译,最后发现有这么一个例子,我知道 ...
- 【ABAP系列】SAP ABAP 总结常用术语简称解析
公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[ABAP系列]SAP ABAP 总结常用术语简 ...
- hdu6576Worker(最小公倍数)
Problem Description Avin meets a rich customer today. He will earn 1 million dollars if he can solve ...