[转载]分布式系统设计二

本节主要通过设计一个分布式的cache系统,来介绍Lease的使用,甚至可以作为一个分布式cache系统,在工程中使用。如果在介绍中涉及到的一些概念,可以参考前面两篇本文:

http://www.cnblogs.com/jacksu-tencent/p/3405680.html

http://www.cnblogs.com/jacksu-tencent/p/3407712.html

背景

在一个分布式系统中,有一个中心服务器节点,中心服务器存储、维护着一些数据,这些数据是系统的元数据。系统中其他的节点通过访问中心服务器节点读取、修改其上的元数据。由于系统中各种操作都依赖于元数据,如果每次读取元数据的操作都访问中心服务器节点,那么中心服务器节点的性能成为系统的瓶颈。

要求

首先为此,设计一种元数据 cache,在各个节点上cache 元数据信息,从而减少对中心服务器节点的访问,提高性能。

另一方面,系统的正确运行严格依赖于元数据的正确,这就要求各个节点上 cache 的数据始终与中心服务器上的数据一致,cache中的数据不能是旧的脏数据。

最后,设计的 cache 系统要能最大可能的处理节点宕机、网络中断等异常,最大程度的提高系统的可用性。

基于lease的设计基本原理

中 心服务器在向各节点发送数据时同时向节点颁发一个 lease。每个 lease 具有一个有效期,和信用卡上的有效期类似,lease 上的有效期通常 是一个明确的时间点,例如 12:00:10,一旦真实时间超过这个时间点,则 lease 过期失效。这样 lease 的有效期与节点收 到 lease 的时间无关,节点可能收到 lease 时该 lease 就已经过期失效。这里首先假设中心服务器与各节点的时钟是同步的,下节中讨论 时钟不同步对 lease 的影响。中心服务器发出的 lease 的含义为:在 lease 的有效期内,中心服务器保证不会修改对应数据的值。因此,节点收到数据和 lease 后,将数据加入本地 Cache,一旦对应的 lease 超时,节点将对应的本地 cache数据删除。中心服务器在修改数据时,首先阻塞所有新的读请求,并等待之前为该数据发出的所有lease 超时过期,然后修改数据的值。

大家思考一下该原理的缺陷偶。。。。

服务器与客户端节点交互基本流程

基于 lease 的 cache,客户端节点读取元数据

1.  判断元数据是否已经处于本地 cache 且 lease 处于有效期内

1.1  是:直接返回 cache 中的元数据

1.2  否:向中心服务器节点请求读取元数据信息 

1.2.1  服务器收到读取请求后,返回元数据及一个对应的 lease

1.2.2  客户端是否成功收到服务器返回的数据

1.2.2.1  失败或超时:退出流程,读取失败,可重试

1.2.2.2  成功:将元数据与该元数据的 lease 记录到内存中,返回元数据

基于 lease 的 cache,客户端节点修改元数据流程

1.  节点向服务器发起修改元数据请求。

2.  服务器收到修改请求后,阻塞所有新的读数据请求,即接收读请求,但不返回数据。

3.  服务器等待所有与该元数据相关的 lease 超时。

4.  服务器修改元数据并向客户端节点返回修改成功。

优点

可 以保证各个节点上的 cache 与中心服务器上的中心始终一致。这是因为中心服务器节点在发送数据的同时授予了节点对应的 lease, 在 lease 有效期内,服务器不会修改数据,从而客户端节点可以放心的在 lease 有效期内 cache 数据。上述 lease 机制可以容错 的关键是:服务器一旦发出数据及 lease,无论客户端是否收到,也无论后续客户端是否宕机,也无论后续网络是否正常,服务器只要等待 lease 超 时,就可以保证对应的客户端节点不会再继续 cache 数据,从而可以放心的修改数据而不会破坏 cache 的一致性。

优化点

优化点一:服务器在修改元数据时首先要阻塞所有新的读请求,造成没有读服务。这是为了防止发出新的 lease 从而引起不断有新客户端节点持有 lease 并缓存着数据,形成“活锁” 。优化的方法很简单,服务器在进入修改数据流程后,一旦收到读请求则只返回数据但不颁发 lease。从而造成在修改流程执行的过程中,客户端可以读到元数据,只是不能缓存元数据。进一步的优化是,当进入修改流程,服务器颁发的lease 有效期限选择为已发出的 lease 的最大有效期限。这样做,客户端可以继续在服务器进入修改流程后继续缓存元数据,但服务器的等待所有 lease 过期的时间也不会因为颁发新的 lease 而不断延长。实际使用中,第一层优化就足够了,因为等待 lease 超时的时间会被“优化点二”中的优化方法大大减少。

