说说“2038年问题”

不要老是想着什么2049或者2077年的赛博朋克故事,先熬过2038年再说吧。

这篇文章是我赶着北京时间11:14分前写完,因为在这个时间点的18年后,就是致命的Y2038K问题。

问题

前段时间的《圣战简史》提到了Unix,2038年问题其实也就是Unix埋了个坑。

这个源于早起Unix的时间戳(timestamp)设计,因为Unix是基于C语言的,这个数字就被设计成了一个有符号的32位整数。具体是从国际标准时间(UTC)的1970年1月1日的00:00:00开始,正数指的是这之后过了多少秒,负数指的是这之前多少秒,基本上就能把20世纪的大部分时间都给覆盖了(前可以到1901年,后可以到2038年)。

这个操作相对是比较明智的,只用很小的代价就把大部分内容给表示完了。而且比较好地避免了千年虫问题(Y2K problem)。后者是因为部分开发者偷懒用两位数字表示年份,导致2000年日期重置带来的灾难性的问题。

当然了Unix这个操作也只是把灾难往后推了38年。

溢出

32位有符号正数(signed 32 bit integer)的最大数值表示是$2^{31}-1$,换算成十进制也就是2,147,483,647。Unix时间戳是以秒为单位的,这样算下来恰好是到UTC时间2038年1月19日的03:14:07。

然而,下一秒就出问题了。

整数溢出在C语言里面是未定义行为,可能跟实现相关,而这里的自增一操作在大部分实现里面都会进位,也就是完全变成了最小的有符号数,$-2^{31}$,对应的时间就变成了UTC时间1901年的12月13日。

危害

这个看起来跟现实无关的问题,坑特别多。绝大多数在还没发现这个问题之前设计的软件系统、标准和协议,在二进制存储格式上都受了Unix时间戳的影响采用了这种形式,基本上都会面临类似的问题,而这些恰恰都是整个软件系统中最底层最隐蔽的部分,不会被上层用户感知到;但同时这些也是最基本的软件系统依赖,被应用到了各个系统的阴暗角落,一旦触发问题,影响不容小觑。

并且神奇的是,这个问题早就在2006年开始对软件系统产生影响了。

AOL事件

这个问题发生在2006年5月。美国在线的AOLServer为解决一个超时问题,把超时时间硬编码成了一个非常长的时间,一百万秒。这个时间大概接近32年。刚好在5月中的一天,超时之后的时间溢出了。于是突然超时后的时间变成了过去,整个服务器就此挂掉。

解决方式是AOLServer的管理员把超时时间改的更短,当然这也只是个monkey patch,得不停更新直到到那一天的到来。

其他时间问题

你可以在维基百科的时间合适和存储bug页上找到大多数相关的问题。包括平成/令和交替的那段时间日本人民水深火热地熬过纪年表示的问题,以及固定4位年份表示时的Y10000问题(这群人就是不长脑子,唉)和波音787使用32位数字表示百分之一秒单位的相对时间造成每284天挂一次的问题等等。

这些事情跟我们息息相关,我曾经因为早期微信时区记录混乱丢失过一大部分的关键聊天记录,所有的这些细节看似无所谓,一旦爆发都是灾难性的。而且解决方案虽然都有,但从最基础的设施从下往上替换的难度和成本都非常高。

18年后好汉肯定是有的,但到底有多少系统能扛过去,还得看大家的努力啊。