分布式挑战
分布式系统故障可能来自网络问题、时钟与时序问题等。
一、故障与部分失效
对于单节点上,通常程序以确定的方式运行:要么工作,要么出错,不会出现摸棱两可现象。然而对于多节点,可能会出现系统的一部分正常工作,但其他某些部分出现难以预测的故障,称为“部分失效”。
部分失效是不确定的,这种不确定性和部分失效大大提高分布式的复杂性。
二、不可靠的网络
我们假设网络是跨节点通信唯一的途径,每台机器都有自己的内存和磁盘,一台机器不能直接访问另一台机器的内存或磁盘,除非通过网络发送请求。
互联网大多数都是异步的,一个节点可以发送消息给另一个节点,但是网络不保证到达。处理这个问题通常采用超时机制:在等待一段时间后,如果没有收到回复则选择放弃,并且认为响应不会到达。
1.检测故障
许多系统需要检测节点失效的功能,能快速告知远程节点的关闭状态很有用,但不是万能的,例如TCP确认一个数据包已经发送给目标节点,但是应用程序可能在处理完成之前崩溃,此时,想知道请求是否执行成功,需要应用级别的回复。
2.超时与无限期的延迟
超时如果是故障检测唯一可行的方法,超时多久?没有答案。
假设一个虚拟网络,保证数据包最大延迟在一定范围:要么在时间d内交付,要么丢失,此外,非故障节点总能在r时间内完成请求处理,那么 2d+r 是一个理想的超时设置。
然鹅异步网络理论上延迟无限大,多数服务端无法保证在给定时间内完成请求。因此超时设置如果太小,只需要一个短暂的网络延迟就会导致包超时进而将系统标记为失效。
较好的做法是,超时设置不是一个不变的常量,而是持续测量响应时间及其变化,然后根据最新的响应时间自动调整。
3.同步与异步网络
固定电话与TCP连接不同,电路方式会预留固定带宽,在电路建立滞后其他人无法使用;而TCP连接的数据包会尽可能使用所有网络带宽。而当TCP空闲时,不暂用任何带宽。
三、不可靠的时钟
分布式系统同步机器之间的时钟,最常用的方法是网络时间协议NTP,根据一组专门的时间服务器来调整本地时间。
1.单调时钟与墙上时钟
计算机内部一般有两种时钟:墙上时钟和单调时钟。
墙上时钟
可用于与NTP同步,但会存在一些奇怪问题。例如本地时钟快于NTP,则会时间跳跃。
单调时钟
适合测量持续时间段,例如超时或服务的相应实现:Linux的clock-geitime返回的就是单调时间。可以在一个时间点读取单调时间,完成某工作,然后再次检查时钟,二者之差就是时间间隔。
单调时钟的绝对值没有任何意义。
2.时钟同步与准确性
NTP同步会受限于当时的网络环境特别是延迟。
对于闰秒的处理,最好是NPT服务器汇报时间时故意做出调整。
对于未完全可控的设备,需要留意,不能完全信任,用户可能会故意调整时钟为错误时间。
3.依赖同步的时钟
如果应用需要精确同步的时钟,最好仔细监控所有节点上的时钟偏差,如果某节点时钟漂移超出上限,应将其宣告为失效,并从集群中移除。
时间戳与事件顺序
LWW无法区分连续快速发生的连续写操作和并发写,需要额外的因果关系跟踪机制(例如版本向量)来防止因果冲突。
对于排序来讲,基于递增计数器而不是振荡石英晶体的逻辑时钟是更可靠的方式。
时钟的置信区间
全局快照与同步时钟
Google Spanner根据TrueTime API返回的时钟置信区间来实现跨数据中心的快照隔离。:根据两个置信区间的最早和最新可能的时间戳,并且两个区间没有重叠来判断事件顺序。
Spanner在提交事务前故意等待置信区间的长度,确保所有读事务足够晚才发生,避免与先前的事务的置信区间重叠。
4.进程暂停
假设数据库每个分区一个主节点,主节点才接受写入,其他节点如何确信主节点没有被宣告失效?一个方法是,主节点从其他节点获得一个租约,类似带有超时的锁,一个时间只有一个节点能拿到租约,为了维持主节点,节点必须到期前去续约,如果续约失败,则另一个节点接管。
但是,如果主节点被暂停一个时间段,暂停期间其他节点宣布它失效并选出新的节点,而前主节点不知道,暂停后回来就会产生问题。
响应时间保存
调整垃圾回收的影响
比较新的想法是把GC暂停视为节点一个计划内的临时离线,当节点启动垃圾回收,通知其他节点来接管客户端的请求。此外,系统可以提前为前端应用发出预警,应用等待当前请求完成,但停止向该节点发送新的请求,这样垃圾回收可以在无干扰情况下高效运行。
另一个方法,只对短期对象(可以快速回收)执行垃圾回收,然后在其变为长期存活对象前,采取定期重启的策略从而避免对长期存活对象执行全面回收。每次选择一个节点重新启动。重启前重新平衡节点间流量。
四、知识、真相与谎言
1.真相由多数决定
节点不能根据自己的信息来判断自身状态,因此分布式系统不能依赖单一节点。依靠的是法定票数,即节点间进行投票(quorum)。任何决策都需要来自多个节点的最小投票时。
最常见的法定票数是去系统节点半数以上,如果某些节点故障,quorom机制可以使系统继续工作。
主节点与锁
持有租约的客户端被暂停太久直到租约到期,然后另一个客户端已经获得了文件的锁租约,并开始写文件。当暂停的客户端重新回来,他仍然错误地认为合法持有锁并尝试写入文件,结果导致错误。
Fencing令牌
假设每次锁服务在授予锁或者租约时,还返回一个fencing令牌,令牌(数字)每授予一次就会递增,然后客户端每次向存储系统发送写请求,都必须包含fencing令牌。
只靠客户端自己检查锁是不够的,要求资源本身必须主动检查所持令牌信息,如果发现已经处理过更高令牌的请求,必须拒绝低令牌的写请求。
2.拜占庭故障
解决拜占庭容错的系统协议异常复杂,因此在绝大多数服务器端数据系统中,部署拜占庭容错解决方案基本不太可行。只有在没有中央决策机制的点对点网络中,拜占庭容错才更为必要。
大多数拜占庭容错算法要求系统超过三分之二的节点要功能正常。
弱的谎言形式
对于硬件问题、软件bug等不是完整的拜占庭式容错,可以采用:
-应用层添加校验和,校验TCP/UDP 中网络数据包的问题。
-NTP客户端配置多个时间服务器。
3.理论系统模型与现实
三个系统模型:
-同步模型:假定有上界的网络延迟。
-部分同步模型:系统大多数情况像一个同步系统运行,但有时会超出网络延迟,进程暂停和时钟飘逸的预期上界。
-异步模型:一个算法不会对时机做任何假设,甚至里面根本没有时钟,也没有超时机制。
三个节点失效系统模型:
-崩溃—中止模型:节点可能在任何时候突然停止,且该节点以后永远消失,无法恢复。
-崩溃—恢复模型:节点可能会在任何时候崩溃,且可能会在一段未知的时间后恢复并再次响应。
-拜占庭失效模式:节点可能发生任何事情,包括试图作弊和欺骗其他节点。
算法的正确性
例如对于fengcing令牌生成算法,要求具有:
-唯一性:两个令牌请求不能获得相同的值
-单调递增:
-可用性:请求令牌的节点如果不发生崩溃则最终一定会收到响应。
安全与活性
安全性可以理解为“没有发生意外”,活性类似于“预期的事情最终一定会发生”(最终一致性也是一种活性)。
将系统模型映射到现实
五、小结
部分失效可能是分布式系统的关键特征,我们的目标是建立能容忍部分失效的系统软件。
为了容忍错误,第一步检测错误。多数系统没有检测节点故障的机制,因此分布式算法更多依靠超时来确定远程节点是否可用,但超时无法区分网络延迟和节点故障,且网络延迟有时会导致节点被误认为发生错误。
检测错误后,后面也很难呀。。。