优化点二:服务器在修改元数据时需要等待所有的 lease 过期超时,从而造成修改元数据的操作时延大大增大。优化的方法是,在等待所有的 lease 过期的过程中,服务器主动通知各个持有lease 的 节点放弃 lease 并清除 cache 中的数据,如果服务器收到客户端返回的确认放弃 lease 的消息,则服务器不需要在等待 该 lease 超时。该过程中,如果因为异常造成服务器通知失败或者客户端节点发送应答消息失败,服务器只需依照原本的流程等待 lease 超时即 可,而不会影响协议的正确性。

 

Lease机制本质

Lease 是由颁发者授予的在某一有效期内的承诺。主要有两方面的承诺

一方面颁发者一旦发出 lease,则无论接受方是否收到,也无论后续接收方处于何种状态,只要 lease 不过期,颁发者一定严守承诺;

另一方面,接收方在 lease 的有效期内可以使用颁发者的承诺,但一旦 lease 过期,接收方一定不能继续使用颁发者的承诺。

就是只要lease不过期,颁发者一定严守承诺;一旦lease过期,接收方一定不能继续使用颁发者的承诺。

Lease机制容错能力

首先,通过引入有效期,Lease 机制有非常好的容错网络异常。 Lease 颁 发过程只依赖于网络可以单向通信,即使接收方无法向颁发者发送消息,也不影响 lease的颁发。由于 lease 的有效期是一个确定的时间 点,lease 的语义与发送 lease 的具体时间无关,所以同一个 lease 可以被颁发者不断重复向接受方发送。即使颁发者偶尔发 送 lease 失败,颁发者也可以简单的通过重发的办法解决。一旦 lease 被接收方成功接受,后续 lease 机制不再依赖于网络通信,即使网 络完全中断 lease 机制也不受影响。

再者,Lease 机制能较好的容错节点宕机。 如果颁发者宕机,则宕机的颁发者通常无法改变之前的承诺,不会影响 lease 的正确性。在颁发者机恢复后,如果颁发者恢复出了之前的 lease 信 息,颁发者可以继续遵守 lease 的承诺。如果颁发者无法恢复 lease信息,则只需等待一个最大的 lease 超时时间就可以使得所有 的 lease 都失效,从而不破坏 lease 机制。例如上节中的 cache 系统的例子中,一旦服务器宕机,肯定不会修改元数据,重新恢复后,只 需等待一个最大的 lease 超时时间,所有节点上的缓存信息都将被清空。对于接受方宕机的情况,颁发者不需要做更多的容错处理,只需等 待 lease 过期失效,就可以收回承诺,实践中也就是收回之前赋予的权限、身份等。

最后,lease 机制不依赖于存储。颁 发者可以持久化颁发过的 lease 信息,从而在宕机恢复后可以使得在有效期的 lease 继续有效。但这对于 lease 机制只是一个优化,如之 前的分析,即使颁发者没有持久化 lease 信息,也可以通过等待一个最大的 lease 时间的方式使得之前所有颁发的 lease 失效,从而保证 机制继续有效。

颁发方和接收方时间不同步该如何处理呢???

分为两种情况:

如果颁发者的时钟比接收者的时钟慢,则当接收者认为 lease 已经过期的时候,颁发者依旧认为 lease 有效。接收者可以用在 lease 到期前申请新的 lease 的方式解决这个问题。

如果颁发者的时钟比接收者的时钟快, 则当颁发者认为 lease 已经过期的时候,接收者依旧认为 lease 有效,颁发者可能将 lease颁发给其他节点,造成承诺失效,影响系统的正 确性。对于这种时钟不同步,实践中的通常做法是将颁发者的有效期设置得比接收者的略大,只需大过时钟误差就可以避免对 lease 的有效性的影响。

基于lease机制如何确定节点状态

通过一个例子来讨论这个问题:

在 一个 primary-secondary 架构的系统中,有三个节点 A、B、C 互为副本,其中有一个节点为 primary,且同一时刻只能有一 个 primary 节点。另有一个节点 Q 负责判断节点 A、B、C的状态,一旦 Q 发现 primary 异常,节点 Q 将选择另一个节点作 为 primary。假设最开始时节点 A为 primary,B、C 为 secondary。节点 Q 需要判断节点 A、B、C 的状态是否正常。

通过心跳无法很好判断节点状态

