让我们来做一个思想实验:我们将设计一个小程序,在一个类似 Unix 的盒子上提供服务。它的设计目的是通过 Internet 定期从可能很近或很远的主机获取信息,然后保留一份本地副本供自己和其他人使用。
您可能让它使用某种配置文件,在其中告诉它要访问的服务器的主机名。也许您已经设置了一个池,这样任何给定的解析 foo.service.example 的尝试每次都会产生不同的 IP 地址,并且有很多这样的 IP 地址。
服务器 0.foo.service.example 服务器 1.foo.service.example 服务器 2.foo.service.example 服务器 3.foo.service.example
您什么时候让它将主机解析为 IP 地址?看起来您可能希望它在您的程序启动时发生。鉴于上述配置,它会找到四个条目,将其转换为四个 IP 地址,然后会忙于尝试从它们同步数据。
但是,我还没有告诉你整个故事。如果您在网络被假定为“始终存在”的时代设计您的程序会怎么样?没有消费级互联网和家庭连接这样的东西。你可能会写它来做一次名称到 IP 的解析,然后就再也不会了。
考虑一下当具有该设计的系统遇到这样的现实:在具有愚蠢的消费级互联网连接的愚蠢的消费级硬件上运行,来自本地公用事业的原始蹩脚电力以及您能想到的所有其他熵源时会发生什么。它可能不会表现良好。
这样的系统会在机器启动时启动,并会尝试获取其 IP 地址。然后它会考虑成功或失败,并会使用它碰巧得到的任何东西。如果什么都没有,那就是它。它会永远坐在那里盯着自己的鞋子看,或者至少直到下一个不稳定的公用电源情况重新开始循环。
当您在用于家庭 Internet 连接的愚蠢的小型消费者“路由器”上运行 ntpd 时,就会发生这种情况。很有可能路由器盒和电缆调制解调器、DSL 网桥(或其他任何东西)都会同时重启。路由器可能会在实际的 Internet 连接出现之前设法启动并启动 ntpd,这也是一个不错的选择。
这意味着 ntpd 会发现自己在一个没有路由到外界的网络上,然后它会尝试解决问题但会失败。然后它就会坐在那里毫无用处,直到某物或某人出现并踢它。
这种情况发生在 Unifi 网关设备上,如果事情的顺序恰好如上所述,它会*现在*咬你。
因此,如果您发现自己有一台机器正在尝试运行,比如说,systemd-timesyncd 针对本地 USG 或类似的东西并且它没有同步,那么您可能掉入了这个陷阱。 ntpd 中的任何内容都不会唤醒它并尝试纠正这种情况。
Unifi + ntpd 的情况实际上是边沿触发的:启动的盒子的“上升沿”将其发送出去以进行一系列设置。如果它有效,你就很好,但如果它失败了,你就完蛋了。
那么让我们尝试一种不同的方法。你是服务员。你的工作是定期与其他服务器对话。您已经获得了一些配置指令来帮助您找到它们。在您有“足够多”的服务器与之通信之前,您会一直尝试添加更多服务器。这意味着尝试 DNS 解析,然后如果成功,则尝试与他们交谈并查看他们是否正常。如果是,则将它们保留在身边并可能将它们用作数据源。如果你不是,你驱逐他们并重新开始这个过程以获得另一个。
这种情况更多是水平触发的。有问题的系统将继续尝试到达需要的位置。它能够在损坏的环境中启动,然后在世界其他地方重新开始工作时最终恢复。它不会只是去度假,因为其他人还没有上班。现在,显然它需要一点小心,因为在一个紧密的循环中重试也是不好的。进行重试(退避、抖动等)是一门艺术,也需要考虑。
这在工作方式上有很大的不同,一旦你开始以这种方式思考系统,你就会开始注意到日常生活中所有的小竞争条件和时序异常,它们会绊倒边沿触发的东西。任何时候你不得不以“正确的顺序”重置某些东西或者通过一系列其他状态运行某些东西以使其全部“同步”时,你可能正在与之抗争。
当系统知道他们应该做什么,然后继续努力直到他们成功时,这不是很好吗?
TL;DR 使用 chrony。