码农们记忆最深刻的时刻非联调莫属了,在一个大型软件项目中,大量的特性的合入往往伴随着问题的大爆发,在蜂拥而至的虫族(bug)面前,定位手段就是码农们手中赖以战斗的武器!良好的定位手段能快速找出隐藏的bug,为快速消灭bug争取宝贵的时间。
在交付的压力面前,码农们往往没有足够的时间来进行定位手段的添加,没有定位手段保护的模块,在疯狂的虫群面前就像待宰的羔羊。
问题定位慢,就需要更多人力和时间的投入,没有精力进行剩余特性的开发和模块的持续优化,更糟糕的情况下,如果没有足够的信息支持问题定位,就需要进行依赖问题的复现,而很多问题恰恰又难以复现,就只有在发布前夕组织通宵达旦的攻关,伤神又伤身。
虫群的几轮冲击下来,码农们四处救火、疲惫不堪,模块质量更加恶化,陷入了恶性循环中。
作为一名有追求的码农,我们的精力应该投入到更有创造性的活动上,不应该在问题定位上耗费大好青春,更早的开展可定位性设计,就可以在联调阶段节省大量的问题定位时间,为解决问题、优化代码和追求美眉留下宝贵的时间,引导开发过程进入良性循环,此即磨刀不误砍柴工是也。
开展可定位性设计,首先我们要知道系统中存在哪些故障,建立起我们的故障模式库,才能有针对性的对症下药。
以作者从事的Linux内核开发为例,归纳起来,系统的故障大致分为以下几类:
1,崩溃
往往由空指针引用、错误的内存地址引用等问题引起,内核态中往往会导致系统复位,用户态对应就是进程的异常终止。
系统复位后,内存中的信息往往会被冲乱,此类问题一般依靠调试日志和复位时的堆栈/寄存器信息来进行定位,我们需要在关键事件和异常分支中增加调试打印,以在出现问题后推测出系统当时的状态。
有一种方法值得尝试,复位时内存是不会断电的,因此内存中的内容在复位前后是不变的,在嵌入式系统中,我们可以令业务的数据存放在内存的高位,OS加载时一般不会使用到高位的内存,可以最大限度的保存复位前的信息,辅助问题的定位。
2,挂死
密集出现于资源死锁、流程互斥错误、时序控制错误等问题,系统挂死后将无法处理业务请求,是最致命的错误之一。
系统挂死的时候是静态的,在这个过程中我们可以自由的查看内存中的内容,因此定位起来相对较为简单,我们需要建立起数据结构之间的关联,并最终挂到一个全局变量上,这样我们就能够通过定位手段一步步找到可疑的数据进行察看。
一个简单的计数可以有效帮助快速定位到可疑的模块,模块的输入输出的计数不匹配时,流程很可能被挂起在其中。
3,错误
错误的产生原因多种多样,一部分是对输入的边界值处理不当、场景遗漏、大压力导致的,通常异常处理流程又是平常难以覆盖到的分支,这可能导致错误被层层扩散到整个系统中。
调试日志、计数都可以有效的帮助定位错误,通过对模块的输入输出结果做计数,我们可以追踪到错误产生的源头,调试日志则可以显示出错误是如何产生的。
4,性能
性能问题的特征是严重依赖于问题环境的保留,一旦过了性能差的时间段,就很难找到性能瓶颈了。
定位性能问题,我们需要在关键路径上添加性能统计,最有价值的信息是最近时间段内的统计信息,以及响应最慢的数个请求,我们可以以60s为周期进行统计,出现性能问题时,我们将最近60s的统计信息显示出来,基本上就可以初步定位到瓶颈的所在,再针对性的做检测。
5,数据不一致
最阴暗的问题,常常造成灾难性的后果。交给其他模块的数据,悄无声息的就变了,实在是让人头痛不已,而且你无法知道数据是在什么时候发生改动的,事实上,你甚至可能永远也不知道发生数据不一致了,就像你不知道钱包中钞票的序号一样,如果你不记得写下去的数据是什么,又如何知道读起来的数据不对呢?
未完待续。
故障检测
现场保存
现场查看
流程追踪