节 点 A、B、C 可以周期性的向 Q 发送心跳信息,如果节点 Q 超过一段时间收不到某个节点的心跳则认为这个节点异常。这种方法的问题是假如节 点 Q 收不到节点 A 的心跳,除了节点 A 本身的异常外,也有可能是因为节点 Q 与节点 A 之间的网络中断导致的。在工程实践中,更大的可能性 不是网络中断,而是节点 Q 与节点 A 之间的网络拥塞造成的所谓“瞬断”,“瞬断”往往很快可以恢复。另一种原因甚至是节点 Q 的机器异常, 以至于处理节点 A 的心跳被延迟了,以至于节点 Q 认为节点 A 没有发送心跳。假设节点 A 本身工作正常,但 Q 与节点 A 之间的网络暂时中 断,节点 A 与节点 B、C 之间的网络正常。此时节点 Q 认为节点 A 异常,重新选择节点 B 作为新的 primary,并通知节点 A、B、 C 新的 primary 是节点 B。由于节点 Q 的通知消息到达节点 A、B、C 的顺序无法确定,假如先到达 B,则在这一时刻,系统中同时存在 两个工作中的 primary,一个是 A、另一个是 B。假如此时 A、B 都接收外部请求并与 C 同步数据,会产生严重的数据错误。上述即所谓“双主”问题。

双主问题的解决

由中心节点向其他节点发送 lease,若某个节点持有有效的 lease,则认为该节点正常可以提供服务。如节 点 A、 B、 C 依然周期性的发送 heart beat 报告自身状态,节点 Q 收到 heart beat后发送一个 lease,表示节 点 Q 确认了节点 A、B、C 的状态,并允许节点在 lease 有效期内正常工作。节点 Q 可以给 primary 节点一个特殊 的 lease,表示节点可以作为 primary 工作。一旦节点 Q 希望切换新的 primary,则只需等前一 个 primary 的 lease 过期,则就可以安全的颁发新的 lease 给新的primary 节点,而不会出现“双主”问题。

一个中心节点风险解决

借助chubby 和 zookeeper

lease 的有效期时间选择

常选择的 lease 时长是 10 秒级别,这是一个经过验证的经验值,实践中可以作为参考并综合选择合适的时长。

GFS 中的 Lease

GFS 中 使用 Lease 确定 Chuck 的 Primary 副本。 Lease 由 Master 节点颁发给 primary 副本,持有 Lease 的副本成为 primary 副本。Primary 副本控制该 chuck 的数据更新流量,确定并发更新操作在chuck 上的执行顺 序。GFS 中的 Lease 信息由 Master 在响应各个节点的 HeartBeat 时附带传递(piggyback)。 对于每一个 chuck,其上的并发更新操作的顺序在各个副本上是一致的,首先 master选择 primary 的顺序,即颁发 Lease 的顺 序,在每一任的 primary 任期内,每个 primary 决定并发更新的顺序,从而更新操作的顺序最终全局一致。 当 GFS 的 master 失去某个节点的 HeartBeat 时,只需待该节点上的 primary chuck 的 Lease 超时,就可以 为这些 chuck 重新选择 primary 副本并颁发 lease。

Chubby 与 Zookeeper 中的 Lease

Chubby 中有两处使用到 Lease 机制。

我 们知道 chubby 通过 paxos 协议实现去中心化的选择 primary 节点。一旦某个节点获得了超过半数的节点认可,该节点成 为 primary 节点,其余节点成为 secondary 节点。Secondary节点向 primary 节点发送 lease, 该 lease 的含义是:“承诺在 lease 时间内,不选举其他节点成为 primary节点”。只要 primary 节点持有超过半数节点 的 lease,那么剩余的节点就不能选举出新的 primary。一旦 primary 宕机,剩余的 secondary 节点由于不能 向 primary 节点发送 lease,将发起新的一轮 paxos选举,选举出新的 primary 节点。

除了 secondary 与 primary 之间的 lease,在 chubby 中,primary 节点也会向每个 client 节点颁发

lease。 该 lease 的含义是用来判断 client 的死活状态,一个 client 节点只有只有合法的 lease,才能与chubby 中 的 primary 进行读写操作。一个 client 如果占有 chubby 中的一个节点锁后 lease 超时,那么这个 client 占有 的 chubby 锁会被自动释放,从而实现了利用 chubby 对节点状态进行监控的功能。另外, chubby 中 client 中保存有数据 的 cache,故此 chubby 的 primary 为 cache 的数据颁发 cache lease,该过程与 前一节介 绍的基于 lease 的 cahce 机制完全类似。虽然相关文献上没有直接说明,但笔者认 为,chubby 的 cache  lease 与 primary 用于判断 client 死活状态的 lease 是可以合并为同一 个 lease的,从而可以简化系统的逻辑。

