虽然之前数个章节介绍了分布式系统各种可能的错误,但是之前的估计仍然过于乐观,原因在于,分布式系统的故障和单节点的故障有着显著区别。本章对于分布式系统可能出现的故障做出了全面、悲观的总结。
故障与部分失效
对于单个节点上运行的程序,一个质量合格的软件要么是功能正常,要么是完全失效,而不会介于两者之间。
对于多个节点上运行的分布式软件,情况将要复杂和混乱很多,系统中可能只有一部分工作正常,或者节点正常但是网络出现故障。
云计算与超算
构建大规模计算系统具有两种不同的思路:
- 高性能计算:包含成千上万个CPU的超级计算机构成一个庞大的集群
- 云计算:具有多个数据中心,弹性/按需分配资源
相比之下,高性能计算更类似于单节点系统而不是分布式系统,书中重点为基于互联网的服务系统,相比于高性能计算具有以下不同:
- 云计算通常用于提供在线服务,需要随时提供低延迟的服务,高性能计算通常承担批处理任务
- 云计算服务通常采用通用机器构建,而高性能计算常采用专用硬件构建
- 云计算各个节点间通常采用IP与以太网通信,高性能计算则通常采用特定的网络拓扑结构
不可靠的网络
互联网与大多数网络中心的内部网络都是异步网络,一个节点可以向另一个节点发送消息当时不保证是否能到达,不保证何时到达,在发送后等待的过程中,可能发生请求丢失,请求在队列中排队,远程接受节点崩溃,远程接受节点暂时无法相应,应答丢失,应答被延迟处理等各种情况。
发送者不清楚数据包是否成功发送,只能通过接收者的应答来确定,但是应答也可能出现丢失活延迟,因此通常采用超时机制处理。
现实中的网络故障
网络分区:网络被分隔为断开的几个部分
检测故障
系统需要能够自动检测节点失效,通常具有以下功能:
- 下线:避免已经失效的节点继续分发请求
- 选举:对于主从复制数据区,如果主节点失败,需要将某个从节点提升为主节点
但是检测节点的失效是困难的,或许可以从应用级别获取错误报告,但是还是应假设无法接收到任何错误报告。重试是另一种解决方案,在等待超时后,如果没有收到响应则最终声明节点失效。
超时与无限期的延迟
如果超时是检测失效唯一的可行方案,那么超时时间长度的设定同样是个问题。设置较长的失效时间阈值会导致需要较长的时间,设置较短的失效时间阈值会导致系统对于网络或性能的波动过于敏感,如果一个节点被宣布失效,则会发生节点切换,为网络带来更多负担,从而加剧网络问题。
网络拥塞与排队
网络上数据包的排队是延迟的重要诱因,网络交换机拥挤,CPU繁忙,容器切换,TCP拥塞控制都可能引起排队,以上因素都导致网络的延迟非常不确定,超时设置并不是一个不变的常量,而是持续测量响应时间及其变化。
同步与异步网络
对于传统的电话,系统会为每个电话连接建立一条电路,这种网络本质上是同步的。
网络延迟是否可以预测
传统的电话能够使用的带宽是固定的,而TCP连接的数据包尝试使用所有的网络带宽,以太网和IP都是基于分组交换的。TCP带宽动态调整与面向分组的特性使得带宽可以更高效的利用,节约了成本。
不可靠的时钟
不同节点的设备都具有自己的物理时钟,但是各个设备的时钟或快或慢,虽然NTP可以从一定程度上同步时钟,但是并不足够精确,时钟的不同步导致了难以确定事件发生的先后顺序。
单调时钟与墙上时钟
墙上时钟
子1970年1月1日0点至今的秒数与毫秒数,不包含闰秒,墙上时钟可以与NTP同步,但是精度较为粗糙。
单调时钟
单调时钟保证总总是向前的,不会出现墙上时钟由于闰秒产生的回拨现象,单调时钟的绝对值没有意义,比较不同节点上的单调时钟同样没有意义,因为单调时钟没有固定的基准。单调时钟可以用于测量一项任务的持续时间,但是不假设有任何的时钟同步。
时钟同步与准确性
时钟可能出现的问题:晶振漂移,NTP连接失败,NTP拒绝授时,NTP网络延迟,NTP服务器故障,闰秒,虚拟机导致的虚拟化时钟,设备时钟不可控等。
依赖同步的时钟
时钟看起来简单,但是存在闰秒等问题,对于高精度依赖于时钟的软件,时钟的偏差是可能产生致命后果的。
时间戳与事件顺序
对于最后写入获胜(LWW)策略,但是LWW策略存在一定的问题:
- 由于时钟速度差异,先写入的数据可能反而被保留
- LWW无法区分连续写操作(客户端A写入后客户端B才发生增量操作)与并发写入(每个写操作都不依赖其他写)
- 由于时钟精度问题,两个节点可能产生完全相同的时间戳
即使采用NTP进行时钟同步,时钟仍然存在不精确的问题,如果需要进行排序,可以基于逻辑时钟,也就是递增计数器。
时钟的置信区间
由于NTP的误差,不应当将时钟的读数视为一个精确的时间点,而应该视为一个具有置信区间的范围,Google Spanner中的TrueTime API会告知本地时间的置信范围,查询会得到两个值[不早于,不晚于]。
全局快照的同步时钟
通常实现快照隔离需要单调递增的事务ID,但是对于分布式事务则需要全局单调递增的事务ID。那么是否可以利用同步后的墙上时钟作为事务ID?利用时钟的置信区间,在提交事务时等待置信区间的长度可以避免事务的重叠。
进程暂停
对于主从复制的系统,主节点需要维持自己主节点的身份,一种思路为主节点定时向其他节点获取一个租约,但是采用这种方式可能会因为进程的暂停导致问题出现,导致进程暂停的可能有很多:垃圾回收,虚拟机切换,进程上下文切换,磁盘或网络I/O,缺页中断,OS命令。分布式系统必须假定执行过程中的进程可能随时暂停相当长的时间。
响应时间保证
对于应用于航天等场合的嵌入式设备,可以通过RTOS保证实时性,但是对于服务器来说保证实时性并不合适。
调整垃圾回收的影响
调整垃圾回收的策略,从而减少垃圾回收对于进程暂停的影响。
知识,真相与谎言
本部分用于介绍在构建分布式系统时进行的假设。
真相又多数决定
首先,节点自身是无法判断自身的状态的,分布式系统中的决策需要依靠法定票数,最常见的法定票数是取节点半数以上。
主节点与锁
在很多情况下,分布式系统只允许在系统内具有一个实例,例如主从复制模式中只有一个主节点,只允许一个事务或客户端持有特定资源的锁,尤其需要注意的是,某个节点自认为自己是唯一的一个,但并不一定获得了系统内法定票数的同意。例如持有租约的主节点可能因为进程暂停失去主节点身份,但是仍然自认为是主节点从而向其他节点发送指令或修改存储,从而引发破坏。
Fencing令牌
可以通过fencing机制保证节点可以意识到租约过期,原理为在授予锁或租约时,返回一个fensing令牌,令牌每授予一次就会递增,拥有锁的节点在对于存储进行修改时需附带令牌编号,存储服务会记录各个修改请求中的令牌编号,如果某个请求中令牌编号小于记录中的最大值,则拒绝此次服务。这种方式要求资源必须检查锁编号,并能主动拒绝。
拜占庭故障
fensing令牌可以排除无意的误操作,但是如果节点故意破坏系统,只需要伪造令牌即可。本书总是假设节点不一定可靠,但总是诚实,而在互不信任的环境中达到的共识则称为拜占庭将军问题。
系统中的某些节点由于故障导致不遵守协议或恶意攻击,但仍可以继续运行,那么称为拜占庭式容错系统,例如航天领域可能由于辐射导致寄存器数据发生改变,比特币系统用于让互不信任的当事方对于交易达成一致。不过本书中通常不需要考虑部署拜占庭式容错系统。
弱的谎言形式
通常节点是诚实的,但是需要一些方式避免一些弱的、无意的谎言,这种谎言可能是由于硬件故障,配置错误等原因引起的,可采用的方法有:
- 添加数据校验和
- 对于开放输入内容则检测用户输入,防止SQL注入等操作
- 对于NTP等服务,可以采用多个授时服务器
理论系统模型与现实
分布式系统的构建需要定义一些系统模型来形式化描述算法的前提条件。
计时模型:
- 同步模型:由上界的网络延迟、进程暂停和时钟误差
- 部分同步模型:大部分情况下如同同步系统一样运行,但有时延迟、进程暂停、时钟误差会超出上限
- 异步模型:不对时间做出任何假设
节点失效模型:
崩溃-中止模型:节点只会在发生故障后永久消失
崩溃-恢复模型:节点在故障后,可能在一段未知长度的时间后恢复并再次响应
-
拜占庭失效模型:节点可能发生任何故障,包括试图作弊或欺骗
对于真实系统的建模,通常在两方面都折中,即采用部分同步模型和崩溃-恢复模型。
算法的正确性
为了描述分布式算法的正确性,可以通过描述目标分布式系统的相关属性来定义其正确性,如果某种算法在各种情况下都能满足属性要求,那么这个算法就是正确的。
安全与活性
安全性可理解为“没有发生意外”,活性可以理解为“预期的事情最终一定会发生“,如果违反了安全性,可以明确指向发生的特定时间点,如果违反了活性,可能无法明确指向具体的时间点。
对于分布式系统,要求在所有可能的情况下都符合安全性,也就是即便任何故障发生都不会返回错误结果,而对于活性则可以放宽,只在一定条件满足时达成活性,例如必须多数节点没有崩溃,且网络最终可以恢复时满足活性。
将系统模型映射到现实世界
理论上的模型和现实世界还有差距。
小结
本章讨论遵循以下逻辑进行:
- 分布式系统的特点
- 不可靠的网络
- 不可靠的时钟
- 进程暂停
- 谎言