1.说明
前面讲的分布是遇到的一些故障,实际还是太乐观了,分布式系统的的真实场景,问题要严重得多。
这一章,从最消极的角度,讲解分布式系统实际上会遇到的多个问题。
2.fault以及partial failures
在单机环境中,一个优秀的软件,要么完全正常工作,要么完全broken,不会出现中间某种状态。
这种是确定性的(deterministic)
而在分布式环境下,情况就不一样了。
各个机器由网络互相连接,会有各种非预期的原因,导致系统处于部分瘫痪,部分正常工作的情况。称为partial failures
这些是非确定性的(non deterministic)。因此,理想化的系统模型不再可行。
因此,设计一个分布式的系统时,必须允许"partial failures"的出现,并且构建一个容错的机制,对于不同的fault,需要明确需要产生的对应行为。
3.不可靠的网络
这里讨论的分布式系统,是仅由网络互连来完成通信的。每个机器有自己的内存,磁盘。并且每个机器不能访问其他机器的内存和磁盘(称为shared-nothing系统)
shared-nothing 并不是唯一构建分布式系统的方式,但是由于以下原因而流行:
1.廉价,不需要额外的硬件支持。
2.能利用云计算服务
3.能通过冗余来实现高可用性
大部分数据中心的网络是异步网络(asynchronous packet networks)
每个节点发给另外一个节点信息,但是网络不保证信息何时到达以及信息是否到达
比如会出现如下几种问题
1.请求丢失(比如网线被扒了)
2.请求在队列中,排队发送(比如zk和rocketmq都会这样)
3.接收方机器挂了
4.接收方暂时没有响应(比如进行gc中)
5.接收方已经处理了请求,但是response在网络中丢了
6.接收方的response也在排队
比如下图
当你发送请求却还没收到回复时,根本不知道是什么原因导致的
处理这个问题的通常方法是超时:一段时间后,发送方放弃等待,并假定响应不会到达。但是,当超时发生时,远程节点可能已经得到请求并进行了处理。
3.1 网络故障实践
有没有方法能让网络变得可靠呢?似乎至今都没有成功。
因此,一定要处理网络故障,不单单表示忍受他们的发生,而是要合理的解决问题(如告诉用户网络中断等)
3.2 故障检测
有些系统需要自动检测故障节点
1.负载均衡器停止发送请求给挂掉的节点
2.单leader模型中,leader挂了的话其他follower要精选出新的leader
不幸的是,有些场景甚至很难判断节点是否挂了.
有些隐式的方式如管理页面,通知脚本等可以让用户知道一个节点是否挂掉.
3.3 超时以及无限延迟
超时作为判断的重要标准,到底应该设置多长呢?答案却不简单。
如果超时标准过长,则用户等待的时间更长。
如果超时标准过短,则有可能误判,造成消息重新发送一次给另外一个节点。
目前,大部分的一部网络都不会有无限期的延迟(unbounded delays, that is, they try to deliver packets as
quickly as possible, but there is no upper limit on the time it may take for a packet to
arrive)
从超时检测的角度来看,超时的时间也不能设置的太低也不能太高,
3.4 网络阻塞,排队
现实世界中,会有交通堵塞。网络中的情况也一样。
1.如果多个节点不断地给一个节点法宝,那么网络会让这些包排队,依次送达目标节点
2.当包到达目标节点时,目标节点可能由于CPU正忙而让请求排队,等候处理
3.TCP本身会有flow control等
上面的第一个情况见下图
诸多因素导致了网络延迟,在这种环境下,只能实验性的选择超时参数
1.测量一段时间内多个机器间的网络往返时间的分布情况,决定出预期的延迟时间
2.结合自身应用的特性,在超时时间过长或者过短的trade off之间决定合适的时间
3.甚至可以避免配置常量超时时间,有些系统按上述方法自适应的调节timeout时间,如Akka的超时器,Cassandra的动态检测,TCP的超时重传。
3.5 同步网络 VS 异步网络
有没有什么办法能让网络在固定的最大延迟内发送包呢
为了回答这个问题,先引入电话网络的例子
1.当您通过电话网络拨打电话时,它会建立一条电路:
沿着两个呼叫者之间的整个路由,为呼叫分配固定的,有保证的带宽量
该电路保持到通话结束
(比如ISDN网络以每秒4000帧的固定速率运行)
2.呼叫建立后,每个帧内(每个方向)分配16位空间。
因此,在通话期间,每一方都保证能够每隔250微秒发送正好16位的音频数据
上面这种网络称为synchronous,
即使数据会经过多个路由,但是不会出现排队情况,
因为呼叫的16位空间已经在网络的下一跳中保留。
因为没有排队,所以
网络的最大端到端延迟是固定的。我们称之为有限的延迟(bounded delay)
3.5.1 能否让网络延迟可预测
电话网络的电路和TCP的连接是非常不一样的.TCP会尽可能的里欧阳一切带宽
你可以给TCP一个可变大小的块数据(例如电子邮件或网页),并且会尽可能在最短的时间内传输
但是当TCP连接空闲时,也不使用任何带宽。
以太网和IP是分组交换(packet-switched)协议,这些协议受到排队的影响,从而导致网络无限延迟。
为什么要用分组交换呢?是为了应对突发的交通情况
电路适合音频或视频通话,这需要在呼叫期间每秒传送相当恒定的比特数。
而请求网页,发送电子邮件或上传文件,则没有任何特定的带宽需求 - 只是希望它尽快完成
所以,和电路相反,TCP根据可用的网络容量,动态调整数据传输速率。
最新的技术并不允许我们对于延迟有任何的保证,必须假设网络会有拥堵,排毒以及无限延迟这些情况的出现.
4.不可靠的时间
在分布式系统中,时间是一件棘手的事情,因为通信不是瞬时的:消息经过网络从一台机器转到另一台机器需要时间。
消息接收的时间总是比发送的时间晚,但由于网络中的可变延迟,我们不知道以后会有多少延迟。很难确定多台机器处理的逻辑与顺序。
每台机器都有自己的时钟,通常是一个石英晶体振荡器。这些设备并不完全准确,所以每台机器都有自己的时间,它可能比其他机器稍快或慢一些。
存在同步时钟的网络协议:最常用的机制是网络时间协议(NTP),它允许计算机时钟根据一组服务器报告的时间进行调整。服务器可以从更精确的时间源获取时间。
4.1 单调时间 VS Time-of-Day 时钟
现在至少有两种不同的时钟:Time-of-Day时钟 以及 单调时钟,需要区分他们
4.1.1 Time-of-day clocks
就是类似java中的System.currentTimeMillis(),返回当前时间(并不考虑闰秒)
它可以和NTP同步,但是也会有对应的问题。如果本身时间点靠后,和NTP同步之后,会跳到之前的某一个时间点。这样测量elapse time会出现问题(比如end-begin出现了负数时间)
4.1.2 单调时钟
单调时钟很适合测量时间间隔(即elapse time),在java中用System.nanoTime()
单个的单调时钟的绝对值并没有意义,可能是电脑启动之后的纳秒数,可能是类似任意的东西。
并且,比较不同机器的单调时钟并没有意义,因为代表的东西不一样(比如A时间代表A机器开机后的纳秒数,B时间代表B机器开机后的纳秒数,两个开机时间又不一样)
另外,单调时钟也能根据NTP时间来调整前进后退的速率(注意不是直接调整时间,而是调整变动的速率),继续保证单调性。
所以,在分布式系统中,使用单调时钟测量经过时间(例如超时)通常很好,因为它不假定不同节点的时钟之间有任何同步,并且对测量的轻微不准确性不敏感。
(注意,这里说的也是同一个机器上的nanoTime来测量流逝的时间,不是不同机器的nanoTime来比较)
4.2 时钟同步以及准确性
机器的时间可能并不如我们期望的那样可靠
电脑的石英时钟并不准确,可能取决于机器的温度
如果电脑时钟和NTP服务器差距太多,就会拒绝同步或者强制重置
由于防火墙等配置失误,本地可能和NTP服务器失去同步
NTP同步也受到网络延迟的影响,如果延迟巨大,NTP client也会放弃同步
等等
4.2.1 依赖同步时钟
和上一节讨论的不可靠的网络一样,时钟也是不可靠的,即便大部分时间表现良好。
因此,如果自己的应用需要时间同步,那么必须小心监控各个机器的时间。来避免某些节点的时间和其他大部分机器的时间相隔太大。
4.2.1.1 顺序事件的时间戳
下面就有这个问题
顺序如下
1.node1在本地时间42.004 写x=1,备份给node 2和3
2.node 3在本地时间42.003 接收x=1,并且自增为2,备份给node1,2
3.node 2先收到了C的x=2的请求,携带的时间戳是42.003
4.node 2后收到了A的x=1的请求,携带的时间戳是42.004
这种冲突解决的方式在之前介绍到是LWW(last write wins),这里为了避免网络延迟造成的影响,用client的时间戳而不是server的时间戳,但是两个client的时间不同步(先发生的node1 本地时间是42.004而后发生的node
2本地时间是42.003),最终导致了错误的出现
那么能否让NTP同步变得准确呢,避免这种错误的顺序发生
不太可能,因为时间同步受限于"不可靠的网络"
所谓的逻辑时钟(logical clocks),基于递增的计数器(而不是石英钟),是对这种顺序事件的一个更安全的选择
4.2.1.2 时钟读数的置信区间
由于网络的延迟,和NTP server同步也会变得不准确
因此把时钟读书当成一个时间点似乎并没有意义,它更像一个有着置信区间的范围
比如,一个系统可能有95%的信心,确信现在的时间在当前这个分钟的10.3到10.5秒之间
4.2.1.3 全局快照的同步时钟
这一节忽略吧
4.3 处理暂停
有多种原因使得线程暂停
1.如java之类的编程语言,会有GC使得县城暂停
2.操作系统进行上下文切换,让另外一个线程执行了
3.应用执行了同步的慢操作,如访问磁盘等等
这些都会造成当前线程暂停(或者说处于等待)
分布式系统中的一个节点必须假定它的执行可以在任何时刻暂停很长一段时间,即使在函数中间也是如此。
在暂停期间,世界其他地方一直在移动,甚至可能因为没有响应而宣布暂停的节点已经死机。
最终,暂停的节点可能会继续运行,甚至没有注意到它在之前一直处于睡眠状态。
4.3.1 回复时间保证
有些软件对于回复时间有严格的要求,如航天,汽车等领域。比如你不希望车祸时安全气囊由于GC等原因延迟了释放的时间。。。
这些系统有特定的“deadline”,表示程序必须在此之前响应。否则会认为整个系统失灵.
然而,这些系统会有大量额外的工作,并且有特定的语言,包,工具才能支持。
因此,构建一个real-time的系统非常昂贵,并且经常用于注重安全的嵌入式设备中。
并且,这种系统的可用性并不高,因为实时的严格要求,限制了吞吐量等。
对于大部分server端数据处理系统,用实时的方式既不经济也不合适。
因此,这种非实时的系统,必然会受到暂停的影响。
4.3.2 限制GC的影响
GC不能完全被阻止,但是可以有一些有效的手段来减少影响
比如有些语言对于GC的支持比较灵活,能够跟踪对象分配的频率,剩余free内存的大小等等
因此,当预计到一个节点需要GC时,把它从集群中抽出来(理解为当它挂了),不处理请求,等到GC结束后,重新加入集群处理请求即可。
5.总结
第二节讲了单机出现的fault以及分布式的partial failures,设计分布式系统需要自行解决这些问题
第三节讲了不可靠的网络
由于网络基于分组交换协议,因此有网络阻塞,排队等现象,会导致无限的延迟。
解决的方式就是超时,但是超时的时间如何界定,需要结合应用处理时长以及网络情况决定
第四节讲述不可靠的时间
区分了单调时间和Time-of-Day clock
讲述了未同步的时钟对应顺序事件的影响
以及提出了时间读数的置信区间的概念
再讲解了暂停造成的影响以及从GC的角度讲述了如何减小影响
6.名词
fault, partial failures
(non) deterministic
shared-nothing system
asynchronous packet networks
timeout(超时)
(un)bounded delays
network congestion
flow control
packet-switched protocols(分组交换协议)
bursty traffic
Network Time Protocal(NTP)
logical clocks
deadline
思考
超时时间与网络阻塞
记得之前有面试官问过,一个请求处理队列太长了会怎么样,太短了又会怎么样
太长:排队在后面的请求,等待时间长
太短:浪费计算机资源(假定1台机器1s能处理1k个请求,现在同时来了1k个请求,每个机器处理队列size只有100,那么代表需要几乎10倍的机器数量来完成请求处理)
网络超时时间的设定的影响,也比较类似
太短,可能会误判,不仅误判了某个节点挂掉,还导致请求重新被发送到其他节点,有可能造成请求被消费两次
太长,等待时间长,影响效率
动态的设置超时时间
文中列举的系统没有了解过。
不过zk上是配置常量的时间,一个超时检测时间应该是
周期时间(如5s) * 超时检测周期数(如4)
TCP连接和电话网络的区别
前者利用带块不固定,后者固定
TCP和电话网络的例子
这一段比较长见识,一些段落直接谷歌翻译的
java中 System.nanoTime()和System.currentTimeMillis()区别
第一次知道这俩的区别,高科技。。。
前者单调,但是仅用来测量过去的时间,后者反应当前时间,但是不保证单调
https://stackoverflow.com/questions/351565/system-currenttimemillis-vs-system-nanotime
还特意回看了一下,zk的源码里面,两种方式都有用到
LWW的缺陷在哪
结合图2
如果说按照统一到达目标server的时间,可以通过单调时钟解决“不可靠的时钟”问题,但是,可能由于网络延迟,导致实际上先发送的请求却后到达
如果说避免网络延迟,按照发送方server的时间来判断请求的先后顺序,可能由于多个发送方server时间不同步,造成实际上先发送的请求"看起来"是后发送的(图2即这个例子)
7.refer
https://www.jianshu.com/p/0a34337445e2