2 分布式系统原理
2.2 基本副本协议
2.2.2 primary-secondary 协议
本文着重介绍一种非常常用的primary-secondary(也称 primary-backup)的中心化副本控制协议。
在primary-secondary 类型的协议中,副本被分为两大类,其中有且仅有一个副本作为 primary 副本,
除primary 以外的副本都作为 secondary 副本。维护 primary 副本的节点作为中心节点,中心节点负 责维护数据的更新、并发控制、协调副本的一致性。
控制 控制
控制
中心节点
节点 节点 节点
Primary-secondary 类型的协议一般要解决四大类问题:数据更新流程、数据读取方式、Primary 副本的确定和切换、数据同步(reconcile)。以下依次分析。
2.2.2.1 数据更新基本流程
流程2.2.1: Primary-secondary 协议的数据更新流程 1. 数据更新都由 primary 节点协调完成。
2. 外部节点将更新操作发给 primary 节点
3. primary 节点进行并发控制即确定并发更新操作的先后顺序 4. primary 节点将更新操作发送给 secondary 节点
5. primary 根据 secondary 节点的完成情况决定更新是否成功并将结果返回外部节点
图 2-8 primary-secondary 基本更新流程
上述基本更新流程体现了primary-secondary 协议的更新流程的基本思路。图 2-8 也给出了这个 流程的基本示意图。其中第4 步 primary 节点将更新操作发送到 secondary 节点时,往往发送的也是 更新的数据。在工程实践中,如果由 primary 直接同时发送给其他 N 个副本发送数据,则每个 secondary 的更新吞吐受限于 primary 总的出口网络带宽,最大为 primary 网络出口带宽的 1/N。为了 解决这个问题,有些系统(例如,GFS),使用接力的方式同步数据,即 primary 将更新发送给第一 个secondary 副本,第一个 secondary 副本发送给第二 secondary 副本,依次类推。由于异常,第 4 步可能在有些副本上成功,有些副本上失败,在有些副本上超时。不同的副本控制协议对于第4 步 异常的处理都不一样。例如,在提供最终一致性服务的系统中,secondary 节点可以与 primary 不一 致,只要后续secondary 节点可以慢慢同步到与 primary 一致的状态即可满足最终一致性的要求。对 于第4 步的具体处理,本节先不展开讨论,在 2.4 中介绍一种基于 Quorum 的副本控制机制。第 5 步中对于最终更新结果的处理也依赖于第4 步的具体处理,这里同样先不展开讨论。
2.2.2.2 数据读取方式
数据读取方式是primary-secondary 类协议需要解决的第二个问题。与数据更新流程类似,读取 更新 更新
同步 同步 外部节点
Secondary Primary Secondary 外部节点
方式也与一致性高度相关。如果只需要最终一致性,则读取任何副本都可以满足需求。如果需要会 话一致性,则可以为副本设置版本号,每次更新后递增版本号,用户读取副本时验证版本号,从而 保证用户读到的数据在会话范围内单调递增。使用primary-secondary 比较困难的是实现强一致性。
这里简单讨论primary-secondary 实现强一致性的几种思路。
第一、由于数据的更新流程都是由primary 控制的,primary 副本上的数据一定是最新的,所以 如果始终只读primary 副本的数据,可以实现强一致性。如果只读 primary 副本,则 secondary 副本 将不提供读服务。实践中,如果副本不与机器绑定,而是按照数据段为单位维护副本,仅有primary 副本提供读服务在很多场景下并不会造出机器资源浪费。回忆 2.1.5 节,将数据分为数据段,以数 据段为副本的基本单位,将副本分散到集群中个,假设 primary 也是随机的确定的,那么每台机器 上都有一些数据的primary 副本,也有另一些数据段的 secondary 副本。从而某台服务器实际都提供 读写服务。
例2.2.1,继续以例 2.1.5 说明,某系统中的数据有 3 个数据段 o、p、q,每个数据段都有三个副 本,其中有一个primary 副本,系统中有 4 台机器,第一台机器上有数据段 o(Primary)、p、q,第二 台机器上有数据段o、p(primary),第三台机器上有数据段 p、o,第四台机器上有数据段 q(primary)、
o。从这个例子可以看出,只要 primary 副本分散到集群中,即使只有 primary 副本提供读写服务,
也可以充分利用集群机器资源。
第二、由primary 控制节点 secondary 节点的可用性。当 primary 更新某个 secondary 副本不成功 时,primary 将该 secondary 副本标记为不可用,从而用户不再读取该不可用的副本。不可用的 secondary 副本可以继续尝试与 primary 同步数据,当与 primary 完成数据同步后,primary 可以副本 标记为可用。这种方式使得所有的可用的副本,无论是primary 还是 secondary 都是可读的,且在一 个确定的时间内,某 secondary 副本要么更新到与 primary 一致的最新状态,要么被标记为不可用,
从而符合较高的一致性要求。这种方式依赖于一个中心元数据管理系统,用于记录哪些副本可用,
哪些副本不可用。某种意义上,该方式通过降低系统的可用性来提高系统的一致性。
第三、基于Quorum 机制,本文在 2.4 节中详细分析 Quorum 机制。这里不展开讨论。
2.2.2.3 primary 副本的确定与切换
在primary-secondary 类型的协议中,另一个核心的问题是如何确定 primary 副本,尤其是在原 primary 副本所在机器出现宕机等异常时,需要有某种机制切换 primary 副本,使得某个 secondary 副本成为新的primary 副本。
通常的,在primary-secondary 类型的分布式系统中,哪个副本是 primary 这一信息都属于元信 息,由专门的元数据服务器维护。执行更新操作时,首先查询元数据服务器获取副本的 primary 信 息,从而进一步执行数据更新流程。
切换副本的难点在于两个方面,首先,如何确定节点的状态以发现原 primary 节点异常是一个 较为复杂的问题。在2.3 中,详细介绍一种基于 Lease 机制确定节点状态的方法。再者,切换 primary 后,不能影响副本的一致性。尤其是提供较强一致性服务的系统,切换 primary 的影响更是需要控 制。要达到这个目的,一种直观的方式是切换的新primary 的副本数据必须与原 primary 的副本一致。
然而在原 primary 已经发送宕机等异常时,如何确定一个 secondary 副本使得该副本上的数据与原 primary 一致又成为新的问题。该问题和上节中选择一个 secondary 副本上读取最新的数据是一个等 价问题。上节中本文在2.4.5 介绍一种基于 Quorum 机制确定新 primary 的方法。
由于分布式系统中可靠的发现节点异常是需要一定的探测时间的,这样的探测时间通常是 10 秒级别(见2.3.3 使用 lease 确定节点状态),这也意味着一旦 primary 异常,最多需要 10 秒级别的 发现时间,系统才能开始primary 的切换,在这 10 秒时间内,由于没有 primary,系统不能提供更 新服务,如果系统只能读 primary 副本,则这段时间内甚至不能提供读服务。从这里可以看到,
primary-backup 类副本协议的最大缺点就是由于 primary 切换带来的一定的停服务时间。
2.2.2.4 数据同步
Primary-secondary 型协议一般都会遇到 secondary 副本与 primary 不一致的问题。此时,不一致 的secondary 副本需要与 primary 进行同步(reconcile)。
通常不一致的形式有三种:一、由于网络分化等异常,secondary 上的数据落后于 primary 上的 数据。二、在某些协议下,secondary 上的数据有可能是脏数据,需要被丢弃。所谓脏数据是由于 primary 副本没有进行某一更新操作,而 secondary 副本上反而进行的多余的修改操作,从而造成 secondary 副本数据错误。三、secondary 是一个新增加的副本,完全没有数据,需要从其他副本上 拷贝数据。
对于第一种secondary 数据落后的情况,常见的同步方式是回放 primary 上的操作日志(通常是 redo 日志),从而追上 primary 的更新进度。本文将在 2.5 节详细讨论日志技术。对于脏数据的情况,
较好的做法是设计的分布式协议不产生脏数据。如果协议一定有产生脏数据的可能,则也应该使得 产生脏数据的概率降到非常低得情况,从而一旦发生脏数据的情况可以简单的直接丢弃有脏数据的 副本,这样相当于副本没有数据。另外,也可以设计一些基于undo 日志的方式从而可以删除脏数据。
如果secondary 副本完全没有数据,则常见的做法是直接拷贝 primary 副本的数据,这种方法往往比 回放日志追更新进度的方法快很多。但拷贝数据时 primary 副本需要能够继续提供更新服务,这就 要求primary 副本支持快照(snapshot)功能。即对某一刻的副本数据形成快照,然后拷贝快照,拷贝 完成后使用回放日志的方式追快照形成后的更新操作。