那些年我们一起犯过的错

Posted zhengyun_ustc

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了那些年我们一起犯过的错相关的知识,希望对你有一定的参考价值。

最后更新于2018/11/16

我们和一般技术公司不一样的地方,在于对待错误的态度。接下来大家会对这一点深有体会。

 

我所能做的,就是和每个人谈谈我的失败经历,我倒是敢保证,如果你们都重蹈我的覆辙,那么你们也一定会跌得很惨。

——唐纳德·基奥

 

接下来昀哥我讲五个故事:

引子:某行网上银行上线那一天,我的悲惨遭遇

别人的错误1—前事不忘后事之师:台湾飞机失速坠毁

别人的错误2—对抗人的错误:401航班失事

我的错误1—做好被攻陷的准备:刚接手工作几个月,黑客入侵

别人的错误3—工具和风控:骑士资本集团的覆灭

 

引子:某行网上银行上线那一天,我的悲惨遭遇

2001年,领导指派我对接某银行网上银行里的一个小项目。

项目内容比较简单,我准备的也很充分,在公司测试服务器上拆了装,装了拆,演练了六七遍。

某行测试环境各方联调的时候,我也是一次性通过。

万万没想到,网银首次上线部署的那一天,我这边却出事儿了。

 

那是一个周五的晚上,各家公司都集中到了该行软件中心的新机房,开始通宵发布。

按部就班部署完之后,程序报错,大概意思是父进程的身份标识无法传递给子进程,导致子进程访问第三方网络资源被拒绝。

我重装了系统和服务,甚至格式化了磁盘,一遍又一遍地重试,结果都是一样的。

一晚上很快就过去了。

某行的人看我面色不佳,劝我先去宾馆房间休息一下,缓一缓。

走在路上,天色大亮,喉头一甜,吐了一口血。

 

睡了一觉,又接着试,还是不行,这时候已经到了周六下午三点。

我只好给领导打电话。领导和两个技术总监带着笔记本电脑来了。

现场改了代码,总算是过了。

 

在这个故事里,昀哥我哪些地方做错了?

首先是我在最后时刻才寻求公司内部资源支持,所以我们遇到线上事故的时候,一定要拿起电话召集人马,不要拖延。

其次,虽然上线前自己反复测试认为已经没有风险,但没有关注自己代码严重依赖的系统服务和第三方库。

自此之后,我非常关注“失败”,非常关注BUG、安全漏洞和投诉。

我通读了所有的系统知识库文章,经常会跑去看一看各种开源系统的Feature List和Bugfix,看看它们发布了什么特性,修复了什么缺陷。

 

这件事改变了我的人生…… 

 

点题:

『我得到正确判断的办法,

通常是先收集各种错误判断的例子,

然后仔细考虑怎样避免得到这些下场。』

——《穷查理宝典2》查理·芒格

尾声:

几年后,我阅读到一篇微软KB,公布了一个系统组件的BUG,可能就是我那个。 

 

别人的错误1—前事不忘后事之师:台湾飞机失速坠毁

上个世纪九十年代,为了迎接开放大陆探亲和两岸三通,台湾一窝蜂地成立了很多小航空公司,从零到一的过程中,大干快上,很容易埋下致命隐患。

 

在这样的大时代背景下,2001年9月3日,台湾凌天航空的一架贝尔206直升机在空中失速坠毁了,奇怪的是,调查小组竟然找不到原因。

第一,意外来得太快,毫无预警,驾驶员刚通报飞机失速,还不到一分钟就坠毁了。

第二,机体摔得严重变形,但飞机却没有爆炸。

第三,一个负责监视引擎燃烧状况的警示灯,钨丝严重烧坏变形,所以调查小组推测,它在出事时可能是点亮的,这是一个非常有价值的信息,调查小组决定沿着引擎燃烧状况这一点排查下去。

但查了很久,也找不出可能导致引擎燃烧异常的原因。

 

有一天,化学鉴识人员打来电话。

你们提供的飞机燃料有没有问题,那根本不是汽油,是水。

怎么可能?你们要不要再重验看看?

早都验过好几次了。

 

