“这是我自学算力大集群相关知识的第6篇笔记。基于上一篇对Raft的基本认识,我们将介绍Zookeeper 与 Crack 方案。”
摘要
- 关于 Zookeeper
n个服务器,能否带给我们n倍的性能?
在一个Raft网络中,当我们增加更多服务器时,Leader几乎肯定会成为系统性能的瓶颈,这是因为Leader必须处理每个请求并将每个请求的副本发送给其他每个服务器。这反而会导致务器数量越多,系统远行反而越慢。
但增加副本并非对系统毫无益处,随着副本的增加,可以提升读取数据时的性能。在类似于Raft的系统中,如 Zookeeper,如果客户端向一个随机副本发送请求。此时,从功能层面看,副本已具备响应客户端、处理来自客户端读请求所需的所有组件。但如何保证副本上的数据是最新的?Zookeeper 的解决思路是不会提供线性一致性的读取,即可以为读操作提供陈旧数据。
在一些对实时性要求不高但并发性很高的场景,这一策略优势较为明显,如Web网站。不过,对于数据一致性,Zookeeper 声明了两个主要的保证:1)声明写操作是线性化的,但读取操作则不是;2)对于任何给定的客户端,其操作将按照客户端指定的顺序执行。
关于第2)点的详细阐述有:读取操作只会访问某个副本。每当一个副本响应客户端的读取请求时,它特定的时间点执行该请求,并且副本会将紧邻前一条日志条目的ZXID返回給客戸端。当客户端向同一或不同副本发送请求时,它会随请求附上其所见过的最高ZXID。确保遵循先进先出的客户端顺序,如上一个操作未完成时,后续操作将等待其完成后再进行。这样做不仅可以提高读取性能,还可以满足异步写入请求。
再次强调,由于是按照客户端的视角进行的顺序操作,当多个客户端同时请求时,可能会返回不一样的结果,所以该系统可能并不具备线性一致性。
ZooKeeper 旨在解决或预期能够解决的问题类型
首先, ZooKeeper 确保了当另一台服务器发生故障时,任何一台服务器都能接管。并具备选举主节点的能力,此外, ZooKeeper 被设计为多个可能不相关活动的共享平台,仅需一个命名系统来确保这些活动的信息得以区分,避免它们相互混淆,错误地读取彼此的数据。
ZooKeeper API结构
拥有一个目录层次结构,这些文件和目录被称为z节点,z节点分为常规节点与临时节点。常规的z节点,一旦创建,除非手动删除,否则将永久存在;临时z节点,如果客户端创建了一个临时z节点,Zookeeper 会在认为客户端已死亡时删除该临时z节点。每个Z节点都有一个版本号,即当前版本号,该号码会随着节点的修改、删除以及其他更新操作而递增。
此外z节点具备顺序性,即当请求创建一个具有特定名称的文件时,实际上创建的是一个带有该名称的文件,但在名称后附加了一个数字。当多个客户端尝试同时创建顺序文件时,绝不会重复使用同一编号,并且始终为附加到文件名的序列号采用递增的编号方式。
存在一个创建RPC(远程过程调用),Zookeeper 为其提供一个名称(实际上是一个完整路径名)。其中包含一些初始数据和这些标志的某种组合以及目录层次结构,并具有独占性。独占性,如:当多个客户端尝试创建同一文件时,客户端将知晓自己是否为成功创建该文件的那一方。
客户端可以发送一个版本号,指示只有当文件的当前版本号与指定版本号相符时,才执行此操作。
注意,极端情况下,如果从某一副本读取的数据,且该副本与Leader节点断开连接,并且永久性地提供陈旧数据,客户端发送的写入请求可能会因为始终不满足最新版本号的情况而导致永远不能提交成功(即羊群效应)。
如,有1000个客户端都在尝试进行增量操作,风险在于可能除第一个被主节点接收到的请求,其余没有任何一个能够成功。因此,如果实际场景中会有大量客户端需要同时提交写或修改请求,那么需要考虑采用不同的策略。但仍可以用过使用“锁”机制来实现逐个响应请求,以降低系统的混乱与开销。
总结,ZooKeeper 本身是一个复制系统,其确保了当另一台服务器发生故障时,任何一台服务器都能接管。且,通过从任意副本读取来实现高性能的巧妙构想,但为此牺牲了一定程度的一致性。
- 关于 Crack
Crack 是一种对较早的链式复制方案的优化。其实现了与 Zookeeper 类似的技巧,旨在通过允许读取任意副本的方式来提升读取吞吐量,从而使得读取性能得到副本数量的倍增。但不同点在于其可在保持线性一致性的同时实现了上述要点。
什么是链式复制
链式复制用于确保拥有的多个副本都能呈现出相同的写入序列,它与Raft的拓扑结构不同,在链式复制中存在一系列服务器其中第一个被称为“头节点”,最后一个被称为“尾部”。
当有写入请求时,比如某个客户端想要写入数据,它总是会将所有写入请求发送给头节点。当写入操作到达尾部时,尾部会将回复发送回客户端,告知:“我们已完成您的写入。”
而在读取时,如果客户端希望执行读取操作,它会向尾部发送读请求,这些读请求被发送至尾部,而尾部则直接根据其当前状态进行响应。
当故障发生时,若是头节点发生故障。则下一个节点可以简单地接管头节点,完成写入操作并继续向下传递。如果有一个写操作在头节点崩溃之前已经到达,但头节点并未转发它,那么这个写操作则不会被提交,客户端也不会得到回应。
如果尾部失败,则上一个节点可直接接管。
当中间节点发生故障时,配置管理器会将其从链中移除,其前节点需要向新的后继节点重新发送最近的权限。
如果第二个节点停止与头节点通信,第二节点能否直接接管?
需要通过配置管理器来实现对链的管理,配置管理器的职责仅在于监控活跃性,在所有服务器中,配置管理器判定某台服务器失效时,便会发出一份新的配置,其中该链将拥有新的定义 — 头节点、尾节点等。配置管理器通常采用 Raft 或 Paxos 算法,而在 Crack 的情况下,则使用Zookeeper,它本身当然也是基于类似 Raft 的方案构建的。如果该服务器确实发生故障,那么头节点需要持续尝试,直至从配置管理器处获取新的配置信息。
在数据中心中,通常的完整配置包括一个基于 Raft 、Paxos 或其他类似协议的配置管理器。因此,它具有容错性,且不会遭受脑裂问题。
方案缺陷
链式复制存在一个性能问题,即如果其中任何一个副本因故稍显迟缓也会拖慢所有写操作的速度,因为每次写入都必须通过每个副本。而 Raft 和Paxos 则不存在此问题,以 Raft 为例,如果其中一个跟随者反应较慢,这并不影响系统运行,因为领导者只需等待多数派的响应即可。
以上内容来自对 MIT 《分布式系统 6.824》的学习笔记,在此特别声明。