与 Chubby 不 同, Zookeeper 中的 secondary 节点(在 zookeeper 中称之为 follower)并不向 primary节点 (在 zookeeper 中称之为 leader)发送 lease, zookeeper 中的 secondary 节点如果发现没 有 primary节点则发起新的 paxos 选举,只要 primary 与 secondary 工作正常,新发起的选举由于缺乏多数 secondary 的参与而不会成功。与 Chubby 类似的是, Zookeeper 的 primary 节点也会向 client 颁 发 lease,lease 的时间正是 zookeeper 中的 session 时间。在 Zookeeper 中,临时节点是 与 session 的生命期绑定的, 当一个 client 的 session 超时,那么这个 client 创建的临时节点会 被 zookeeper 自动删除。通过监控临时节点的状态,也可以很容易的实现对节点状态的监控。在这一点 上,zookeeper 和 chubby 完全是异曲同工。

 

本节主要介绍基于quorum机制选择primary,确保数据一致性,quorum机制的相关理论可以参考别的文档,在此就不再赘述。

基本 primary-secondary 协议中, primary 负责进行更新操作的同步工作。现在基本 primary-secondary 协议中引入 quorum 机制,即 primary 成功更新 W 个副本(含 primary 本身)后向用户返回成功。读取数据时依照一致性要求的不同可以有不同的做法:如果需要强一致性的立刻读取到最新的成功提交的数据,则可以简单的只读取 primary 副本上的数据即可,也可以通过最多读取R(R>N-W)个数据的方式读取;如果需要会话一致性,则可以根据之前已经读到的数据版本号在各个副本上进 行选择性读取;如果只需要弱一致性,则可以选择任意副本读取。

在 primary-secondary 协议中,当 primary 异常时,需要选择出一个新的 primary,之后 secondary副本与 primary 同步数据。 通常情况下,选择新的 primary 的工作是由某一中心节点完成的,在引入quorum 机制后,常用的 primary 选择方式与读取数据的方式类似,即中心节点读取 R 个副本,选择R 个副本中版本号最高的副本作为新的 primary。新 primary 与至少 W 个副本完成数据同步后作为新的 primary 提供读写服务。首先, R 个副本中版本号最高的副本一定蕴含了最新的成功提交的数据。再者,虽然不能确定最高版本号的数是一个成功提交的数据,但新的 primary 在随后与 secondary 同步数据,使得该版本的副本个数达到 W,从而使得该版本的数据成为成功提交的数据。

在 N=5,W=3,R=3 的系统中,某时刻副本最大版本号为(v2 v2 v1 v1 v1),此时 v1 是

系统的最新的成功提交的数据,v2 是一个处于中间状态的未成功提交的数据。假设此刻原 primary副本异常,中心节点进行 primary 切换工作。这类“中间态”数据究竟作为“脏数据”被删除,还是作为新的数据被同步后成为生效的数据,完全取决于这个数据能否参与新 primary 的选举。下面分别分析这两种情况。

wps_clip_image-26277

第一、如图  2-12,若中心节点与其中 3 个副本通信成功,读取到的版本号为(v1 v1 v1),则任选一个副本作为 primary,新 primary 以 v1 作为最新的成功提交的版本并与其他副本同步,当与第 1、第 2 个副本同步数据时,由于第 1、第 2 个副本版本号大于 primary,属于脏数据,实践中,新 primary 也有可能与后两个副本完成同步后就提供数据服务,随后自身版本号也更新到 v2,如果系统不能保证之后的 v2 与之前的 v2 完全一样,则新

primary 在与第 1、2 个副本同步数据时不但要比较数据版本号还需要比较更新操作的具体内容是否一样。

wps_clip_image-1849

第二、若中心节点与其他 3 个副本通信成功,读取到的版本号为(v2 v1 v1),则选取版本号为v2 的副本作为新的 primary,之后,一旦新 primary 与其他 2 个副本完成数据同步,则符合 v2  的副本个数达到 W 个,成为最新的成功提交的副本,新 primary 可以提供正常的读写服务。

Zookeeper 中的 Quorum

当利用 paxos 协议外选出 primary 后,Zookeeper 的更新流量由 primary 节点控制,每次更新操作,primary 节点只需更新超过半数(含自身)的节点后就返回用户成功。每次更新操作都会递增各个节点的版本号(xzid)。当 primary 节点异常,利用 paxos 协议选举新的 primary 时,每个节点都会以自己的版本号发起 paxos 提议,从而保证了选出的新 primary 是某个超过半数副本集合中版本号最大的副本。新 primary 的版本 号未必是一个最新已提交的版本,可能是一个只更新了少于半数副本的中间态的更新版本,此时新primary 完成与超过半数的副本同步后,这个版本的数据自动满足 quorum 的半数要求;另一方面,新 primary 的版本可能是一个最新已提交的版本,但可能会存在其他副本没有参与选举但持有一个大于新 primary 的版本号的数据(中间态版本),此时这样的中间态版本数据将被认为是脏数据,在与新 primary 进行数据同步时被 zookeeper 丢弃。

 

