delphij's Chaos

选择chaos这个词是因为~~实在很难找到一个更合适的词来形容这儿了……

30 Jun 2015

关于闰秒

phk 老大如此 吐槽

One week until the leap-second. If you haven’t tested your IT-system already, it is too late now. Good luck if you’re in California or Japan.

背景

关于时间,A core 几年前写过一个 时间是什么? 的系列: 序言 续一 续二 续三

为了兼顾天文观测获得的时间 UT1 (更符合普通人的需要)和由铯-133原子震荡获得的时间 TAI (更稳定),将两者综合一下就获得了协调世界时 UTC。UTC 和 UT1 之间的差距永远保持在 1 秒以内。UTC 中的一秒和 TAI 相同,但 UTC 和 UT1 保持最多一秒的差距,这是通过 IERS 在 UTC 事件中人为插入或减少闰秒这样的人为调整来实现的。从1972年这套系统投入运营到今天为止,总共插入了35个闰秒(2015年6月30日还将再插入一个闰秒)。

闰秒只在每年的6月30日和12月31日的UTC时间 23:59:59 之后插入,表示为当天的 23:59:60。

Unix时间

Unix 时间是从 UTC 时间1970年1月1日0时0分0秒起的秒数,但不计入闰秒。这一定义后来又被 POSIX 继承并成为了标准。由于 Unix 时间没有考虑闰秒,因此如何表达闰秒在 POSIX 中是没有定义的。换言之,UTC 时间 2015年6月30日23:59:59的Unix时间是1435708799,2015年7月1日00:00:00的Unix时间是1435708800,两者之间的这个闰秒,根据标准就只能用两个时间之一代替了。很明显,这样一来,在闰秒本身或它后面那一秒,在 Unix 时间戳看来就是完全一样的了。)

这会带来什么问题呢?假如我们令 23:59:59 = 23:59:60,那么如果有事件 A、B 分别发生于 23:59:59.2 和 23:59:60.1,则在 Unix 看来,B发生在A之前,而实际上应该是相反的结果。这样一来,一些程序,例如 POSIX 线程库中某些等待时间采用的是绝对时间,由于闰秒的原因,这些等待可能会提前或推后1秒才会唤醒。又比如,依赖于文件时间戳的应用,如make等,可能会误认为某些文件的修改时间发生了倒序问题,而将已经联编过的文件再联编一遍。一般来说,这些都不至于导致太大的问题,但假如应用依赖于精确的时间,而同时又假定时间戳不会重复的分布式系统来说,这个问题就可能产生比较严重的后果了。

NTP对于闰秒的处理

如果内核支持,NTP 在发现闰秒时,将向内核宣示这一现象(STA_INS 或 STA_DEL),并由后者进行适当的处理。内核的状态机将经历 TIME_OK -> TIME_INS -> TIME_OOP -> TIME_WAIT -> TIME_OK 的变化。在 FreeBSD 的实现中,CLOCK_MONOTONIC 的时间戳将持续前进,CLOCK_REALTIME 的时间戳在 TIME_INS 到 TIME_OOP 状态变化时将倒回前一秒。

如何避免闰秒导致的问题

对于没有使用 ntp/ptp 的机器来说,由于它们本来也无法精确计时,因此自然也就无所谓闰秒了。事实上,这些机器很可能完全不知道闰秒的存在。

对于使用了 ntp/ptp 的机器来说,时间戳可能会出现绕回现象。Google针对闰秒的对策 中提到,他们将引入一个基于余弦函数的调整量(1.0 - cos(pi * t / w)) / 2.0,其中w是闰秒的延展时间。利用余弦函数在0、pi两点的导数为0的特性,这一做法可以避免出现跳变。在延展窗口内,系统时钟由于这种调整被人为变慢。这种做法避免了由于时间戳绕回现象导致的问题,但会导致 Unix 时间戳在某个时间段内不准,不过考虑到反正它也不准(因为符合POSIX标准就不能表示闰秒了)所以似乎也就只能大丈夫了?

另一种比较常见的做法是使用 ntpd 的 -x 参数,不过与Google的做法相比,这种做法引入的随机性更大(Google的做法,整个w周期内的时间戳是点点连续且单调递增的)。