断电这个场景

作者:陆麟
转载请征得作者同意.
2026.1.28



这几年招聘中发现一个规律,基本没有任何一个应聘的开发人员在项目履历中提及断电这个场景。
断电这个场景其实很常见,但是那么多年的面试招聘过程中没有任何一个开发人员在面试过程中提及,说明断电这个异常场景比高并发、海量数据等常见场景偏门。即便是某些系统的核心开发也不一定涉及过。而一旦提及断电 场景相关规范设计或者代码实装的,一定会令我另眼看待。

最早让我意识到断电这个开发场景是黄飞,是我个人网站最早几篇文章的作者。那时候还没普及isdn, 好多人称之为上网其实是在家里拨号上CFIDO BBS,那时候他在UPS厂商山特里面带队搞开发。UPS产品天生和断电相关,必须要在最后一个电池即将耗尽信号出现前完成关机动作。那时候差不多是Windows95/98, Windows NT 3.5/4.0左右,Linux还是玩具的时代,作为UPS软件第一难题是常用的软件根本不考虑断电,因此UPS配套的软件要让那些常规的应用软件执行存盘,然后关机。当然,UPS软件来执行 这类操作肯定是“尽人事、知天命”而已。肯定不知道文档之间的关联性,反正都要断电了,主人不管,那管理程序就只能瞎存盘关程序就算完事了。如果存盘顺序不对,有多少内容损坏了,只好怪主人命不好。
但是我想,真正导致电池耗尽还没能完成关机的场景,反而是操作系统依照依赖关系stop各个service的过程。经常一个数据库服务开机后即便没有任何操作,关闭数据库服务竟然也要好几分钟。黄飞写了几篇文章后 跑去美国了,到现在也失联了几十年了。

后来再要考虑断电这个场景就是到了NECAS接手电子邮件系统开发的时候了。那时候NEC有个世界上性能第一快的邮件系统叫做Express Mail。SMTP是电子邮件的传输协议,SMTP协议里面代表一封邮件从 客户电脑上发完的最后报文是".\r\n"。这个报文一旦发出,邮件服务器就认为这封邮件已经发送给自己了。当邮件服务器响应“250 2.0.0 Ok: XXXXX\r\n”的时候,代表服务器确认信件接收了。 此时邮件必须要已经在存储中,即便遇到断电,当服务器再度开机的时候,应当进入邮件的转发或者投递动作,不可以因为服务器发生故障或者掉电而产生邮件丢失。

知道这些细节的只有真正面对代码的开发专家,当然就是我啦。在一边写代码的时候一边已经确定好断电时邮件由哪个模块作为保障责任方,这套undocumented动作规范,大概在05-07年间成型, 具体是什么版本不记得了。总之服务器返回 250 响应的前提,是已将邮件数据成功写入持久化存储。到了2008年,IETF发布的RFC 5321中彻底确立同样的规则。所有的全世界商用邮件系统都必须遵循这个规定。 但是我想即便如此,绝大部分都邮件厂商开发者并不看RFC,实现标准语义的开发者是少数核心中的核心。其他人基本不知道这个规定的存在。

在写这篇文章的时候,查到AI提示有一类MTA 配置错误:关闭了fsync/flush功能,邮件仅写入系统缓存,未落地磁盘,断电后缓存丢失(配置违规),我不禁纳闷起来,关闭fsync/flush功能是我当时为某大客户 专门开的开关,怎么也变成语料训练ai去了。当时NEC作为大集成商上了所有几乎自家软件,其中包含一套名为InfoCage的信息安全系统。大致可以理解为NEC搞的DLP(数据防泄漏)系统。这系统的开发者有几个比较有名的 国内的windows内核专家,还写了书,我就不提名字了。当时一上线,交付团队就反馈系统性能超级慢。不用想就是InfoCage大量占用磁盘IO,为了让路,我就专门加了个配置开关,允许关闭fsync/flush功能。 这样一来邮件系统的收发性能大致可以上升5倍,但是邮件服务器给邮件客户端返回250响应时候,突发断电可能导致邮件丢失。由于它作为大系统整体的性能补偿开关,除了冒出问题的那个大客户,我感觉没有大规模给 SE们培训过,连个图形化的配置界面也没有,必须手工写到配置文件中。虽然这个开关后来常态化保留在了所有后续版本中,但是我觉得这应该不是所有邮件系统供应商都会提供的正常开关,也不是错误,是真正的功能 开关,只有系统管理员明确后果才能用的开关。被当作“关闭了fsync/flush功能”的配置错误就有点有意思了。

fsync/flush功能开关还有一个另外的故事。我接手了ExpressMail的linux版的windows化工作,为了让windows下实现fsync/flush时(我们在windows下写了一堆语义上等同于linux的同名函数) 数据切实落到了磁盘上,还看了一圈文件系统驱动代码和SCSI驱动的代码。。。

Windows的CreateFile这个API,其中有一个dwFlagsAndAttributes参数,FILE_FLAG_NO_BUFFERING参数只是指示文件系统不要开缓存。数据不经过文件系统驱动提供的缓存流转到硬盘接口后,文件系统 认为数据已经写完了,但是数据不见得进了磁盘。加FILE_FLAG_WRITE_THROUGH这个参数就增加了一个语义,文件系统会在调用磁盘驱动时增加FUA请求flag(强制写入物理介质)。理解这类flag对于掉电会导致数据 丢失的应用非常关键,还要吃透应用自己的缓存,C运行库自带的缓存,文件系统API的缓存参数,文件系统的缓存机制,硬盘的缓存机制和关闭缓存的SCSI开关要怎样在非常上层的应用中打开。涉及很多和硬件打交道的 知识,我想如果不是长期和固件打交道的人是不会有这种意识的。这也是数据库、邮件系统必须有懂硬件的人才会写得靠谱的原因。

回过头来看断电这个场景,断电这个场景其实是一个非常典型的“非常规场景”,大部分开发人员根本不会考虑这个场景。即便是考虑到了,也不见得能把这个场景落实到代码中去。下次面试,能讲明白断电场景时的代码 实装的开发人员,我会格外关注。