WHY
在网络世界中, 各个计算机之间要想协同工作, 时间同步是一个十分重要的基础. 在计算机内部是有自己的时间的, 这个时间通过内部的晶体振荡器差生的固定频率, 来模拟时间流逝进行计算. 虽然频率十分稳定, 但也是有误差的, 虽然现在的工艺水平误差已经十分小了. (关于震荡的具体原理, 在此不表)
既然本地时间会产生误差, 那么就会造成两台服务器的时间不一致. 要消除不一致, 就需要有一个统一的时间标准, 然后大家都以这个标准为基准时间并对自己的本地时间进行校准, 既协调国际时(UTC), 关于这个时间是怎么来的, 不是本文讨论的重点.
好了, 现在, 在另一个地方, 有一个标准时间, 如何将这个标准时间通过网络同步到你的本地计算机呢? 如何在同步的过程中, 尽量消除网络延迟带来的影响呢?
HOW
如果直接进行网络请求, 然后拿到一个返回时间戳并修改本地时间可不可以呢? 显然不行. 别忘了, 包在网络中传输也是需要时间的, 这个请求从对方发出, 至到达本地计算机, 经过了多久你并不知道, 这中间的延迟会严重影响时间校准的结果.
OK, 现在遇到的问题就是网络延时了, 如果能够消除网络延迟, 就可以精准同步了, 但以现在的技术水平是做不到的. 既然延时无法消除, 如果我们能够知道这个延时的时间, 也可以通过计算消除延迟的影响.
包在网络中的传输大致如图:
其中各值如下:
C1
: 客户端发出请求的本地时间C2
: 客户端接收到返回的本地时间S1
: 时间服务器接收到请求的服务器时间S2
: 时间服务器发出响应的服务器时间SC1
: 客户端发出请求时的服务器时间SC2
: 客户端接收到返回的服务器时间CS1
: 时间服务器收到请求时的本地时间CS2
: 时间服务器发出响应时的本地时间
我们现在的目的看起来很简单, 就是在接受到返回的时候, 将本地时间C2
校准为SC2
. 首先要明确的是, C2
和SC2
是不想等的, 否则二者时间相同就不需要校准了嘛.
首先, 我们本地知道的信息有: C1
, C2
, 可以令服务器在返回结果中, 告诉我们S1
, S2
. 并且, 我们假定网络中的延时是基本固定的, 既S1-C1=C2-S2
.
好了, 现在已经成功的将其转换成了一道数学题. 是不是突然发现简单了许多? 步骤如下:
- da = (C2 - C1) - (S2 - S1) # 总延时时长
- d = da / 2 # 单次延时时长
- SC1 = S1 - d
- SC2 = SC1 + (C2 - C1)
如何? 很巧妙的将网络延时消除了.
以上, 就是时间同步ntp
协议的内容了. 不过, 如此同步的时间也是有误差的, 首先上面就假设了往返的网络延时相同, 如果延时不对称, 则同步结果就会不准确, 而且, 协议跑在应用层, 从物理层到应用层之间的时延也会影响最终结果.
应用
我们在实际实际编程中, 经常会写入类似这样的代码:
$t1 = time();
// do someting...
$t2 = time();
如果, 在获取$t1
变量后, 正巧进行了时间同步, 那么$t2
有可能小于$t1
, 岂不是很诡异. 不过还好, 有不同的同步方式供我们选择, 以下在Ubuntu
系统上进行了测试.
ntp
: 时间平滑过度, 保证本地时间递增, 一点点减少本地与远端的时间差.ntpdate
: 立即进行同步, 这种功能情况就可能出现上面$t2
小于$t1
的场景
不过, 在我的服务器Ubuntu 18
上, 已经默认不再使用ntp
工具了, 转而使用timedatectl
, 其内部协议是一样的, 有关timedatectl
的详细内容可以参考一下官网的说明: https://ubuntu.com/server/docs/network-ntp