NTP 与时间同步:互联网的心跳

你手机上的时间和服务器上的时间,差多少算正常?这篇讲 NTP 怎么用四个时间戳算出差值、stratum 层级为什么会退化、闰秒为什么这么难缠,以及时间一旦被污染会出什么事。

两台机器都说「现在 12:00:00」,它们说的其实不是同一件事。分布式系统里,日志要按时间排序、缓存要判过期、TLS 证书要判有效、金融交易要定先后 —— 这些全建立在一个前提上:大家的时间是一致的。而时间恰恰是计算机最不擅长的东西之一。

智利总统府接收 ESO 的第一台原子钟

图片来源:ESO,CC BY 4.0,来自 Wikimedia Commons。

石英钟到底有多不准

主板上那颗晶振靠石英的压电谐振计时,成本几分钱。它的频率随温度漂移,普通晶振在整机温度范围内动辄偏 20 ppm —— 也就是每秒钟差 20 微秒,一天下来就是 1.7 秒。一个月能漂出一分钟。

石英表机芯特写:石英晶体提供稳定振荡,电路分频后驱动指针

图片来源:Glosome,CC BY-SA 4.0,来自 Wikimedia Commons。

铯原子钟的精度是另一回事:它是用铯-133 原子在两个能级之间跃迁的频率(9,192,631,770 Hz)来定义「一秒」的,这个频率在定义上不漂。问题在于它塞不进你的笔记本 —— 一台上世纪五十年代的实验室铯钟有整整一柜子。

原子喷泉钟:让原子在真空中上抛再落下,测量跃迁频率

图片来源:Steve Jurvetson,CC BY 2.0,来自 Wikimedia Commons。

所以现实的做法是:让廉价的晶振走,让昂贵的原子钟校。

四个时间戳算一个差值

NTP(Network Time Protocol)跑在 UDP 123 端口,核心就四个数:

记号 含义
T1 客户端发出请求的本地时间
T2 服务器收到请求的服务器时间
T3 服务器发出响应的服务器时间
T4 客户端收到响应的本地时间

假设来回路径对称,那么网络往返是 (T4 − T1) − (T3 − T2),而客户端相对服务器快了多少是:

offset = ((T2 − T1) + (T3 − T4)) / 2

客户端把这个 offset 加到自己的时钟上就同步了。注意它算的是差值,不是绝对值 —— NTP 从不要求你知道「现在几点」,只要求你和别人一致。

一次同步只能给出这个瞬间的差值。所以 NTP 客户端不是「校一次就走」,而是持续采样、用统计方法拟合出晶振的频率偏差和漂移率,把时钟驯服成一条平滑的曲线,而不是一次次生硬地跳。这也是为什么 ntpd 会拒绝明显离谱的样本 —— 它怕把已经收敛的曲线带偏。

一台 Soekris 单板 NTP 服务器,三个网口分别接不同网络

图片来源:Kristian Mollenborg,CC BY 2.0,来自 Wikimedia Commons。

stratum 是距离,不是精度

NTP 的层级叫 stratum:

  • stratum 0:真正的参考时钟 —— 原子钟、GPS 接收机。它们不直接对外服务;
  • stratum 1:直接连着 stratum 0 的服务器;
  • stratum 2:从 stratum 1 取时间,以此类推,上限 15,16 表示不可用。

常见的误解是「stratum 数字小 = 精度高」。其实它只表示离参考时钟隔了几跳,跟精度没有直接关系。一台负载过重、网络拥塞的 stratum 1 服务器,给出来的时间可能还不如一台网络状况良好的 stratum 3。

公网 NTP 的典型精度是几毫秒到几十毫秒;同一个机房内能到亚毫秒。要做到亚微秒,就得换 PTP(IEEE 1588)—— 它和 NTP 最大的区别是在网卡硬件上打时间戳,绕开了操作系统里那几十微秒的不确定延迟。

闰秒:一个拖了五十年的补丁

原子钟走得极其稳定,但地球自转不稳定。为了让「世界时」和「太阳在天上的位置」对得上,1972 年起人们用闰秒来补差:UTC 偶尔插入一秒。到 2016 年为止一共插了 27 次。

麻烦在于这一秒没法优雅地插入。软件里一个时间戳回退 1 秒,可能意味着:

  • 日志时间倒流,排序错乱;
  • 定时任务在同一个「时间点」触发两次;
  • 带过期时间的缓存条目永远不过期。

2012 年闰秒时,Reddit、Mozilla、LinkedIn 等一批服务出现过故障。各家后来发明了各种绕法:Google 把这一秒摊到 24 小时里慢慢抹平(leap smear),Amazon 干脆用自己的时间源。

2022 年,国际计量大会决定:最迟 2035 年停止插入闰秒。也就是说,未来某个时刻 UTC 和 UT1 会差到大约一分钟 —— 到那时再想别的办法。

时间被污染会出什么事

时间同步是一条信任链,而它经常被忽视:

  • NTP 放大攻击:早期 NTP 的 monlist 命令,一个几十字节的请求能换回几百倍的响应。2013 到 2014 年,它被用来打出几百 Gbps 的 DDoS。
  • 伪造时间源:如果攻击者能应答你的 NTP 请求,他就能让你的时钟偏掉。证书有效期、令牌过期、双因素验证的 TOTP 全部跟着失效。
  • 对时依赖本身就是单点:只配一个上游服务器,等于把时间交给了那一台机器。

所以正经的做法是配 4 个以上互不相关的上游(常见的 pool.ntp.org 就是干这个的),让 NTP 的投票算法自己剔除异常源。

我的判断

时间同步是个典型的「平时完全看不见、坏起来查半天」的东西。我自己的做法很简单:

  1. 服务器一律配 4 个以上不同机构的上游,不写死单台;
  2. 监控里加一条「与上游的偏移量」告警,阈值设在 100 毫秒 —— 这个量级对绝大多数业务无害,但一旦超过,通常意味着上游或者网络出了问题;
  3. 需要跨机严格排序的场景,不去依赖物理时钟,改用逻辑时钟或版本号。

物理时钟永远只是个近似。把它当成精确的东西用,才是真正的问题。

← 返回文章列表

评论

…