技术分享分布式架构中的时间真相
Posted 唯品会安全应急响应中心
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了技术分享分布式架构中的时间真相相关的知识,希望对你有一定的参考价值。
感谢业界小伙伴——吴松林,投稿精品原创类文章。VSRC欢迎精品原创类文章投稿,优秀文章一旦采纳发布,将有好礼相送,我们已为您准备好了丰富的奖品!
(活动最终解释权归VSRC所有)
分布式架构中的时间真相
对于生活中常见的事物,我们总是缺乏细心的观察与足够的思考。作者将在这篇文章中带领大家深入分布式架构中时间的真相。本文不打算使用过于专业的术语和晦涩难懂的数学公式,希望能和同学们轻松愉快的展开讨论。

我们所熟知的时间

时间是人类用以描述物质运动过程或事件发生过程的一个参数。确定时间的方法通常是靠不受外界影响的物质周期变化的规律,例如月亮绕地球旋转周期,原子震荡周期等。
以上是基本物理学中对于时间的定义,也是我们生活中对于时间的主要感受。

能不能稍微高大上一点?
爱因斯坦在相对论中提出:不能把时间、空间、物质三者分开解释。时间与空间一起组成四维时空,构成宇宙的基本结构。时间与空间在测量上都不是绝对的,观察者在不同的相对速度或不同时空结构的测量点,所测量到时间的流逝是不同的。
就此打住,再下去的专业知识就要让人抓狂了,比如时空扭曲。好在这些不是我们今天要聊的话题。
再玄一点试试?

爱因斯坦认为:“现在、过去和将来之间的差别只是一种错觉。”这句话对时间的存在与否提出了质疑。无独有偶,佛教的创立者释迦牟尼也没有正面回答过时间的存在问题。佛经多以“一时”开头,比如大乘经典《华严经》:“如是我闻,一时,佛在摩竭提国阿兰若法菩提场中,始成正觉。”另外,佛经里对于时间长度也常用“一弹指”“一刹那”等词语描述。可见佛学的理论体系中也并没有对时间的精确定义,这种独特的时间观和现代物理学非常接近。

差不多该切入正题了
以上是分别从日常生活,现代物理学,宗教等三个角度对于时间的认知。到现在为止,我们应该知道时间并非客观精确存在的东西。但是生活中时间是如此的重要,人类的群体活动必须有一个时间作为准则,否则就乱套了。那怎么办?很简单,靠约定。

约定时间可以让一个群体准确的完成任务。注意:约定发起者的时间就算是错误的,也不影响这个群体完成任务,只要活动期间内的群体内部时间是一致的就行。
“同志们,对一下时间,准备行动”小时候看电视记得最清楚的台词就是这一句。
计算机里的时间是如何确定的呢?

学过计算机系统的同学应该都知道,计算机中的CPU并不负责计时。计算机是靠一个叫做时钟芯片的东西来产生时钟脉冲推动工作的,它就像电脑的心脏一样,持续稳定的周期性跳动。所以计算机经过时间校对后,接下来这个时钟芯片的稳定性将直接关系到这台计算机的时间后续是否会出现偏差。时钟芯片就像一个乐队的指挥家,指挥计算机的各个组件按一样的节奏协同工作。

单机工作对时间的要求是最低的
就单个计算机来讲,计算机的CPU,内存和输入输出等设备都在同一个时钟芯片的指挥下工作,所以就算这台计算机的实际时间不准确,也不会影响计算机的正常工作。
最大的麻烦来了,多台计算机协同工作


上图中3台服务器和客户的手机都处于不同的时间。如果要联合完成一项任务,比如接受一个客户的订单,究竟系统该以哪个时间作为订单产生的时间呢?
通常对于一个系统来说,越往系统内部时间越可信。比如上图中客户手机的时间是最不可信的,因为一千个客户的手机时间可能各不相同。后面的3台服务器,选任何一台服务器的时间作为订单产生的时间都是可以的。

难道就这么简单?
当然不是。如果系统稍微复杂一点,比如访问量大了以后,WEB服务和应用服务都要由多台服务器来支持,如下图。

这时候我们发现,只有最后面的数据库服务器是单台的,其他节点都变成了多台服务器。这样的系统结构在中小系统里面很常见。一般都会选取数据库服务器作为时间标准,比如在数据库中插入订单库记录的时候由数据库生成时间戳作为订单的产生时间。
生意越来越好,系统需要进一步扩容

随着客户进一步增多,系统压力越来越大,数据库也要拆分成多台服务器,如下图。

现在,那台唯一的作为时间标准的数据库服务器也不存在了。我们应该怎么做?在目前对时间的认知程度上,很难再讨论这个问题的解决方案了。所以,让我们先离开这个问题,回到对时间的认识上来。