分布式系统的实践中,广泛使用了日志技术做宕机恢复,甚至如 BigTable 等系统将日志保存到一个分布式系统中进一步增强了系统容错能力。本节介绍两种实用的日志技术 Redo Log结合checkpoint 与 No Redo/No undo Log。

Redo Log 与 Check point

Redo Log

Redo Log 更新流程

1.  将更新操作的结果(例如 Set  K1=1,则记录 K1=1)以追加写(append)的方式写入磁盘的日志文件

2.  按更新操作修改内存中的数据

3.  返回更新成功 

用 Redo Log 进行宕机恢复非常简单,只需要“回放”日志即可。

Redo Log 的宕机恢复

1.  从头读取日志文件中的每次更新操作的结果,用这些结果修改内存中的数据。

从 Redo Log 的宕机恢复流程也可以看出,只有写入日志文件的更新结果才能在宕机后恢复。这也是为什么在 Redo Log 流程中需要先更新日志文件再更新内存中的数据的原因。假如先更新内存中的数据,那么用户立刻就能读到更新后的数据,一旦在完成内存修改与写入日志之间发生宕机,那么最后一次更新操作无法恢复,但之前用户可能已经读取到了更新后的数据,从而引起不一致的问题。

Check point

宕机恢复流量的缺点是需要回放所有 redo 日志,效率较低,假如需要恢复的操作非常多,那么这个宕机恢复过程将非常漫长。解决这一问题的方法即引入 check point 技术。在简化的模型下, check point 技术的过程即将内存中的数据以某种易于重新加载的数据组织方式完整的 dump 到磁盘,从而减少宕机恢复时需要回放的日志数据。

Check point更新过程

1.  向日志文件中记录“Begin Check Point”

2.  将内存中的数据以某种易于重新加载的数据组织方式 dump 到磁盘上

3.  向日志文件中记录“End Check Point”

在 check point 流程中,数据可以继续按照redo log更新流程被 更新, 这段过程中新更新的数据可以 dump到磁盘也可以不 dump 到磁盘,具体取决于实现。例如,check point 开始 时 k1=v1,check point 过程中某次更新为 k1 = v2,那么 dump 到磁盘上的 k1 的值可以是 v1 也可以是 v2。

基于 check point 的宕机恢复流程

1.  将 dump 到磁盘的数据加载到内存。

2.  从后向前扫描日志文件,寻找最后一个“End Check Point”日志。

3.  从最后一个“End Check Point”日志向前找到最近的一个“Begin Check Point”日志,并回放该日志之后的所有更新操作日志。

注意

上 述 check point 的方式依赖 redo 日志中记录的都是更新后的数据结果这一特征,所以即使 dump的数据已经包含了某些操作的结果,重 新回放这些操作的日志也不会造成数据错误。同一条日志可以重复回放的操作即所谓具有“幂等性”的操作。工程中,有些时候 Redo 日志无法具有幂等性, 例如加法操作、append 操作等。此时,dump 的内存数据一定不能包括“begin check point”日志之后的操作。为此,有两种方 法,其一是 check point 的过程中停更新服务,不再进行新的操作,另一种方法是,设计一种支持快照(snapshot)的内存数据结构,可以 快速的将内存生成快照,然后写入check  point 日志再 dump 快照数据。至于如何设计支持快照的内存数据结构,方式也很多,例如假设内存 数据结构维护 key-value 值,那么可以使用哈希表的数据结构,当做快照时,新建一个哈希表接收新的更新,原哈希表用于 dump 数据,此时内 存中存在两个哈希表,查询数据时查询两个哈希表并合并结果。

No Undo/No Redo log

若数据维护在磁盘中,某批更新由若干个更新操作组成,这些更新操作需要原子生效,即要么同时生效,要么都不生效。

 

0/1 目 录技术中有两个目录结构,称为目录 0(Directory  0)和目录 1(Directory  1)。另有一个结构称为主记录 (Master record)记录当前正在使用的目录称为活动目录。主记录中要么记录使用目录 0,要么记录使用目录 1。目录 0 或目录 1 中记 录了各个数据的在日志文件中的位置。

活动目录为目录 1,数据有 A、B、C 三项。查目录 1 可得A、B、C 三项的值分别为 2、5、2。0/1 目录的数据更新过程始终在非活动目录上进行,只是在数据生效前,将主记录中的 0、1 值反转,从而切换主记录。

