乐趣区

关于后端:意想不到这个神奇的bug让我加班到深夜

给大家分享一个近期解决的线上问题,起因是这样的,近期参加公司的一个我的项目,工程量很大,代码编写测试过后终于到了缓和的上线时刻。

我的项目上线

上线前照例局促不安了一番,因为工程量比拟大,预估可能不会很顺利,但还不至于到了祷告服务器不要出 bug 的境地,bug 对于程序员来说几乎是粗茶淡饭,没有 bug 反而可能会嘀咕半天,这都是职业病,没治。

缓和了一会儿,我屏气凝神,点了上线按钮,那一刻几乎就像在点核按钮一样,惟恐点上来后服务器会轰的一声炸掉。

后果一切正常。。。

这不对啊,这时博主的职业病又犯了,这么大的改变不会这么顺利吧,内存、CPU、tp99 耗时一切正常,这太不失常了吧。

说曹操曹操就到

就在博主想为什么没有 bug 时,bug 几乎就像听到了我的号召一样如约而至,博主过后甚至在想为什么幻想中的 500 万彩票就这么不听话呢?

(bug 妹妹:“程序员哥哥,我来啦”;

500 万妹妹:“不,程序员哥哥,不要说门,窗户都没有。。。”)

上线最后一切正常,问题就出在了接下来的一段时间里。

在接下来的工夫里,tp99 耗时就像通货膨胀一样不可遏制的一路上扬,线上支出就像股市一样不可遏制的一路重挫。

这时博主的心田反而虚浮了很多,没错,就是这个味儿,还是相熟的配方还是相熟的滋味,I know it。

废话少说,连忙回滚,线上恢复正常后接下来就是问题排查了。

排查问题

从监控上看,一次申请的解决工夫会越来越高,那么显然问题的要害就是定位耗时呈现在了哪段代码上。

没有方法,只能一点一点的去找监控了,幸好代码中监控比拟丰盛,一番梳理后最终锁定在了这样一行代码:

// 监控代码
obj a = b;
// 监控代码

这段代码实质上是在干什么呢?很简略,就是对象的 copy,而且在咱们的实现中还是浅 copy,也就是仅仅 copy 了一些字段。

从监控看就是这行代码导致了 tp99 耗时一直上涨。

这。。这怎么可能呢?简简单单的一个对象拷贝居然会让耗时上涨那么多,而且还是随着工夫迟缓上涨,这也太神奇了吧。

当人遇到本人不能了解的问题时通常会归因于内部因素,博主也不能免俗。

接下来就是狐疑人生的时刻。

不会是监控有问题吧,不会是编译器的起因吧,不会是硬件的起因吧,不会是天气的起因的吧,总之不是我的起因。

一番考虑后最终感性战败了本人,哪有那么多起因,在没有其它证据下目前看就是这个对象拷贝导致的。

那么为什么一个简略的对象拷贝会导致 CPU 耗费越来越高,耗时越涨越多呢?

这里的关键在于意识到这一点,既然随着工夫的推移耗时会越来越高,那么很显然是某个 全局 性质的数据随着申请的解决越沉积越多,而出问题的这个对象应用到了这个全局数据。没错,就是这样,终于要见到曙光啦,哈哈,冲动!

这就解释了为什么这个对象随着工夫的推移就和美债一样越滚越多,变得越来越宏大了,尽管美国政府可能没打算还美债,然而 CPU 拷贝越来越多的数据必然导致耗时越来越高。

找出 bug

既然明确了方向,接下来就有针对性了。

首先去看一下这个对象都有哪些成员变量,对于内置类型像 int、bool 之类必定不会有问题,因为这些类型的变量大小是固定的,须要留神的就是这种 vector、set 之类的容器。

最终通过一番查看后判定问题就是出在了某个 vector 成员变量上,同时也验证了上述猜测。

水落石出

问题是这样的。

这个对象的某个 vector 成员变量每次在解决申请时都要用另一个对象 (假如为对象 A) 的数据来进行初始化,就像这样:

在每次解决一个申请之前,A 持有的 Data 都会被 push 一些特定的数据。

而零碎为了优化内存调配开销,对象 A 被放到了内存池中,就像这样:

因为对象被放到了内存池,因而对象 A 是不会被开释的,这就让对象 A 无形中变为了全局性质的对象

当初,有的同学可能曾经发现问题了,那就是,如果对象 A 在放回内存池后没有清空持有的 Data,那么就会导致这样的一个问题,那就是 A 持有的 Data 随着每个申请的到来一直的被 push 数据,这就会导致 A 持有的 Data 就像泡沫一样越吹越大,相应的 Obj 对象持有的 vector 也会越来越大:

在这种状况下拷贝 Obj 对象必然要拷贝持有的 vector,因为 vector 越来越大,因而耗费在拷贝上的工夫也越来越多,应用内存池本意是好的,但 因为应用完后遗记清理其保留的旧数据反而造成了内存透露

经验教训

这次的问题从代码编写角度看是这样的,对象 A 中 Data 字段的清理工作没有放在对象 A 的 Clear 函数,反而要靠使用者本人清理,因为代码非常复杂极其容易疏漏,博主就在这里踩坑了。。。

因而,总结下来经验教训就是:

  1. 向类中 增加新成员时肯定要留神其清理工作,应用前肯定要确保该成员是簇新的,外面没有之前旧存货。
  2. 代码中增加必要的监控,有利于排查问题
  3. 测试的工夫要略微长一些,否则相似这里的问题不容易裸露
  4. 程序员遇到 bug 是很失常的,大胆假如,小心求证,每次问题的解决都是能力的晋升

查到问题后,修 bug、自测、验证、代码提交零打碎敲,再次上线就今天了,出工回家。

从下午上线发现问题到问题解决耗时超过 6 小时,博主到家时,已是满天繁星。

心愿本文能帮忙大家避开一些坑。

退出移动版