这篇文章回看 Y2K 之后真正更值得关注的一组问题:很多系统内部都在用“自某个纪元以来经过了多少秒或多少周”来表示时间,而 2036、2038、2040 等新的计数回卷节点,才是未来十多年里运营商和系统维护者真正需要提前消化的时间风险。
这篇文章讨论的是一个很容易被忽略、却迟早会重新回到基础设施视野里的问题:时间计数器的回卷。
快三十年前,全球 IT 行业曾经被 Y2K 焦虑笼罩。临近 2000 年时,人们发现不少旧软件只用年份的最后两位来表示日期。这样一来,当世纪切换发生时,本地时钟看起来就像突然倒退,很多写得粗糙的软件理论上会崩溃,或者直接陷入死循环。
这个问题本来相当技术化,也相当边缘,但它很快被主流媒体放大成一种“科技末日预告”:电梯停摆、电话系统故障,各类灾难性场景被反复渲染。
整个行业为此花了大量时间和金钱审计 IT 系统,确保千禧年切换时不会出问题。很多公司——包括作者当时工作的电话公司——甚至在午夜时分把技术团队叫回办公室,专门守着系统过零点。
结果什么都没有发生。
Y2K 最终之所以成了一次没有真正爆发的风波,是因为真正使用“两位年份”作为内部时间表示的编程语言和操作系统都很少。我们手写日期时会这么写,软件栈内部通常并不这么处理时间。
例如 COBOL 确实有一种六位数字日期格式,所以千禧年切换时,日期会从“991231”跳到“000101”。即便如此,到 1999 年时,真正运行大量 COBOL 的环境也没有想象中那么普遍,大多数系统都平稳跨过了这个时间点。
从某个纪元开始计时
在当时更常见的做法,是把时间表示为“自某个定义好的纪元以来,已经过去了多少个时间单位”。
比如 Excel 会把时间存成“自 1900 年 1 月 1 日以来经过的天数”,包含小数部分。Unix 操作系统内部的时间表示,则是自 1970 年 1 月 1 日 UTC 以来经过的秒数。按照这种内部表示方式,千禧年本身只是一个计数器从 946,684,799 跳到 946,684,800 的瞬间。这个数字既没有仪式感,也很难天然触发软件灾难。Windows 使用的则是自 1601 年 1 月 1 日以来的 64 位毫秒计数。
GPS 的做法又不同。它用一个 10 位周计数器,统计自 1980 年 1 月 6 日以来过了多少周,所以总长度是 1,024 周。这个计数器最近两次回卷已经分别发生在 1999 年 8 月 21 日和 2019 年 3 月 31 日午夜。GPS 的使用者一直都需要结合其他信息源,自己判断当前所处的 1,024 周区间到底是哪一个。
软件系统一直都在处理时间差。如果代码默认假设计数器只会单调递增,那么一旦发生回卷,时间差就会变成负数,而这往往会以非常意外的方式把程序搞坏。
所以问题来了:这些不同的平台和计数器,下一次究竟会在什么时候回卷?
Unix
在通用 Unix 系统里,time() 是一个非常常见的系统调用,它会返回当前时间戳,通常以 time_t 这种整数秒计数器形式给出。
在 32 位系统中,time_t 往往被定义为有符号 32 位整数。不过今天这种情况已经越来越少见,因为大多数现代平台都支持有符号 64 位整数。如果本地软件库也已经更新到 64 位支持,那么秒计数所用的通用数据类型就会随之扩展。
问题在于,应用在编译时会被绑定到一套统一的数据视图里。一个 32 位 Unix 应用或 32 位库,依旧期待内核返回一个 32 位值,即使底层内核已经运行在 64 位架构上。这会留下兼容性问题,因为就算未来几年 64 位平台会继续全面普及,很多遗留应用仍然会继续服役。
如果你运行的是 32 位有符号整数平台,或者你还在跑 32 位遗留应用,并且时间计数纪元是 1970 年 1 月 1 日,那么回卷会发生在 2038 年 1 月 19 日。
如果使用的是无符号 32 位时间计数器,回卷时间会推迟到 2106 年 2 月 7 日。
如果你已经使用 64 位时间表示,那么秒计数器的回卷时间将落在公元 292,277,026,596 年 12 月 4 日。这个时间尺度基本已经把问题送出了现实运维的讨论边界。
Windows
32 位 Windows 应用,或者显式定义了 _USE_32BIT_TIME_T 的 Windows 程序,同样也会被 2038 年问题击中,只要它们使用 time_t 这种数据类型。
换句话说,这并不是 Unix 独有的问题。只要某类软件仍然把内部时间表示绑在 32 位时间戳上,风险就还在那里。
NTP
那网络时间协议 NTP 呢?
NTP 会通过网络把设备时钟和多个参考时钟同步。在协议的大多数部分里,它使用一种两段式 64 位时间戳表示法:前 32 位表示自 1900 年 1 月 1 日以来的秒数,后 32 位表示秒以下的小数部分。
这意味着,NTP 的秒计数器每 136 年会回卷一次,因此它的第一次秒计数回卷事件会落在 2036 年 2 月 7 日。
NTPv4 规范里也定义了一种 NTP 日期格式,使用的是 64 位秒计数器,这套格式理论上可以覆盖 5840 亿年。至于实际实现会如何处理低位 32 位秒计数的回卷,这件事目前还没有完全明确。不过我们大约还有十年时间去搞清楚它。
文件系统
文件系统也有各自的时间上限。
HFS+ 的时间戳使用无符号 32 位整数,表示自 1904 年 1 月 1 日 UTC 午夜以来的秒数。因此它的文件系统时间戳会在 2040 年 2 月 5 日回卷。
FAT32 则把 16 位用于日历日期,另 16 位用于一天中的时间,所以它可以表示的时间范围是 1980 年到 2107 年。
NFSv4 和 ZFS 使用 64 位 Unix 时间结构,因此它们的回卷时刻同样远在未来,和前面提到的 64 位 Unix 情况一致。
回卷时间总览
表 1 汇总了几类常见平台和系统的关键回卷时间。
| 平台 / 系统 | 回卷时间 |
|---|---|
| Unix 32-bit | 19 January 2038 |
| Unix 64-bit | 4 December 292,277,026,596 |
| Windows 32-bit | 19 January 2038 |
| Windows 64-bit | 4 December 292,277,026,596 |
| NTP | 7 February 2036 |
| HFS+ | 5 February 2040 |
| FAT32 | 31 Dec 2107 |
| NFSv4 | 4 December 292,277,026,596 |
| ZFS | 4 December 292,277,026,596 |
不需要上演没有准备的戏剧
作者认为,这整件事看起来很像 Y2K 故事的重演:一些没有被迁移、没有被重构、也没有跟上当前 64 位硬件平台的旧系统,会在接下来的几年里陆续碰到一个或多个回卷节点,最重要的几个年份就是 2036、2038 和 2040。
这件事确实要求 IT 与网络服务的运营者尽到基本的工程审慎:检查平台、检查库、检查应用,把需要重构的部分提前迁移到 64 位时间戳体系上。
只要这类准备工作足够早、做得足够扎实,那么 2036 年 2 月、2038 年 1 月和 2040 年 2 月到来时,我们大概率还会看到和 Y2K 相似的结果:时间正常跨过门槛,世界继续运行,真正的“事故剧情”依旧没有上演的机会。
这篇文章最有价值的地方,在于它把“时间问题”从一种旧时代的媒体恐慌,重新拉回到现实运维语境中:你不需要恐慌,但你确实需要提前把时间表示、时间库、遗留应用和文件系统兼容性查清楚。
作者最后也注明,文中观点仅代表作者本人,并不必然代表 APNIC 的立场。