0/1 目录数据更新流程

1.  将活动目录完整拷贝到非活动目录。

2.  对于每个更新操作,新建一个日志项纪录操作后的值,并在非活动目录中将相应数据的位置修改为新建的日志项的位置。

3.  原子性修改主记录:反转主记录中的值,使得非活动目录生效。

0/1 目录的更新流程非常简单,通过 0、1 目录的主记录切换使得一批修改的生效是原子的。

0/1 目录将批量事务操作的原子性通过目录手段归结到主记录的原子切换。由于多条记录的原子修改一般较难实现而单条记录的原子修改往往可以实现,从而降低了问题实现的难度。在工程中0/1 目录的思想运用非常广泛,其形式也不局限在上述流程中,可以是内存中的两个数据结构来回切换,也可以是磁盘上的两个文件目录来回生效切换。

zookeeper

在 zookeeper 系统中,为了实现高效的数据访问,数据完全保存在内存中,但更新操作的日志不断持久化到磁盘,另一方面,为了实现较快速度的宕机恢复,zookeeper周期性的将内存数据以 checkpoint 的方式 dump 到磁盘。

 

本节介绍MVCC(Multi-version Cocurrent Control,多版本并发控制)技术。大家知道SVN的工作原理吗?SVN就是MVCC的实际应用。

MVCC 简介

顾名思义,MVCC 即多个不同版本的数据实现并发控制的技术,其基本思想是为每次事务生成一个新版本的数据,在读数据时选择不同版本的数据即可以实现对事务结果的完整性读取。在使用MVCC 时,每个事务都是基于一个已生效的基础版本进行更新,事务可以并行进行,从而可以产生一种图状结构。

 

如 图  2-14 所示,基础数据的版本为 1,同时产生了两个事务:事务 A 与事务 B。这两个事务都各自对数据进行了一些本地修改(这些修改只有事务 自己可见,不影响真正的数据),之后事务 A首先提交,生成数据版本 2;基于数据版本 2,又发起了事务 C,事务 C 继续提交,生成了数据版 本 3;最后事务 B 提交,此时事务 B 的结果需要与事务 C 的结果合并,如果数据没有冲突,即事务B 没有修改事务 A 与事务 C 修改过的变 量,那么事务 B 可以提交,否则事务 B 提交失败。

MVCC 的流程过程非常类似于 SVN 等版本控制系统的流程,或者说 SVN 等版本控制系统就是使用的 MVCC 思想。

事务在基于基础数据版本做本地修改时,为了不影响真正的数据,通常有两种做法,一是将基础数据版本中的数据完全拷贝出来再修改,SVN 即使用了这种方法,SVN  check  out 即是拷贝的过程;二是每个事务中只记录更新操作,而不记录完整的数据,读取数据时再将更新操作应用到用基础版本的数据从而计算出结果,这个过程也类似 SVN 的增量提交。

分布式 MVCC

分 布式 MVCC 的重点不在于并发控制,而在于实现分布式事务。这里首先给出一个简化的分布式事务的问题模型,之后对 MVCC 的讨论基于该问题展开。 假设在一个分布式系统中,更新操作以事务进行,每个事务包括若干个对不同节点的不同更新操作。更新事务必须具有原子性,即事务中的所有更新操作要么同时在 各个节点生效,要么都不生效。假设不存在并发的事务,即上一个事务成功提交后才进行下一个事务。

例 如,用(site, k, op, oprd)表示在 site 节点上对变量 k 进行 op 操作,操作数为 oprd。那么一个典型的事务可能是 {(site_A, var1, add, 10), (site_B, var2, sub, 1), (site_A, var3, set, 2)}, 这个事务在site_A 上将变量 var1 加 10,将变量 var3 设置为 2,在 site_B 上将变量 var2 减 1。

基于 MVCC 的分布式事务的方法为:为 每个事务分配一个递增的事务编号,这个编号也代表了数据的版本号。当事务在各个节点上执行时,各个节点只需记录更新操作及事务编号,当事务在各个节点都完 成后,在全局元信息中记录本次事务的编号。在读取数据时,先读取元信息中已成功的最大事务编号,再于各个节点上读取数据,只读取更新操作编号小于等于最后 最大已成功提交事务编号的操作,并将这些操作应用到基础数据形成读取结果。

例 2.7.1:假设系统中有两个节点 A、B。节点 A、节点 B 状态如下表

 

1.  若此时全局元信息中的最大的生效事务序号为 1,则在节点 A 上:var1=1,var2=2,在节点 B上:var3 =2;

