2012.10.19 事故分析

来源:互联网 发布:千兆交换机端口速率 编辑:程序博客网 时间:2024/04/28 02:39

事情起因:

前一个礼拜MoNET升级了一个 overdue user blocking的功能 , 经过反复测是和小范围生产环境测试没问题,然后就上了 ,这是来公司以后除了手头上的那个MoNET代码迁移以外地一个实际上线的小功能。

功能的具体作用是  , billing team 提供一个目前大小为, 1 ,200,000用户数的list 然后,新增加一个服务叫 BSP ,作为Proxy,每小时去调一把 ,然后把它封装为一个自定义包发到MoNET, MoNET当成普通服务的包处理。 MoNET本身维护一个64 * 1024 *1024的类型为unsigned char类型的静态数组。 用途是一个bitmap , 被block的为1 , 反之为0.收到的包解析为一个个MoNET ID,然后作为参数传入相应函数去查询和设置。 言外之意他们是最后是作为这个数组的下标的。

事情很简单 ,操作在200ms以内完成。 一开始是使用的一个HashMap后来速度不够, 然后换成了个 同样大小的INT型数组 ,清空的速度仍然不够,最后再改成了char 。 我花了很多时间关心速度这件事情上。 然后测了几把没问题就上了,事实证明功能是没问题。


然后周五早上就挂了。 原因也很简单,下标越界。 我们现在MoNET ID的上先是到50 M ,理想状况下, 64M的下标大小应该是完全没有问题的。 越界的情况在理想状况下是不可能发生的。 但是 , 问题发生了。 是因为Billing team的 Block list中有一条手工插入的 ID为1234554321的测试数据。


这就是整个事情的起因经过和结果。解决方案也很简单 ,做下参数检查。


由这件事情我想了一下为什么会发生。

最根本的问题是我没有做参数检查。 而没做参数检查这件事情我觉得和我本人的编程习惯也有关系,而编程习惯其实也是和我的expierience也有关系。

OK 。 过去我是一个console game programmer .所谓的console game就是那些 游戏机上的游戏 例如 PSP 和 Wii XBOX. 开发周期一般很长,而其中又肯定包含了一个很长的DEBUG阶段。 我们有一个专门的数十人的 QA TEAM ,甚至实在中国和美国分别进行两轮测试。 经过pre-alpha , alpha ,beta ,等等几个阶段。可以保证最后release的版本是不会有任何crash 和 soft crash 的。 所以我更倾向于不检查 或者使用ASSERT来检查, 让程序CRASH掉, 然后把问题解决掉。

第二, 每个软件产品 关注的重点不一样, 作为游戏, 更关注的显然是 效率 ,速度。 所以想这个遍历64M大小的数组, 然后每一个元素还有做一次比较运算以进行参数检查显然是不科学的。 但是我想作为一个服务, 面对数百万的用户, 稳定性和可维护性才是更重要的。

第三, 一个console game ,他是一个孤立的系统, 我们能想象用户的所有可能的输入 ,并基于这个基准上来进行一些测试,最终保证程序的稳定的运行。 

但是网络服务是一个需要与其他系统互相交互的一个服务。 前面接受的客户端的信息。后面与DB的交互,你并不确定所有的输入信息都是合法的或者是在你的掌控之中的。

所以我想这需要的是一个在软件开发过程中对我个人而言观念上的转变。你必须把所有的输入都认为是不可靠的。来确保你程序内部系统的稳定。

然后再延伸出去,let's say ,我们有两个服务 A 和 B, A会去call B,这两套服务都是你自己写的。 那我们可以预想B的情况是在自己掌控之中而不用去做检查。

但是实际情况是。 有可能这两套代码是你1年前写的, 然后A升级了,他有新的功能然后对B有新的参数输入。 这时候你因为时间长了,内部逻辑你都忘了, 然后B的接受到的新的A的输入就不在当初设计的范围之中。然后就 BOOM~~挂了。

所以你必须假设所有的输入都是不可靠的,哪怕是你自己写的。


我还举一个另外的关于异常的小例子,这个例子是我以前同事写的,然后查了很久的一个多线程的BUG。 但是其实根源是一个sprintf(a , "XXXXXXXXXXXXX"); 而其实这个时候a的大小是10 而copy过去的内容大于10 ,然后后面的那一块内存就脏掉了。 导致了后面的BUG 。 然后从那以后我们都开始用snprintf 这个函数 ,他带了一个额外的参数来指定会被赋值到a的 长度。 还有类似的strcpy。


这件事情发生了对公司对我都是一场灾难。 不过我想对我的在服务端这块的开发经验来说。我想应该是非常宝贵的。 所谓吃一堑长一智。