我们需要对时间多一点认识
我们列出时间的几个重要特性如下,之后会逐一进行分析,并运用到分布式架构中来尝试解决时间这个难题。
时间是靠约定的
可接受的时间精度
不存在真正的并发,凡事有先后
让我们从第一个特性开始吧。
时间是靠约定的
一个群体只要约定好一个时间作为群体标准时间就可以了。作为一个封闭的群体,时间标准只需要一个。而现实中的世界也就像一个封闭的系统,整个地球的人类只需要约定一个时间作为世界标准时间就可以了。
在系统架构中,我们就可以尝试引入一个叫做时间仲裁的服务节点来帮助整个系统约定时间,如下图所示:

但是实际上问题还远不止这么简单,如果系统足够庞大,就需要有多个服务器群,且服务器还不在同一个机房,甚至不在同一个城市。这时候我们就需要一个顶级时间仲裁服务节点,然后每个机房的时间仲裁节点需要跟这个顶级节点同步。对于时间精度要求一般的系统来说,可以通过NTP网络时间协议(Network Time Protocol)来把计算机的时钟和UTC世界协调时(Universal Time Coordinated)进行同步。它的对时精度在局域网内可达0.1ms,在互联网上大概能达到1-50ms。
我看一下实际场景中的NTP应用示意图:

可接受的时间精度
即便约定了标准时间,系统内各个节点的时间依然是不绝对一致的。为什么?因为时间同步需要通过网络进行,而网络是有延迟的。实际环境中,每台服务器到时间仲裁节点的网络条件是不一样的,所以各台服务器之间依然会有细小的时间差异。面对这种现状,我们需要妥协,只要保证在业务可接受精度内的时间一致就行了。

一般来说,大部分业务的时间精度需求在秒级,少数电商和金融领域的需求在毫秒级,微秒级的需求不多见。时间同步问题之所以没有特别显著,因为大部分同学经历的业务场景需求在秒级就可以满足了。实际状况,笔者这几年经历的真实生产环境中,服务器时间误差达到分钟级的都很常见。这样的时间误差,就要引起重视了。
不存在真正的并发,凡事有先后
任何两件事情,都不会在同一个时间点发生。时间是线性的,是连续不断的,用数字的方式去表示时间,是天生有缺陷的,会有时间测量颗粒度问题。理论上,只要技术够先进,时间的度量单位可以不断缩小。所以我们可以推断,世界上任何两件事情都不可能绝对的同时发生,一定是有先后的。也就是说,没有真正的所谓的并发。通常所说的并发,准确的说是在一定时间段内的请求总数,比如1秒内的请求数。

读到这里,我们明白在理论上并不存在真正并发的两个事件,但是在计算机系统中,对时间模拟的颗粒度并不是无限小的。比如现在常见的内核版本2.6的Linux系统服务器的节拍值是每秒1000次,也就是说时间的精确度在1毫秒,如果通过CPU时钟计数,可以达到微秒级。对这部分内容感兴趣的同学可以参阅《深入理解Linux内核》。
于是我们可以得出结论,在计算机世界中,由于时间颗粒度模拟的限制,会存在毫秒级别的多事件并发。
来回顾一下刚才说明的三个重要的时间特征
时间是靠约定的
可接受的时间精度
不存在真正的并发,凡事有先后
当我们一开始对时间的这几个特征并不了解的时候,也并不影响我们实施分布式架构。但是随着架构的深入和系统的日渐庞大,一旦出现问题后,我们就必须要对时间有清楚的认识才能获得有效的改进方案。
接下来我们看看基于上述特征的几个应用
应用1. 提前约定全局可用的时间
在本文前面的例子中,我们发现依靠后端数据库节点来约定时间在分布式架构下是不可靠的。我们可以考虑把时间约定的环节前移。比如在业务请求进入的时候,由一个类似时间仲裁的节点来统一分配全局范围内可辨识的业务发生时间。时间仲裁节点可以根据系统的实际业务需求,选择合适的时间精度。之后,系统内任意节点在处理这条事件记录的时候,都以这个时间为准。

应用2. 针对并发可以使用队列实现先来后到
把所有事件全部压入一个队列。把进入队列的时间约定为事件发生的时间,按照先进先出(FIFO)的原则公平的处理所有事件。这个办法简单有效,很多使用了队列的系统都获得了这个利益。但是对于更大的系统,比如使用了多个队列,那么会面临另外一个问题,就是队列之间的时间同步。这似乎是一个无底洞,但是在实际业务设计中,这并不难解决。我们可以把同类型业务的所有事件安排在同一个队列中进行,这样就解决了多队列的时间同步问题。目前流行的队列系统,比如Kafka等,都支持集群部署,可以避免单点故障。如下图所示:

应用3. 实现分布式架构下的事件全局有序回放
这是一个非常有挑战的事情。以往单机系统中,事件回放很容易,因为所有事件都有一个时间戳,这个时间戳是基准一致的。但是在分布式架构中,事件的时间戳没有一个统一的基准,集群中的每一台服务器时间都不完全一致,要做到精准有序的回放,几乎是不可能的事情。
众所周知,目前的分布式架构多用到了微服务。微服务是高内聚、低耦合的,每个服务单元都可以完成一个基本的任务,如果要协同其它微服务一起完成一个任务,就需要使用消息通讯。每个微服务单元因为高度自治,所以日志也是各自独立的。这样一来系统中就没有一个中心点负责全局日志,避免了系统中日志瓶颈点的存在的同时,也带来一些副作用。如果强行把每个服务的日志集合到一起,因为每个服务所在的服务器时间是不一样的,归集后的日志也无法实现全局有序的回放。
基于本文对时间的认识,我们尝试寻找解决方案。首先想到的是能不能在全局约定一个标准时间,比如引入时间仲裁服务。然后考虑究竟时间应该精确到什么程度才能满足需求。因为计算机的硬件限制,日志时间通常只能达到毫秒级别,再加上网络延迟抖动,我们不可能在一个复杂的分布式网络中对时间精度提出苛刻的要求。最后依据不存在真正的并发,事件必定有先后的原则,我们考虑是否可以引入一个计数器来标记事件的先后。
我们先来看一个图:

图中一共ABCD四个节点。最初由A节点先后发出m1和m2两个消息,分别发给B和C节点。B和C节点处理完事件后,先后发出m3和m4两个消息给D节点。由于时间精度的关系,实际先发出的m3和后发出的m4拥有相同的时间戳。当系统回放所有事件的时候,m3和m4就无法区别先后,很有可能m4被先于m3回放,而导致错误。
现在我们尝试提出方案。既然使用精准的时间是一条不归路,那么我们就引入一个叫做逻辑时间的概念。逻辑时间是一种组合的时间表示方式,即:精准时间+偏移量。表示为{T,C},两个逻辑时间比较的规则是先看T分量哪个大,如果相等,再看C分量。

逻辑时钟服务作为一个服务层在系统中的位置如图:

当事件通过消息在系统内传递的时候,逻辑时钟服务会分配该事件到达各个处理节点的逻辑时间,保持全局有序。当我们需要全局回放事件的时候,我们可以把位于各个微服务节点的日志记录收集到统一日志服务器,然后通过归并排序,使日志有序,最后按逻辑时间进行重放即可。由于逻辑时间中的基准时间部分和物理时间比较接近,所以也可以进行倍速重演。
改造后的方案如图所示:
当D节点收到m3和m4的时候,会向逻辑时钟服务申请全局唯一的逻辑时间。m3和m4因为先来后到的关系,分别获得偏移量001和002,这样在事件回放的时候就可以准确的判断顺序了。
当然,这只是一个简单的方案,实际在生产环境中,我们还要考虑很多问题。比如细心的同学可能已经发现问题了,如果大量的事件传递都要由逻辑时钟服务层来统一提供时间服务,就会对这个时钟层形成很大的压力,可能会成为新的性能瓶颈。所以,这个时钟服务层也必须是一个去中心化的设计。有兴趣的同学可以参阅以下主题的文章,比如Raft(分布式一致性算法),以及逻辑时钟(Lamport Clock)等等。本文不再深入。
最后,我们做一点总结
时间和空间是构成这个世界的重要基准元素。软件系统的复杂性,很多时候是在解决时间和空间的问题。越来越多的架构师已经认识到中国传统文化中“天人合一”思想的意义。比如在计算机中寻找随机数种子是一个难题,因为计算机系统是一个确定的系统,没有真正的随机。于是有人使用大气噪音(Atmospheric Noise)这种大自然的随机现象来产生随机数序列,效果非常不错。道法自然,也许是解决软件系统架构难题的最后一枚银弹。
最后希望这篇文章可以抛砖引玉,引发同学们更多的思考,谢谢。
。
。
精彩原创文章投稿有惊喜!
VSRC欢迎精品原创类文章投稿,优秀文章一旦采纳发布,将为您准备的丰富奖金税后1000元现金或等值礼品,上不封顶!如若是安全文章连载,奖金更加丰厚,税后10000元或等值礼品,上不封顶!可点击“阅读原文”了解规则。(最终奖励以文章质量为准。活动最终解释权归VSRC所有)
不知道,大家都喜欢阅读哪些类型的信息安全文章?
不知道,大家都希望我们更新关于哪些主题的干货?
精彩留言互动的热心用户,将有机会获得VSRC赠送的精美奖品一份!
同时,我们也会根据大家反馈的建议,选取热门话题,进行原创发布!
点击阅读原文进入 为了挣到10000块,他在VSRC投了一篇稿!
以上是关于技术分享分布式架构中的时间真相的主要内容,如果未能解决你的问题,请参考以下文章