调查小组只好对失事飞机燃料箱内的燃料重新采样,再请他们检验,报告结果出来了,仍然是100%的纯水。

检查加油记录,检查贮油槽的汽油,检查供应商,一切都合乎规定。

 

百思不得其解。

大家觉得会是什么原因呢?

 

这家航空公司拥有两个自制的大贮油槽,原先失事飞机加油的那个贮油槽已经用光汽油了,另外的这一大槽还剩下将近一半。

因为是小型民航公司,所以飞机航次不多,上一桶汽油大约用了整整四个多月。

他请人找来一条管子,伸到油槽底部,利用虹吸管原理把底层的汽油抽出来。

果然,管子流出来许多清澈无味的液体,不用化验就知道那是水。

难怪飞机没有爆炸。

再检查给这架贝尔206加油的油罐车里,其所装载的油料含水量为99.8%。

 

WHY?!

请注意台湾是个岛,湿度比较大,四个月以来,槽内空气在白天温度升高时凝成水滴,不断地沿着内壁滑入汽油中。由于水的比重大于油,这些水全沉入了最底层。

 

调查小组找出国外贮油槽的设计图,发现人家全都可以排除或避免底层液体被抽取使用。而小民航公司急匆匆制造出来的贮油槽,虽然外观差不多,可是实际上的功能却只是个容器而已。

 

所以,悲剧就这么发生了,那架倒霉的飞机有好几个油箱,一个是原先剩下的汽油,其他的从油槽底层加了一肚子水,第一个油箱耗光后就突然失速掉了下来,一肚子水自然不会爆炸。

 

而大陆一线机场的燃油管道都敷设在地下,加油车只是一个移动的油泵。

技术图片

【大陆一线机场的加油方式】

所以不必担心抽到含水量998的燃油。

技术图片

【油库在地下】

 

点题:

『我们要把别人的历史当作自己的未来,

这样,

才能知道过去人家在做什么,

我们现在应该怎么做。

——冯仑《行在宽处》

 

尾声:

你以为这就是结局?

 

14年后。

2015年2月4日上午10:45,从台北飞往金门的台湾复兴航空ATR-72-600型客机起飞不到五分钟即坠毁。40人遇难。

坠毁客机机体严重损毁,但是河道上却全无油污。

技术图片

【没有油污的河面】

 

别忘了这是一架刚起飞不久的飞机。

技术图片

【干干净净的残骸】

 

这是复兴航空这架飞机失速的最后遗照。

技术图片

【最后遗照】

整整齐齐摆放的行李箱,

提醒我们仍然是:

善于遗忘的愚蠢的人类。

技术图片

【整整齐齐的行李箱】

所谓的出师其实就是把该犯的错误都犯一遍,

有人出师快其实是他试错快。

如果有人说他没有犯错误,

只能说他还没学到家,

就算现在绕开了以后迟早还是要碰到的。

——韩冰

  

别人的错误2—对抗人的错误:401航班失事

1972年12月29日深夜,美国东方航空401号航班准备降落在迈阿密国际机场。

技术图片

【美国东方航空相似机型】

 

机长开始做例行降落检查,不久后却以每小时365公里的速度撞向沼泽,飞机解体爆炸。

技术图片

【残骸】

 

虽然沼泽的黑泥吸收了飞机撞击的能量,吸收了20吨航空燃油,有一些人侥幸活了下来,但也都浑身沾满了强腐蚀性的航空燃油,在残骸和断肢中等待救援。

技术图片

【大沼泽地国家公园】

 

到底发生了什么?

首先,这架飞机的自动驾驶有一个设计,你也可认为是缺陷,就是在自动驾驶接通的情况下,只要在方向舵上稍稍用点力,就会解除自动驾驶系统中负责高度的控制,设计者的本意可能是为了方便人工干预吧。

技术图片

【Left Gear是左起落架指示灯,Right Gear是右起落架指示灯】

 

其次,用于指示前起落架放下锁好的绿灯不亮。这可能是起落架真的有问题,但更有可能是灯泡坏了。

 

