两种bug

两种bug

1985年,Jim Gray在这篇文章中第一次提出了软件bug分为两种:玻尔bug和海森堡bug。

《两种bug》 Jim Gray

玻尔和海森堡是两位伟大的物理学家,都是诺贝尔奖获得者。为啥以这两人区分bug?那是因为这两人物理贡献的不同特点正好应对与两种不同性质的bug。

玻尔提出了原子结构模型,借助于这个模型可以确定分析出原子中电子的行为。所以,以玻尔命名的Bohrbug,是确定性的,容易复现,比较容易解决,一般都能通过调试、日志等方法解决。

《两种bug》 玻尔

而海森堡提出了测不准理论,根据这个理论,原子中电子的行为是无法确定的。所以,以海森堡命名的Heisenbergbug,是不确定的,不容易复现的,不容易解决的,让我们非常头疼的bug。这种bug过一段时间可能就消失了,也可能以另外一种形态存在,很难靠打印查出来或debug出来。

《两种bug》 海森堡

玻尔bug的处理方法

传统解决办法

由于玻尔bug是确定性的,容易复现的,那么这种bug相对容易解决掉。相应有很多种处理办法,最经典的自然是:调试和日志。这个解决方式相当于:有病,得治。​但是我们不能过度依赖于这种方法,这样做会造成包治百病的狗皮膏药。

《两种bug》 狗皮膏药

预防

更好一点办法是预防,就像防止疾病最好的方法是预防。我们教小朋友也是这样,勤洗手、别揉眼睛、别吃脏东西等等。这种方式下我们很容易想到防御性编程。比如为了杜绝空指针,可以写很多if判断来堵住防止bug的方法。这的确是可以,也能起到一定作用,但是这么做并不算真正的防御性编程,后果有时弊大于利。

当然,很多人也会想到更好的应对bug方式是测试。这个也没错。不过测试倒不是为了解决bug,而是提前暴露bug,又或是加强反馈,加快迭代来促进开发。关于测试,以后会专门写文章说。我们这里还是围绕着解决bug。

思路转变

我们尝试换个思路,为什么程序员一定只能终日和bug作战呢?能不能写出没有bug的程序呢?很多程序员都会嗤之以鼻,程序怎么可能没bug呢?还记得有位大人物为了一个bug付钱的事吗?

《两种bug》 Knuth的bug支票

这张bug支票,对很多人来说都是一个骄傲,因为这来自于Knuth,相信没人愿意去兑现这张支票

在一定某种思考角度上,无bug的确是有可能发生的,取决于你如何理解程序正确性。其实写程序说到底都是解决问题,那么这个问题如何能解决对,取决于我们对这个问题的理解。如果理解都不对,如何能解决对呢?好像我们现实中遇到一个问题,如果能通过数学建模,应对到数学上的理论,那么由这个理论就可以保证程序运行的正确性,那么问题就可以得到完美解决。

举一个小例子,我希望实现1+2+3+4+...+n的功能,有人会用for循环,实现时得注意for的语法有没有错,停止条件是<还是<=,++ 在前在后……

我们有没有想起一个小时候学习的数学公式呢?

$(1+n)\cdot n \over 2$ 看来简书不支持TEX,即n*(1+n)/2

用这个公式实现,应该肯定无错了吧。这个例子能展现一定无错设计的思想,但如果想要说到位,那就话长了,以后再慢慢介绍。

海森堡bug的处理方法

很多程序员在编写分布式系统后,才发现原来只解决玻尔bug是多么幸福的事,海森堡bug会把程序员折磨的死去活来。为什么?

令人崩溃的测不准

Heisenberg bug想来神龙见首不见尾,飘忽不定,就像买彩票一样,虽然极不可能中,却仍然会有中的时候。如果你总想着怎么才能中彩票,那么结果会非常悲剧。

Heisenberg bug太难解决了,寻常的调试和日志很难发现问题,因为你根本就不知道何时何地会发生这个问题,那该怎么调试呢?最多也只能加大量的日志输出,一遍一遍反复尝试,最终拼的是运气。

当然了,运气也是一种能力。

思路转变

以确定性的思路去解决海森堡bug,就像自己觉得按照一个号买彩票的结果一样,很难很难中奖。

那么是否可以换一个思路,认为这种bug是必然存在的,存在就存在了,存在并不等同于系统不能再提供服务。基于这种看待bug的观点,其处理方式变成:限制bug的作用范围,并保证系统能继续服务。

就好像如果最终目的不是为了中彩票,而是想赚钱,那么还是有其他很多更靠谱的方法。类似的,我们不能因为bug难解决的问题而转移了注意力,陷入到bug泥潭,而忘记了更重要的事——保证服务。

解决bug

发现错误

当bug出现时,如果能及时发现,那就能及时处理。那么怎么才能及时发现错误呢?

  • 如果能提供一种监测机制,让进程之间处于一种互相监控的关系网中,如果有一个进程死掉了,那么其他进程也就可以及时作出相应的处理。这里关注的是怎么发现,具体怎么处理这个进程的死亡,是一个设计问题。

  • 如果某个进程没有死掉,但是已经出问题了,那么能否提供一种进程通信协议机制,让另外一个进程及时发现此进程已经不按照规矩办事了,然后及时作出处理,那么系统也可以得到及时恢复。

处理错误

当系统出现问题后,还要能提供服务,怎么提供?当office把计算机卡死时,很多时候都是直接强行开关机,很少有人等一天的吧,office不能用,不能其他事也不让做吧;当office程序出问题时,一般都是到任务管理器里面杀进程,杀完后接着再打开文档慢慢看。

由此借鉴,遇到进程错误后,干两件事:

  • 限制错误扩散。
    在一个用户任务(计算节点)里面,不能因为某个部分计算失败,导致整个任务失败;在多个用户任务(计算节点)里面,不能因为一个用户的计算错误导致其他节点错误。这就要求我们在收到进程失败消息后,做出适当的处理,总之不让进程错误影响其他进程。

  • 重启错误进程。
    对于有错的进程,直接杀掉,重新起一个干净健康的进程。这个恢复过程肯定是需要一些关键数据的,关键数据是哪些数据,这个就是很大的设计问题。我现在给不出什么很好的总结,不过有一点需要注意,对关键数据考虑的过于精细,不注意舍弃,会导致数据同步和恢复系统很复杂,最后可能还起不到备份效果,这个坑还需要重点注意。找到这些数据后就要重点保护好,和其他一些复杂逻辑功能隔离开,保证其简单性,不能经常crash,同时要有备份,crash了也能轻易恢复出数据。当把有了这部分关键数据,当其他计算节点出错后,就可以通过关键数据把流程恢复出来了。

总结

当面对两种bug时,我们都可以改变一些思路,基于这些思路就能够解决一些困难的问题,但是这些思路并不是最近才发现的,文章开头提到了1985年Jim Gray就已经针对分布式中最容易出现的海森堡bug,提出了解决办法,Jim Gray也因为这一类的贡献,获得了计算机界最高奖–图灵奖。

    原文作者:拉肚啦
    原文地址: https://www.jianshu.com/p/b4b92ba264a5
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