2.  若此时全局元信息中的最大的生效事务序号为 2,则在节点 A 上:var1= 1+2=3,var2=2,在节点 B 上:var3=2,var4=1;

从 这个例子可以看出,每个节点上保存了对数据的更新操作,也就是数据的增量(delta),从而可以在读取数据时将应用不同的更新操作得出不同的数据版本。 上例中,计算编号小于等于 1 的事务操作得出的数据即为版本号为 1 的数据,计算编号小于等于 2 的事务操作得出的数据即为版本号为 2 的数据。 在新事务执行过程中,虽然更新操作已经逐步记录到各个节点,但只要全局元信息不修改,始终不会读到没有生效的事务数据,从而实现了全局一致性。另外,由于数据具有多个版本,可以自然实现对历史版本数据的读取。

上述方法的一个重要问题是,随着执行的事务越来越多,各个站点保存的更新操作会越来越多读取数据时需要应用的更新操作也越来越多。工程中可以对此周期性的启动合并操作,将历史上不再需要的版本合并为一个更新操作。例如,对例 2.7.1 中事务序号小于等于 2 的操作进行合并,合并后的节点状态如下表:

 

大家猜猜下面这幅图是干什么的,如果有接触过分布式理论的应该一看就知道;如果有正在使用分布式系统的,拿你的系统和图对比一下,了解一下你系统的 理论;本节先上个图,大家好好研究一下,因为下午有公司嘉年华,只能随后整理好,预先预告一下,下节介绍设计分布式你不得不知道的理论,围绕该图展开的理 论,让我们不要成为那个埋头制造永动机的烈士。

 

避免我们不是在制造永动机,在分布式系统设计前,几个理论必须了解。CAP、BASE、ACID、一致性以及五分钟理论。

CAP

 

C: Consistency 一致性

A: Availability 可用性(指的是快速获取数据)

P: Tolerance of network Partition 分区容忍性(分布式)

CAP理论最早是在2000年7月19号,由Berkeley的Eric Brewer教授在ACM PODC会议上的一个开题演讲中提出,PPT在。此后,MIT的Seth GilbertNancy Lynch理论上证明了Brewer猜想是正确的,CAP理论在学术上正式作为一个定理出现了。

NoSQL 一定程度上就是基于这个理论提出来的,因为传统的SQL数据库(关系型数据库)都是都是具有ACID属性,对一致性要求很高,因此降低了 A(availability)和P(partion tolerance),因此,为了提高系统性能和可扩展性,必须牺牲 C(consistency),推翻关系型数据库中ACID这一套。

依据CAP理论,从应用的需求不同,我们对数据库时,可以从三方面考虑:

· 考虑CA,这就是传统上的关系型数据库(RMDB).

· 考虑CP,主要是一些Key-value数据库,典型代表为google的Big Table

· 考虑AP,主要是一些面向文档的适用于分布式系统的数据库,如SimpleDB。

而对大型网站尤其是SNS网站,对于数据的短期存储,可用性与分区容忍性优先级要高于数据一致性,一般会尽量朝着 A、P 的方向设计,而对于数据的持久存储,可以通过传统的SQL来保证一致性(最终一致性)。

CAP理论出现后,很多大规模的网站,尤其是SNS网站的数据库设计都利用其思想,包括Amazon,Facebook和Twitter这几个新兴的IT巨头,因此,一定程度上来讲,他们都是CAP的信徒。另一方面,他们从实践上证明了CAP理论的正确性。

各个系统在CAP理论中的详细体现可以参考上一节的图:

http://www.cnblogs.com/jacksu-tencent/p/3426605.html

ACID和BASE

有趣的是,ACID的意思是酸,而BASE却是碱的意思,因此这是一个对立的东西。其实,从本质上来讲,酸(ACID)强调的一致性(CAP中的C),而碱(BASE)强调是可用性(CAP中的A)。

传统关系型数据库系统的事务都有ACID的属性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。英文为:

· Atomic: Everything in a transaction succeeds or the entire transaction is rolled back.

· Consistent: A transaction cannot leave the database in an inconsistent state.

· Isolated: Transactions cannot interfere with each other.

· Durable: Completed transactions persist, even when servers restart etc. 

中译为:

· 原子性: 整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

· 一致性: 在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。

· 隔离性: 两个事务的执行是互不干扰的,一个事务不可能看到其他事务运行时,中间某一时刻的数据。 两个事务不会发生交互。

· 持久性: 在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。 