为了把前起落架指示灯拔出来检查灯泡坏没坏,机长让一副把飞机切换成了自动驾驶,保持2000英尺的高度,在机场附近盘旋。

之后机长、一副、二副都忙着插拔灯泡。

 

灯泡确实是坏的。

 

机长怒火中烧,让二副下到机腹直接观察前起落架是否放下锁好,但忙乱中,机长忘了开着陆灯,外面漆黑一片,所以二副无功而返。

机长把灯打开,二副又带着人去观察了。

 

在这个过程中,机长的身体碰了方向舵并前推,于是飞机开始缓慢地下降。

不幸的是,机组成员都在专心研究指示灯为什么不亮,而忽略了飞机在偏离机组设定高度上下250英尺时发出的警报声。

 

其次,美国联邦航空管理局(FAA)当时没有要求空中管制员必须提醒机组高度不正确,再加上地面雷达本身经常有错误显示高度随后恢复正常的缺陷,所以塔台的空中管制员虽然注意到了401航班的飞行高度已经降到了900英尺,但没有干预。

 

401航班的最后一次生还机会丧失了。

 

当近地警报响起时,当机长终于发现有问题的时候,高度已经不足200英尺了。

 

最后的对话是这样的:

一副:飞行高度有变动。

机长:什么?

一副:高度还在2000英尺吧?

机长 :嘿,这是怎么了?

Stockstill: We did something to the altitude.

Loft: What?

Stockstill: We‘re still at 2,000 feet, right?

Loft: Hey—what‘s happening here?

 

这个时候飞机已经拉不起来了。

99人当场死亡。机长、一副、二副都伤重不治。

技术图片

【残骸】

事后检查,起落架其实是好的。

 

我们遇到的很多灾难性事故,往往是一系列小问题、小故障、小 BUG 连起来造成的。401航班就是这样。

 

插曲:

乘客,机组成员甚至于美国东方航空的高管,都声称在某些装了401航班部件的飞机上,见到过奇怪的“东西”……

【401航班灵异事件】

 

尾声:

6年后,1978年12月28日的美国联合航空173号航班事故中,同样是起落架指示灯没有如期变绿,反而是出现一声巨响,机体开始抖动,同样是机长专注于排查问题,多次被提醒燃油即将耗尽,一再忽略。

最后,事故导致8名乘客和2名机组成员遇难。

 

173航班是航空安全史上的重要分水岭:自此,分成173之前和173之后。

 

首先,173号航班的事故调查报告催生了航空界首套机组资源管理系统,CRM。

 

美国国家运输安全委员会(National Transportation Safety Board)要求各航空公司加强对机员的机组资源管理(CRM)训练。CRM的前提是基于这样一个假定:人的错误是普遍存在和不可避免的。如果人的错误是不可避免的,CRM就可以看成是一个对抗人的错误的工具。CRM包括六大工具:标准程序、标准喊话、交叉检查、飞行简令、检查单和指令复诵与证实。

 

其次,美国联邦航空管理局(FAA)要求空管员向飞行员提供飞行高度数据。

 

推荐书单:

每一位TeamLeader都应该读一读这本书:

技术图片航空业的“黑匣子思维”与一般人思维的根本区别在于:

一般人认为错误是不好的,出于本能会为错误找各种借口;

但这套方法,会把错误看成进步的契机。犯错的人要改正,没犯错的人也要自省,从而杜绝重复犯错,使整个组织,甚至整个行业都能从中获益。

-救生衣必须出舱之后再充气,是因为埃塞俄比亚航空961号;

-锂电池必须随身携带,是因为UPS航空6号班机;

-所有的飞机都安装密码驾驶舱门,是因为著名的911。

 

这本书的作者称,航空业与医疗业对错误的态度是迥然不同的,航空业更愿意正视错误,飞行员们总体上说对自身的失误都抱着公开和坦诚的态度,部分原因是错误会导致他们自己死亡。这个行业里有强势并独立的组织专门负责对空难进行调查。失败不会被当成控诉某一位飞行员的理由,而会被视为能让所有飞行员、航空公司和管理者们学习进步的一次宝贵机会。

  

阶段性小结:错误是我们的财富

