本文知识点来源于官网地址https://www.cockroachlabs.com/docs/v22.1/architecture/replication-layer.html
CRDB体系结构的复制层在节点之间复制数据,并通过我们的共识算法确保这些副本之间的一致性。
高可用性要求数据库能够容忍节点离线,而不会中断对应用程序的服务。通过在节点之间复制数据可以确保数据一直可以被访问。
节点离线时仍然能保障数据的一致性是一个挑战,CRDB使用了一种共识算法,要求在提交更改之前,所有副本的仲裁都同意对range的任何更改。
3是实现quorum的最小数目(即3个中的2个),所以CRDB的高可用性需要至少3个节点。
可容忍的失败数等于 (复制因子- 1)/2 。例如,对于3副本可以容忍一个节点失效;5副本容忍两个节点失效,等等。可以在集群、数据库和表级别控制复制因子。
当发生故障时,CRDB会自动意识到节点停止响应,并努力重新分配数据,以继续最大化生存能力。
当新的节点加入集群时,数据会自动在集群上重新平衡,确保负载均匀分布。
Raft是一种共识协议——它是一种算法,可以确保数据安全地存储在多台机器上,即使其中一些机器暂时断开,也能就当前状态达成一致。
Raft将包含range副本的所有节点组织成一个组——Raft组。Raft组中的每个副本要么是leader,要么是follower。leader由Raft选出,负责协调所有写入到Raft组的操作。它会定期给follower发送心跳,并保持他们的日志被复制。在没有心跳的情况下,follower在随机选举超时后成为candidate,继续举行新的leader选举。
还引入了第三种副本类型,即 “不投票”副本。这些副本不参与Raft选举,但对于需要低延迟多区域读操作场景非常有用。
一旦节点接收到它所包含range的BatchRequest,它就会将这些KV操作转换为Raft命令。这些命令被提交给Raft leader——并被写入Raft日志。
当写入操作达到quorum数量(如3个中的2个)并由Raft leader提交时,就会被追加到Raft日志。这包含了所有副本一致同意执行的有序命令集。
因为该日志是序列化的,所以可以通过重放日志的方式使节点从过去的状态恢复到当前状态。该日志还允许临时脱机的节点“恢复”到当前状态,而不需要以快照的形式接收现有数据的副本。
在V21.1版本以前,CRDB只支持投票副本,所有副本都要作为投票者参与Raft共识协议。但是,需要所有的副本参与到共识算法中,意味着增加复制因子以增加写延迟为代价,因为额外的副本需要参与Raft仲裁。
为了更好地支持多区域集群,比如让多region读取更高效,引入了一种新的副本类型:“不投票”副本。
不投票副本跟随Raft日志(因此能够提供跟随读取),但不参与仲裁。它们对写延迟几乎没有影响。
它们有时也被称为只读副本,因为它们只提供读服务,而不参与仲裁(因此不会产生相关的延迟成本)。
非投票副本可以通过num_voters和num_replicas来配置。当num_voters小于num_replicas时,差值代表不投票副本的数量。但是,大多数用户应该使用高级的多区域SQL特性来控制不投票副本位置。
每个副本都可以打“快照”,它将根据特定的时间戳(MVCC功能)复制所有数据。此快照可以在重新平衡期间发送到其他节点,以加快复制。
在加载快照之后,节点通过重放Raft组日志中自快照拍摄以来发生的所有操作来获取最新信息。
Raft组中的单个节点充当租赁者(以下统称为leaseholder),这是唯一可以向Raft leader提供读操作或提议写操作的节点。
CRDB尝试将leaseholder和Raft leader放在同一个节点,这样可以优化写的速度。
如果没有leaseholder,任何接收到请求的节点都将尝试成为该range的leaseholder。为了防止两个节点获取租约,请求者包含它拥有的最后一个有效租约的副本;如果另一个节点成为leaseholder,它的请求将被忽略。
当提供强一致的读取时,leaseholder绕过Raft;对于首先提交的leaseholder写操作,它们必须已经达成共识,因此没有必要对相同的数据进行第二次共识。这样做的好处是不会产生Raft所要求的网络往返延迟,并大大提高了读取速度(而不牺牲一致性)。
range租约完全独立于Raft leader,因此如果没有进一步动作的话,Raft leader和range租约可能不会由同一个副本持有。但是我们可以通过将同一个节点同时设置为Raft leader和leaseholder来优化查询性能;如果接收请求的leaseholder可以简单地向自己提出Raft命令,而不是与另一个节点通信,就可以减少网络往返。
为了实现这一点,每次租约续期或转让都试图对它们进行配置。在实践中,这意味着不匹配是罕见的,而且自我纠正很快。
为了管理表数据的租约,CRDB实现了“epoch”的概念,它被定义为节点加入集群和节点从集群断开之间的时间段。为了延长租约,每个节点必须定期更新活动记录,该记录存储在一个系统range的key上。当一个节点断开连接时,它将停止更新活动记录,并且认为epoch已更改。这将导致节点在活动记录过期几秒钟后失去所有租约。
因为租约直到节点断开与集群的连接才到期,所以leaseholder不必单独更新自己的租期。以这种方式将租约与节点活动联系起来,可以让我们消除大量的流量和Raft处理,同时仍然跟踪每个range的租约。
meta和系统 range也是正常的key-value数据,因此像表数据一样有租约。
然而,与表数据不同的是,系统range不能使用基于epoch的租约,因为这会创建一个循环依赖:系统range已经被用于为表数据实现基于epoch的租约。因此,系统range使用基于到期的租约代替。基于过期的租约在特定时间戳(通常在几秒钟后)到期。但是,只要节点继续提出Raft命令,它就会继续延长租约。如果没有,则下一个包含该range副本的节点将成为leaseholder,该副本试图从该range读取或写入。
当集群需要访问故障的leaseholder节点上的range时,该range的租约权必须转移到正常的节点。这个过程如下:
因为CRDB是从range的leaseholder中读取数据,所以如果跟读最靠近的副本持有租约权,那么性能会更好。但由于到集群的流量是不断变化的,我们希望能动态地转移哪些节点持有租约。
每个leaseholder会定期(大型集群默认每10分钟一次,小型集群则更频繁)判断是否应该将租约转移到另一个副本,主要考虑因素如下:
Intra-locality
如果所有副本都位于同一位置,则完全根据每个包含副本的节点上的租约数量做出决策,试图在所有节点上实现租约的大致公平分布。这意味着分布不是完全平等的;它有意容忍节点之间的小偏差,以防止抖动(例如,为了达到平衡而进行的过度调整)。
Inter-locality
如果副本位于不同的位置,将尝试计算哪个副本将成为最佳的租约者,即提供最低的延迟。
为了实现动态的租约者再平衡,一个区域的当前租约者跟踪它从每个地区收到的请求的数量,作为指数加权移动平均数。这种计算的结果是,最近请求range最多的地区通常被赋予最大的权重。如果另一个位置开始非常频繁地请求该范围,那么这个计算将转移到给第二个区域分配最大的权重。
当检查leaseholder的再平衡机会时,leaseholder将每个请求区域的权重(即最近请求的比例)与每个副本的区域关联起来,通过检查这些区域的相似程度。
然后,leaseholder对比其他副本评估自己的权重和延迟,以确定调整因素。权重之间的差异越大,位置之间的延迟越大,CRDB就越倾向于权重较大的位置的节点。
在检查leaseholder再平衡机会时,当前leaseholder评估每个副本的再平衡权重和权重最大的地方的调整因子。如果转移leaseholder是有益的和可行的,当前的leaseholder将把租约转让给最好的副本。
可以通过kv.allocator.load_based_lease_rebalancing.enabled和kv.allocator.lease_rebalancing_aggressiveness来控制leaseholder的再平衡。注意,根据部署的需要,您可以通过配置复制区域来对租约和副本的位置进行额外的控制。
每当集群的节点数量发生变化时,Raft组的成员也会发生变化,为了确保最佳的生存能力和性能,副本需要重新平衡。根据成员关系的变化是添加节点还是脱机节点,情况会有所不同。
除了在节点加入或离开集群时进行再平衡之外,副本还会根据集群内节点之间的相对负载自动进行再平衡。