不可靠的时钟
还在用传统的System.currentTimeMillis来计算代码运行时间嘛? - 掘金 (juejin.cn) 上一篇文章简单地讲到了墙上时钟和单调时钟,本篇将从分布式架构的角度下,谈谈为什么我们称时钟为不可靠的时钟。
时钟同步与准确性
分布式架构下,每个节点的时间很难保持一致,
石英晶体振荡器
机器的时钟是依靠硬件 石英晶体振荡器 来实现的然而石英振荡器会受到温度的影响。
虚拟化切换
即便时在 虚拟机环境 下,CPU核会切换虚拟机,从而导致正在运行的操作系统会突然短暂暂停几十毫秒。
那为什么我们现在的电脑时钟并没有差很多呢。
NTP(network time protocol)
因为我们使用 NTP
进行同步时钟。NTP 提供了时间校准的功能,可以让机器上的时钟相对来说误差更小,但是没办法实现全部一致。
其次这里会同步是需要走 网络 的,然而网络跟时钟都是 不可靠的 因素,比如 网路阻塞,进程暂停 导致超时等。
影响时钟的还有自然因素。
闰秒
正常情况下,一分钟是60秒。但由于地球的自转不均衡和潮汐的问题(具体感兴趣可以自己百度),可能会出现一分钟只有59秒或是61秒。
依赖同步的时钟
网络故障很容易被发现,但是时钟同步出现故障一般都是潜在故障,难以发现。
网络故障,会出现大量的超时请求。这个是可以轻易地被监控到的。当应用程序监控到了之后,可以采取相对应的fail-over
措施。
- 节点下线处理。
- leader选取等。
但是对于时钟故障
就很难立刻让应用程序反应出来。
时钟不一致导致的故障
比如:时间往后跳了1秒、2秒对日常功能可能不会引起大问题。但是对于依赖时钟的服务,比如:
- 限时购买
- 定时推送
- 租约(分布式锁)
- ....
出现的问题可能就是:
- 虽然已经到点了,我这个节点上的请求还可以继续购买。
- 现实世界中,我是抢到了第一,但是在程序里,因为我节点的时间比别人慢,可能别人就是第一了。(
事件顺序性
)
- 客户端A写入
x = 1
成功后,开始进行节点数据同步。 - 客户端B写入
x = x + 1
成功后,开始进行节点数据同步。虽然客户端B是在A之后写入的,虽然客户端A同步开始得比客户端B要早,但是最后客户端B的请求同步比客户端A的要早。
先出发的未必先到达。
那么在节点2的视角来看,由于时间戳,会判断客户端B的请求先于客户端的请求,他的视角来看是,x+=1 -> x = 1。这样就会导致客户端B的请求被覆盖了。
这种解决冲突的方式就是LWW
(最后写入获胜 Last-Writer-Win)。这个策略是依赖写入时间,也就是依赖时钟的。这是他无法解决的缺点。
版本号技术
时钟(时间戳)大量用于版本号的生成。
其次对于 事件发生的因果顺序 ,一般我们都会采取 版本号 的技术,因为 自然数是一个全序关系 可以进行比较。这样我们就可以判断事件A是发生在事件B之前还是之后。
对于 版本号 的生成方式有很多选型:
- 如果采用
wall-clock time
(墙上时钟) 生成的时间戳,在单机情况下是没有问题的,但是在多节点的情况下,极小概率会出现问题,有可能现实世界中发生的顺序,在程序中是不一样的。 - 如果采用
monotonous time
(单调时钟),就可以解决墙上时钟的问题,但是又引入了另外一个问题,这个单调时钟该怎么生成,由主节点生成吗?那么他会有单点故障的风险吗?
所以说在讨论一项技术的时候,我们需要了解他的 优势 以及 代价。再根据我们的场景进行取舍。
总结
分布式集群情况下,各个节点难以保持同样的时钟,影响时钟不可靠的因素有许多:
- 硬件本身的问题(石英晶体受问题影响,时钟飘逸、时钟回拨等问题)
- 虚拟化切换的问题
- 不可靠的网络(NTP同步受到网络的限制)
- ...
常见的 版本号 技术采用时间戳来进行实现,同样会面临 不可靠的时钟 问题,在跨节点的环境下,过分依赖时钟是一个比较危险的操作。
全局快照的同步时钟 目前业内都没有一个很好的落地方案。
Google Spanner
采用的是一个 置信区间 的机制来实现高精度的时钟(尽可能缩小误差区间),同样他 Google
还部署了高精度的时钟仪器比如 GPS接收器或者原子钟 尽可能地减少误差,感兴趣可以了解一下。
来都来了,点个赞再走吧彦祖👍,这对我来说非常重要!
转载自:https://juejin.cn/post/7249981013416984631