在数据库系统中,事务的ACID属性保证了数据库的一致性,比如银行系统中,转账就是一个事务,从原账户扣除金额,以及向目标账户添加金额,这两个数据库操作的总和构成一个完整的逻辑过程,不可拆分,为原子,从而保证了整个系统中的总金额没有变化。

然而,这些ACID特性对于大型的分布式系统来说,适合高性能不兼容的。 比如,你在网上书店买书,任何一个人买书这个过程都会锁住数据库直到买书行为彻底完成(否则书本库存数可能不一致),买书完成的那一瞬间,世界上所有的人 都可以看到熟的库存减少了一本(这也意味着两个人不能同时买书)。这在小的网上书城也许可以运行的很好,可是对Amazon这种网上书城却并不是很好。

而 对于Amazon这种系统,他也许会用cache系统,剩余的库存数也许是之前几秒甚至几个小时前的快照,而不是实时的库存数,这就舍弃了一致性。并 且,Amazon可能也舍弃了独立性,当只剩下最后一本书时,也许它会允许两个人同时下单,宁愿最后给那个下单成功却没货的人道歉,而不是整个系统性能的 下降。

其实,上面的思想是从CAP理论得到的启发,在CAP理论中:

在设计分布式服务中,通常需要考虑三个应用的属性:一致性、可用性以及分区宽容性。但是在实际的设计中,不可能这三方面同时做的很好。

由于CAP理论的存在,为了提高性能,出现了ACID的一种变种BASE:

· Basic Availability:基本可用

· Soft-state :软状态/柔性事务,可以理解为”无连接”的, 而 “Hard state” 是”面向连接”的

· Eventual consistency:最终一致性,最终整个系统(时间和系统的要求有关)看到的数据是一致的。 

在 BASE中,强调可用性的同时,引入了最终一致性这个概念,不像ACID,并不需要每个事务都是一致的,只需要整个系统经过一定时间后最终达到是一致的。 比如Amazon的卖书系统,也许在卖的过程中,每个用户看到的库存数是不一样的,但最终买完后,库存数都为0。再比如SNS网络中,C更新状态,A也许 可以1分钟才看到,而B甚至5分钟后才看到,但最终大家都可以看到这个更新。

一致性

为了更好的描述客户端一致性,我们通过以下的场景来进行,这个场景中包括三个组成部分:

• 存储系统

存储系统可以理解为一个黑盒子,它为我们提供了可用性和持久性的保证。

• Process A

ProcessA主要实现从存储系统write和read操作

• Process B 和ProcessC

ProcessB和C是独立于A,并且B和C也相互独立的,它们同时也实现对存储系统的write和read操作。

下面以上面的场景来描述下不同程度的一致性:

• 强一致性

强一致性(即时一致性) 假如A先写入了一个值到存储系统,存储系统保证后续A,B,C的读取操作都将返回最新值

• 弱一致性

假如A先写入了一个值到存储系统,存储系统不能保证后续A,B,C的读取操作能读取到最新值。此种情况下有一个“不一致性窗口”的概念,它特指从A写入值,到后续操作A,B,C读取到最新值这一段时间。

• 最终一致性

最 终一致性是弱一致性的一种特例。假如A首先write了一个值到存储系统,存储系统保证如果在A,B,C后续读取之前没有其它写操作更新同样的值的话,最 终所有的读取操作都会读取到最A写入的最新值。此种情况下,如果没有失败发生的话,“不一致性窗口”的大小依赖于以下的几个因素:交互延迟,系统的负载, 以及复制技术中replica的个数(这个可以理解为master/salve模式中,salve的个数),最终一致性方面最出名的系统可以说是DNS系 统,当更新一个域名的IP以后,根据配置策略以及缓存控制策略的不同,最终所有的客户都会看到最新的值。

I/O的五分钟法则

在 1987 年,Jim Gray 与Gianfranco Putzolu 发表了这个"五分钟法则"的观点,简而言之,如果一条记录频繁被访问,就应该 放到内存里,否则的话就应该待在硬盘上按需要再访问。这个临界点就是五分钟。 看上去像一条经验性的法则,实际上五分钟的评估标准是根据投入成本判断的, 根据当时的硬件发展水准,在内存中保持1KB 的数据成本相当于硬盘中存据400 秒的开销(接近五分钟)。这个法则在1997 年左右的时候进行过一次 回顾,证实了五分钟法则依然有效(硬盘、内存实际上没有质的飞跃),而这次的回顾则是针对SSD 这个"新的旧硬件"可能带来的影响。

 

 转载请注明出处 jacksu
开源文档:  redis源码解析
开源库:  github链接
个人小站:  jack's blog

posted @ 2015-10-29 14:36  Jingle Guo  阅读(157)  评论(0编辑  收藏  举报