闰秒是国际标准时间UTC为了对齐地球自转与原子钟而产生的一秒调整,分为正闰秒与负闰秒。当正闰秒发生时,当天的23:59:59之后会插入23:59:60,再进入次日00:00:00;负闰秒则会直接跳过23:59:59。对大多数依赖单调时间的服务而言,这种非 monotonic 的跳变会破坏计时假设,造成证书校验失败、日志错乱、分布式锁超时异常。理解操作系统与NTP实现如何应对闰秒,是构建稳定系统的前提。

闰秒的底层传播机制与内核行为
闰秒信息并不是凭空出现在操作系统里的,它通常由上级时间源通过NTP或PTP协议下发。NTP报文中的闰秒标志位会在闰秒来临前的一段时间被设置,操作系统内核据此决定将这一秒体现为时间回退还是平滑 smear。Linux内核自2.6起支持通过adjtimex系统调用读取状态,当状态为TIME_INS或TIME_DEL时,说明内核已知晓即将发生的闰秒。
传统内核在默认配置下会采用 stepping 方式,即在闰秒瞬间让gettimeofday返回包含60秒的时刻,随后立刻跳到次日零时。这种方式对认为时间是严格递增的程序是致命的,因为从60秒跳到00秒相当于时间倒流了一秒。某些文件系统与数据库在检测到时间回退时会拒绝写入,防止数据覆盖。因此,理解内核状态机比盲目升级NTP更重要。
另一种做法是让NTP守护进程在用户态将闰秒摊薄,也就是所谓的 leap smear。此时内核始终认为时间是连续递增的,闰秒被均摊到数小时或一整天中,每秒微调几微秒。这种方案对应用完全透明,但要求全网设备采用相同的 smear 策略,否则跨机器时间对比会偏差明显。
ntpd与chrony的闰秒配置差异
使用广泛的ntpd默认并不做 smear,它依赖内核完成闰秒插入,因此管理员常会观察到23:59:60这一特殊秒。若业务无法容忍该跳变,可以改用chrony,它在配置文件中加入leapsecmode smeared后,会把闰秒均摊到约24小时。下面是一段典型的 chrony 配置示例,展示了如何开启平滑模式。
# /etc/chrony.conf 示例 server time.ipipp.com iburst leapsecmode smeared maxdistance 3.0 rtcsync
对比之下,ntpd如果需要类似能力,必须借助外部脚本在闰秒前调用adjtimex修改时钟斜率,或者切换到支持 smeared 时间的上级服务器。很多团队在混合环境中同时运行两种服务,结果因策略不一致造成节点间时间差超过容忍阈值。因此,统一时间中间件策略比单纯追求精度更关键。
还需要注意,当系统使用虚拟化平台时,宿主机和虚拟机的闰秒处理可能脱节。如果宿主机内核直接 step,而虚拟机内的 chrony 在做 smear,虚拟机会出现相对宿主机慢一秒的错觉。在金融交易系统中,这类偏差足以触发风控拦截,所以应在虚拟化层明确禁用宿主机的 step 行为。
应用层如何写出闰秒安全的代码
无论底层如何配置,应用层都不应假设System.currentTimeMillis或time.time永远递增。最稳妥的做法是使用单调时钟接口,例如 Linux 的clock_gettime(CLOCK_MONOTONIC),它不受闰秒与手工校时影响。下面的 C 示例演示了如何获取单调递增时间,用于计算间隔。
#include <time.h>
#include <stdio.h>
int main() {
struct timespec ts;
// 使用单调时钟,闰秒不会让其回退
clock_gettime(CLOCK_MONOTONIC, &ts);
printf("sec=%ld nsec=%ldn", ts.tv_sec, ts.tv_nsec);
return 0;
}
在 Java 中,应避免用new Date()做耗时统计,而使用System.nanoTime。Python 则推荐time.monotonic。这些接口在闰秒发生时依然保持向前,适合生成事务序号、限流计数。若业务逻辑必须使用墙上时钟,则应捕获时间回退事件,记录告警而非直接崩溃。
对于必须处理日期展示的场景,例如日志系统,应当兼容23:59:60的解析。许多旧版时间库会将该字符串判为非法,导致解析异常。可以用正则先放通60秒,再在存储层转成 TAI 偏移。如下 Python 片段展示了宽松解析思路。
import re
from datetime import datetime
def parse_lenient(s):
# 允许秒为60
m = re.match(r'(d{4})-(d{2})-(d{2})T(d{2}):(d{2}):(6d)', s)
if m and m.group(6) == '60':
base = s[:17] + '59'
return datetime.fromisoformat(base)
return datetime.fromisoformat(s)
print(parse_lenient('2023-06-30T23:59:60'))
最后,分布式系统应在设计协议时携带逻辑时钟或版本号,而不是单纯依赖物理时间排序。当闰秒导致个别节点慢一秒,只要逻辑时钟比较机制健全,就不会出现乱序提交。将时间处理从核心链路剥离,放到边缘适配层,是大型系统抵御闰秒风险的有效架构思路。
Leap_SecondNTPclock_discipline修改时间:2026-08-15 08:54:13