对于事故处理,我们遵从航天二十字诀:定位准确、机理清楚、可以复现、措施有效、举一反三。

“丰田生产体系”与航空航天的这个原则是相通的,如果对待错误的态度是开诚布公的,那么整套系统就能从中学习,能取得进步。

我们坚持每错必查、错了又错就整改、每错必写,用身体力行告诉每一个新员工直面错误、公开技术细节、分享给所有人,长此以往,每一次事故都会变为我们的财富。

 

 

我的错误1—做好被攻陷的准备:刚接手工作几个月,黑客入侵

很多年前,没想到我刚刚到任几个月,数据库就被黑客下载了。

过程是这样的:

凌晨2:46,黑客通过某地IDC机房里的一台肉鸡,仅仅两次尝试输入管理后台登录地址后,就准确地输对了,说明此黑客清楚我们的后台登录拼写规则,否则不可能在7秒内依次尝试很难的拼写。

登入8分钟后,他利用 FCKeditor 版本2.4.2以下的php版上传文件漏洞,上传了多个 php 文件。

然后,他种了一个 rootkit,即在 include 文件夹下放了一个 lndex.php,浏览这个 php 就可以从网页上修改操作系统 root 帐号的密码。

总之,黑客在凌晨3点到6点之间,在那台宿主机上密密麻麻地放了很多 php 文件上来,并修改了很多系统文件。

 

几天后,黑客又从那个肉鸡来了,访问他之前在服务器上种下的 rootkit。

然后,可能利用他从服务器上代码配置文件中看到的数据库用户名和密码,备份数据库并下载。

 

尾声:

内部IT系统的后端:

-在服务器端,日志文件里不要存储用户或商户的敏感信息,如登录密码、银行卡号、身份证号,曾经有一个知名公司的工程师为了调试方便,将用户的信用卡卡号和卡密记在日志文件里,但白帽子们发现能访问到这个日志文件……

-我们认为数据库有可能被盗走,所以要做到即使被拿走,也不要给客户和用户造成损失,所以数据库存取这些商业敏感信息时需要做高强度的对称加解密。

-工程配置文件只允许存储加密后的数据库登录密码,同时部署人员和开发人员都不允许掌握明文密码。

 

内部IT系统的前端:

-登录做两步验证;

-禁止多点登录;

-从框架上做好防 XSS+SessionHijacking+CSRF+SQLi……

-我们认为第三方很有可能拿到我们的平台登录权限(通过 session hijacking,或通过内部人),所以即使在合法用户登录状态下,敏感字段的展示要有遮挡,修改或查看敏感信息的时候要输入短验验证身份;

-内部IT系统的 robots.txt 内容必须是:『User-agent: * Disallow: /』,禁止搜索引擎收录。

 

点题:

我的态度是:放眼一年、三年、五年、十年,你的系统一定会被人攻陷,你的数据一定会被人拿走,往往是几个初中级安全漏洞,再加上一次社会工程学,就能成功渗透,并不需要高危漏洞。所以要做好灾难即将来临的准备,即使被攻陷,被拿走,也不要给商户和用户带来二次伤害。

 

 

别人的错误3—工具与风控:骑士资本集团的覆灭

2012年的时候,骑士资本是美国股票市场最大的经纪商,分别占有纽交所和纳斯达克 17% 的市场份额。

骑士资本的电子贸易部门管理的平均日交易量超过 33 亿股,交易额高达210 亿美元。

截止到 2012 年 7 月 31 日,骑士资本拥有高达 3 亿6500 万美元的现金及现金等价物。

 

在8月1日之前,骑士资本按照纽交所的项目计划,更新了算法程序 SMARS,它从交易平台接收大订单,然后根据买家或卖家的股票交易数量把大订单拆分成合适的小订单。

 

这次更新去掉了一些过时的代码,如 Power Peg,虽然它已经 8年没有用过了,但实际上 Power Peg 模块一直处于待命状态,只要系统的某一个特殊的参数被设置为「YES」,该模块就会被调用用来交易。

 

程序员开发了一个新的 RLP 模块,取代之前的 Power Peg 模块。取代后,之前那个特殊的参数被设置为「YES」,意思是使用 RLP 模块。

 

