在分布式系统中,我们经常遇到多数据副本保持一致的问题,在我们所能找到的资料中该问题讲的很笼统,模模糊糊的,把多个问题或分类糅合在一起,难以理解。在思考和翻阅资料后,通俗地把一致性的问题可分解为2个问题:

1、任何一次修改保证数据一致性。

2、多次数据修改的一致性。

在弱一致性的算法,不要求每次修改的内容在修改后多副本的内容是一致的,对问题1的解决比较宽松,更多解决问题2,该类算法追求每次修改的高度并发性,减少多副本之间修改的关联性,以获得更好的并发性能。例如最终一致性,无所谓每次用户修改后的多副本的一致性及格过,只要求在单调的时间方向上,数据最终保持一致,如此获得了修改极大的并发性能。

在强一致性的算法中,强调单次修改后结果的一致,需要保证了对问题1和问题2要求的实现,牺牲了并发性能。本文是讨论对解决问题1实现算法,这些算法往往在强一致性要求的应用中使用。

解决问题1的方法,通常有两阶段提交算法、采用分布式锁服务和采用乐观锁原理实现的同步方式,下面分别介绍这几种算法的实现原理。

  • 两阶段提交算法


在两阶段提交协议中,系统一般包含两类机器(或节点):一类为协调者(coordinator),通常一个系统中只有一个;另一类为事务参与者(participants,cohorts或workers),一般包含多个,在数据存储系统中可以理解为数据副本的个数。两阶段提交协议由两个阶段组成,在正常的执行下,这两个阶段的执行过程如下所述:

  • 分布式锁服务

 分布式锁是对数据被外界修改持保守态度,在整个数据处理过程中将数据处于锁定状态,在用户修改数据的同时,其它用户不允许修改。
      采用分布式锁服务实现数据一致性,是在操作目标之前先获取操作许可,然后再执行操作,如果其他用户同时尝试操作该目标将被阻止,直到前一个用户释放许可后,其他用户才能够操作目标。分析这个过程,如果只有一个用户操作目标,没有多个用户并发冲突,也申请了操作许可,造成了由于申请操作许可所带来的资源使用消耗,浪费网络通信和增加了延时。
      采用分布式锁实现多副本内容修改的一致性问题, 选择控制内容颗粒度实现申请锁服务。例如我们要保证一个文件的多个副本修改一致, 可以对整个文件修改设置一把锁,修改时申请锁,修改这个文件的多个副本,确保多个副本修改的一致,修改完成后释放锁;也可以对文件分段,或者是文件中的单个字节设置锁, 实现更细颗粒度的锁操作,减少冲突。

常用的锁实现算法有Lamport bakery algorithm (俗称面包店算法), 还有Paxos算法。下面对其原理做简单概述。


  • Lamport面包店算法


是解决多个线程并发访问一个共享的单用户资源的互斥问题的算法。 由Leslie Lamport英语Leslie Lamport)发明。    
    Lamport把这个并发控制算法可以非常直观地类比为顾客去面包店采购。面包店只能接待一位顾客的采购。已知有n位顾客要进入面包店采购,安排他们按照次序在前台登记一个签到号码。该签到号码逐次加1。根据签到号码的由小到大的顺序依次入店购货。完成购买的顾客在前台把其签到号码归0. 如果完成购买的顾客要再次进店购买,就必须重新排队。

     这个类比中的顾客就相当于线程,而入店购货就是进入临界区独占访问该共享资源。由于计算机实现的特点,存在两个线程获得相同的签到号码的情况,这是因为两个线程几乎同时申请排队的签到号码,读取已经发出去的签到号码情况,这两个线程读到的数据是完全一样的,然后各自在读到的数据上找到最大值,再加1作为自己的排队签到号码。为此,该算法规定如果两个线程的排队签到号码相等,则线程id号较小的具有优先权。

把该算法原理与分布式系统相结合,即可实现分步锁。


  • Paxos算法


 该算法比较热门,参见WIKI,http://zh.wikipedia.org/wiki/Paxos%E7%AE%97%E6%B3%95

Paxos算法解决的问题是一个分布式系统如何就某个值(决议)达成一致。一个典型的场景是,在一个分布式数据库系统中,如果各节点的初始状态一致,每个节点都执行相同的操作序列,那么他们最后能得到一个一致的状态。为保证每个节点执行相同的命令序列,需要在每一条指令上执行一个“一致性算法”以保证每个节点看到的指令一致。一个通用的一致性算法可以应用在许多场景中,是分布式计算中的重要问题。节点通信存在两种模型:共享内存(Shared memory)和消息传递(Messages passing)。Paxos算法就是一种基于消息传递模型的一致性算法。BigTable使用一个分布式数据锁服务Chubby,而Chubby使用Paxos算法来保证备份的一致性。

  • 采用乐观锁原理实现的同步

      我们举个例子说明该算法的实现原理。如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进行修改时(如更改用户帐户余额),如果采用前面的分布式锁服务机制,也就意味着整个操作过程中(从操作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操作员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对几百上千个并发,这样的情况将导致怎样的后果。
      乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
     
 对于上面修改用户帐户信息的例子而言,假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。

乐观锁机制与分布式系统相结合上, 我整理了伪代码如下:

该算法未考虑节点下线、失效等问题,在后续我将分析采用乐观锁原理实现一致性算法,解决问题2、节点失效、通信失败等问题。

11-08 10:34