为了账号安全,请及时绑定邮箱和手机立即绑定

本文是《如何学习分布式系统》中,关于时钟的相关介绍。

物理时钟的无奈

大家可能都知道,计算机的时钟过一段时间就不准了,为什么计算机的时钟不能保持统一的速度前进呢?因为计算机的时钟速度取决于它的石英振荡器,计算机上的石英振荡器一般来说精度都不是很高(因为精度高了就贵了。。。),在各种物理条件发生变化时有可能频率也发生变化(计算机全速运行一个for循环时的时钟速度和空闲时是不一样的),所以导致计算机时钟不能匀速前进。

现在一般都采用NTP之类的技术来保持时钟同步,不过由于网络延迟,同步频率等因素,物理时钟还是很难保持精确一致。用真实的时间作为横坐标来看,理想情况下,节点上时间应该是下图那样一条斜率为1的直线,真实情况则是它在这条直线上上蹿下跳。
图片描述
对于分布式系统来说,时钟不能精确一致会有什么问题呢?假设在真实时间100的时候,一个操作X=200发生在节点A,节点记录时间为101,接着在真实时间101的时候一个操作Y=50发生在节点B,节点记录时间为98,那么从外部观测者看来,Y=50是发生在X=200前面了。
图片描述

简单粗暴的“等”

如何处理上述情况呢,最简单的方法就是一个字——等!

要想使用“等”字诀,就要知道节点的时钟和真实时钟的误差的最大值。假设系统中节点时钟和真正的时钟最大误差为e,意味着在真正的t时刻,节点的时钟应该在[t-e, t+e]这个范围。完成一个操作之后,不管三七二十一,先等一会儿,再执行下一个操作,这样就保证了后一个操作的时钟大于前一个。要等多久呢?万无一失的操作是,每次等2e的时间就行了。
图片描述

Google Spanner的“等”

Google Spanner就是用“等”来解决分布式系统的时钟问题的。按照论文中的说法,Spanner是Google的“scalable, multiversion, globally distributed, and synchronously replicated database”,它实现了external
consistency的一致性模型(按照论文中说法,就是linearizability),其方法就是利用TrueTime和“等”字诀(官方说法叫“Commit Wait”)。TrueTime有两方面的作用:

  1. 使e变得非常小
  2. 不用每次操作都等待e,而是最多等待e

TrueTime的实现

TrueTime的实现细节Google没有透露,论文里只是介绍说同时使用了GPS和原子时钟,因为GPS和原子时钟有不一样的失效模型。他们在数据中心内部署一组time master,大部分的master上有GPS接收器,剩下的master上则装备原子时钟(这些master叫做世界末日master。。。),master之间互相通信,按照特定的规则运行。在其他服务器上则有time slave,slave定期从master进行时间同步。所以服务器上的时钟误差一直在变化,刚同步完时误差最小,同步之前则误差最大。
图片描述
如下图所示,TrueTime对外提供了三个API。
图片描述
在一般的时间库中,时间都是以一个时间点的方式取得,而TT.now()返回的则是一个区间[earliest,latest]。TrueTime保证的是调用TT.now()的真实时间一定处于它返回的区间之内。
图片描述
TT.after和TT.before在论文中被一笔带过——“The TT.after() and TT.before() methods are convenience wrappers around TT.now()”,从种种迹象推断,TT.after(t)TT.now().earliest > t,而TT.before(t)TT.now().latest < t

说起TT.after() and TT.before(),有个有趣的故事。读论文的时候,里面只说了一句——“The TT.after() and TT.before() methods are convenience wrappers around TT.now()”。我一开始想它们莫非就是简简单单的和TT.now()两个端点比较一下?以after方法为例,TT.after(t)应该就是TT.now().earliest > t,而TT.after(t)为true的意思是“t has definitely passed”,就是“t肯定已经过去了”,如果都已经“definitely”了,那么以下两种情况如何理解:

  1. 同一时刻,t在某些服务器上肯定已经过去了,而另一些服务器上不能肯定。
  2. t在某台服务器肯定已经过去了,过了一会儿,可能又不能肯定了。真是出尔反尔啊。

所以我想是不是Google有一些更黑科技的实现方法呢?于是分别去知乎Stackoverflow提问。直到最后我看到了下面这个图,原来真的就是和TT.now()两个端点比较啊,看来是有点过度解读了。。。
图片描述

TrueTime的应用

Spanner中有以下四种操作,这里我们以简化版的“Read-Write Transaction”为例,说明一下TrueTime的使用。
图片描述
在“Read-Write Transaction”操作中,Spanner想要完成的一致性模型是:如果事务T2的start发生在事务T1的commit之后,那么T2的commit时间必须比T1的commit时间大。这里,我们假设某个事件e的真实时间是tabs(e),事务i的start事件是eistart,事务i的commit事件是eicommit,事务i的commit请求到达服务器的事件为eiserver,事务i的commit时间是si,那么要达到的要求就是:如果tabs(e1commit) < tabs(e2start),那么s1 < s2

Spanner的操作有两条规则:

  1. Start:在事务i的eiserver事件发生之后,它产生一个时间si,要求si >= TT.now().latest,所以si >= tabs(eiserver) ,这个时间si将作为事务Ti的commit时间。
  2. Commit Wait:产生si以后,它要一直等到TT.after(si)返回true之后再commit。这样si小于真实的commit时间,即si < tabs(eicommit) 。

很容易证明如果tabs(e1commit) < tabs(e2start),那么s1 < s2

  1. 由“Commit Wait”可知,s1 < tabs(e1commit),所以s1 < tabs(e2start)
  2. 显然tabs(e2start) < tabs(e2server)
  3. 由“Start”可知,tabs(e2server) <= s2
  4. 所以s1 < s2

这样平均每次等待误差的平均值即可,而不是每次每次等待最大值。
图片描述
当然如下图所示,实际上Spanner会有更复杂的步骤,这里就不深入展开了。
图片描述

更多相关内容,请参考系列文章《如何学习分布式系统》

点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消