听起来是不是让人担心?

 

不用担心,测试完全通过,虽然更新后的代码沿用了以前用来激活 Power Peg 模块的标识符,但代码非常可靠。

 

7月27日到7月31日,骑士资本把 SMARS 软件手动部署到公司为数不多的服务器上。

一共才 8 台。

不幸的是,漏了一台服务器。

因为没有其他技术人员对部署过程做复查,所以没有人发觉第 8 台服务器上的 Power Peg 代码并没有被移除。

所以这台服务器上并没有 RLP 模块,只有 Power Peg 模块。「Power Peg」模块在被停用后的第10年被启动了。

 

灾难正在一分一秒地迫近。

 

2012年8月1日早上9点30分开盘后,很多交易员感觉到异乎寻常的事情发生了,某些个股涌现出大量不符合常理的订单,而且没有停止的迹象。

 

这个系统竟然没有断开的开关。

 

于是乎,在 45 分钟之内,骑士资本执行了超过日均交易额 50% 的订单,导致部分股票市值上升超过 10%,带来的连锁反应是其他股票价格暴跌。

 

由于没办法断开系统,也没有相关情况的预案说明,魂飞魄散的程序员只能在每分钟交易 800 万股的生产环境里调试。

 

因为没有能在线上发现问题,所以回滚了代码。

 

情况反而恶化了。

 

原本只是第 8 台上的 Power Peg 在疯狂地工作。

现在另外 7 台服务器上的 Power Peg 也加入了进来。

 

最后,骑士资本的技术人员和纽交所一起终于想办法终止了交易系统,然而已经过去了 45 分钟。

 

灾难现场,一片狼藉。

 

在这 45 分钟里,

对于内行人来说,骑士资本建立了 80 支个股 35 亿美元的净多头仓位和 74 支个股 31 亿 5000 万美元的净空头仓位。

对外行人来说,骑士资本在 45 分钟内亏损了 4 亿 6000 万美元,而上文提到,骑士资本仅有 3亿6500万美元的资产,这意味着骑士资本破产了。

 

骑士资本集团在整个事件中犯下的错误有哪些呢?

1,Power Peg 模块在停用时并没有从系统中删除,而是保留在系统里成为僵尸程序。

2,运维工程师手工部署,没有交叉验证,操作重大失误。

3,他们的风险管理完全是事后管理,缺乏事前控制。虽然对公司的敞口设置了限额,但超过限额时交易系统无任何限制。

4,他们的风险管理工具PMON,是一个事后的风险管理工具,完全依赖于人工监控。当交易量较大时,该系统还会有延迟,产生错误的报告。所以在灾难发生的时候,业务人员没有快速定位到敞口的来源,也没有意识到问题的严重性。

 

点题:

1,工具:假定人的错误是不可避免的,上线部署就应该是自动化的,而且是可重复的过程,尽量排除人为因素的干扰。如果你常年靠手动发布,总有一天会大难临头。

2.风控:你的业务保障平台,你的风控管理系统,是你的最重要的伙伴,不要轻视它,在关键时刻,它会救你的命。

 

最后,我们再呼应一下主题:

第一,

『我得到正确判断的办法,

通常是先收集各种错误判断的例子,

然后仔细考虑怎样避免得到这些下场。』

——《穷查理宝典2》查理·芒格

 

第二,

错误是我们的财富。

我们坚持每错必查、错了又错就整改、每错必写的RCA制度,

用身体力行告诉每一个新员工直面错误、公开技术细节、分享给所有人,

长此以往,每一次事故都会变为我们的财富,而不是包袱。

 

-EOF-

欢迎关注老兵笔记

技术图片

????老兵笔记,拿起手机扫一下

以上是关于那些年我们一起犯过的错的主要内容,如果未能解决你的问题,请参考以下文章

那些年犯过的憨憨错误

那些年犯过的憨憨错误

那些年犯过的憨憨错误

反省我十年开发犯过的错

那些年我们一起追逐过的工具

那些年我们一起踩过的坑——WebIDE 前端札记