从前,有个程序员叫小明,他在开发一个股票的投资组合功能。这个功能简单的来说,就是一个投资组合里有现金、股票等资产,我们根据组合里股票涨跌等情况,给出一个整体组合的指数,以评判组合的水平。
聪明的小明在开发的时候就发现了,如果存在现金为0,并且所持有的股票都退市了的话,这个股票指数的计算就会存在问题。小明想:“现金为0的情况很少,退市的情况就更不多,这个情况很极端,肯定不会遇到,实现异常情况处理的逻辑也很复杂,测试肯定也发现不了,不管了”,于是代码就欢快地跑到了线上。
随着业务的发展,投资组合越来越多了,也随着政策的转向,退市的股票也陆陆续续地变多了。某一天,夜里跑批报错了!告警短信在夜里发到了小明的手机了,小明只好半夜跑来公司排查问题,原来之前考虑的情况真的出现了,小明心里暗骂了一句,还TM真的出现这情况了!骂归骂,小明运用他聪明的大脑,重新看了一遍逻辑,分析了各种情况,然后给出了在这种情况下指数的跑批值应该是多少,经过一次又一次战战兢兢的复核计算,最后战战兢兢地把数据一条一条地更新到了数据库。经过一番折腾后,已经快早上5点了...第二天跟领导解释了情况,说这种情况很少见,我们后续抽时间把这块的逻辑补上!
然而在这次事故的不久之后,小明还在想啥时候开始补这块逻辑时,小明夜里又收到了投资组合模块的告警短信.....
从前,有另外一个程序员叫小王,他在做一个完成交易时给用户的积分账号加积分的功能,订单与积分在不同的数据库,这涉及到一个分布式事务的问题。
聪明的小王早就知道了有很多形态可以解决这个问题,最合适的当然是MQ,但是目前系统没有MQ要额外加入维护,很麻烦;TCC也可以,但要写额外的代码,也好烦;2PC嘛,好像没有合适的,性能也据说不高,不选;Best Efforts 1PC,这个不错!性能比2PC高,无需额外编码,大多数情况下能有强一致性保证,唯一可能出现异常的情况就是在commit阶段,若一个数据库事务提交了,另外一个数据库事务由于宕机提交失败的话,才会出现不一致,这情况多少见呀!就Best Effors 1PC啦!出问题我手工给他补上就好啦。
于是代码就轻松愉快地上线了。
随着业务的发展,业务范围和交易量也在逐步地变大,小王写了一个新功能准备更新到交易服务里,公司里有超强的滚动发布,上线过程很愉快的就完成了。完成验证后,小王心满意足地跑回家睡觉了。
这个小王比较幸运,睡到了第二天上班。然后客服部传来消息,说客户投诉,其交易没有产生积分!小王一下子就猜测到可能是昨晚滚动升级服务下线的时候,没有做优雅停机相关代码设计,导致Best Effors 1PC失败了,于是小王翻了下日志和数据,排查出果然是这个问题导致的,实际还有额外几条数据也有这问题,小王一并修复了这些数据问题,虽然小王很聪明,但这个修复过程也占用小王一上午的时间。同时,小王也决定,给加上优雅停机的相关逻辑。后来再也没有出现过由于更新导致的不一致了。
再随着业务的发展,用户交易量也跟着上去了,然而在一个交易较为频繁的时段,突然飚出来很多告警,机房内网络也不稳定,经过一排查,原来是网络设备出现了问题,经过一小段时间折腾网络恢复了正常。然后小王心里带着忐忑地去翻看交易服务的日志,发现出现了大量的报错,还有不少报错是显式提示BestEffors1PC在Commit阶段的错误!统计了一下,这次可不是手工能解决的问题了.....
小王写了一个批量数据修复程序,开始在生产跑了起来,但小王开始想,我用BestEffor1PC是对的么?
相信大家都听过墨菲定律,就是说,你越担心的事情,越有可能发生。
当然,这不符合我们程序员严谨的风格。我们万事都要量化。小明同学的案例中,即使出现的概率是万分之一,但当组合数量达到十万个呢?达到百万个呢?是不是就会变得经常出现了呢?小王同学的案例里,因commit要走网络等IO,这个整个过程要1-2毫秒不过分吧?当TPS增多,这个1-2毫秒的危险窗口是不是就遍布整个时间范围了呢?这个时候随便发生下网络抖动,会怎样呢?
写程序有一个特点就是,写了一次,计算机会帮你执行无数遍,而你无需付出任何额外努力。但我们人工重复执行一件事情,却要我们每次都付出精力与时间。
如果我们在写代码时放过的万一经过的评估是:一年都不会发生一次的话,个人认为你是可以强行闭上眼睛不管这个万一的。
但是谁能评估准呢?当发生了的时候,你要做的补救措施就是你当时遗漏的逻辑。当发生需要人工补救的情况时,所需付出的努力甚至要远大于你当时完成这段逻辑付出的努力。
因此,小明和小王之后都懂得了 “出来行,迟早要还”的道理,不再放过任何一个万一。