Bayou复制分布式存储系统
本文主要参考文献[1]完成。
第1章导读
Bayou是一个复制的、弱一致性的存储系统,用于移动计算环境。为了最大化可用性,Bayou为用户提供了可以任意读写访问的副本。Bayou的设计侧重于为应用程序提供一套检查和解决更新冲突的机制,确保整个系统趋向最终一致性。Bayou提供了一套协议,通过这个协议,解决冲突时可以使系统保持一致。
Bayou的冲突检测方法被称为依赖性检查。为了保证最终一致性,bayou 服务器必须能够回滚先前执行的写操作的效果,并根据全局序列化顺序重做它们。此外,bayou 允许客户端观察服务器接收到的所有写操作的结果,包括尚未最终解决冲突的暂时写操作。
Bayou复制存储系统的目标是支持各种非实时协作性的应用,诸如日历、电子邮件、书目数据库等。
bayou的目标是为处于不可靠网络连接状态下的移动设备构建一个满足最终一致性的分布式存储系统。bayou不能保证应用程序读写的数据一定是处于一致性状态的。
第2章 Bayou基本系统模型
在 Bayou系统中,部署在服务器(虚拟服务器,非实体服务器)中的每个data collection(数据收集中心)都是一份完整的备份。客户端通过Bayou提供的API与数据中心进行交互。这些基于RPC实现的API提供了两个基本功能:读和写。
在Bayou系统中,客户端和服务器可以同时运行在同一个主机中。Bayou中的server在接受client的写请求之后,client并不会等待server与其他server进行同步。因为Bayou是弱一致性的,要尽可能提高系统可用性(availability)、简易性(simplicity)、可伸缩性(scalability).
虽然单独的读写操作是在单个服务器上执行的,但是客户机不必局限于与单个服务器进行交互。实际上,在移动计算环境中,服务器之间的切换通常是可取的,bayou 提供会话保证,以减少访问不同服务器时客户端观察到的不一致。
为了在各个server之间处理冲突,server会为每个写操作提供一个全局唯一的WriteID。该ID由第一个接受该写请求的server提供。每个服务器在本地执行写操作时都会执行冲突检测和解决。
为了尽可能减少服务器之间的网络通信,bayou仅在成对通信的节点间传播写操作,这被称之为anti-entropy 会话。
第3章 冲突检测与解决
冲突检测的难点:粒度选择。如果粒度选择过大,如将一个文件作为粒度,将会出现文件替换情况导致数据丢失;如果粒度过小,如将每次操作记录作为粒度,将会检测不出来冲突。因此冲突的定义需要由应用程序来定义。
Bayou提供了依赖检查(dependency checks)来检查冲突,采用合并过程(merge procedures)来处理冲突。
Bayou_Write(update, dependency_check, mergeproc){
if(DB_Eval(dependency_check.query) != dependency_check.expected_result)
{
resolved_update = Interpret(mergeproc);
}
else
{
resolved_update = update;
}
DB_Apply(resolved_update);
}
本质上,bayou系统要求应用开发者向Bayou_Write
传入三个函数指针,分别执行更新、冲突检查和合并过程。即,bayou只是将三个函数进行打包成一个写操作,在后文中的所有对写操作的描述都是对Bayou_Write
的描述。
3.1依赖检查
与应用相关的冲突检测是通过Bayou系统的依赖检查实现的。每个写操作都包含一个依赖检查,每个依赖检查是由程序提供的查询语句和它期望的结果组成的。当服务器执行的查询语句与所拥有的数据副本不吻合时,将不会返回期望的结果,这样就检测到了冲突。这种依赖检查是执行更新操作的前提条件。如果依赖检查失败,将不会执行该更新操作,而是开始执行合并过程以解决冲突。依赖检查可以检测读写冲突。
3.2合并过程
在写操作中会提供一个合并过程以供检测到冲突时解决冲突调用。该合并过程是使用高语义的解释型语言实现的。
在实践中,Bayou合并过程是由应用程序开发者以模板的形式编写的。模板实例化时需要为每个write填充实现细节。
应用程序的用户不需要知道合并过程,除非程序无法解决冲突。在无法实现自动解决的情况下,合并过程仍将运行到完成,但预计将产生一个经过修订的更新,以某种方式记录检测到的冲突,使用户能够在稍后解决冲突。为了实现手动解决,可能使用交互式合并工具,必须以允许用户理解所发生的事情的方式向用户呈现冲突的更新。按照惯例,大多数Bayou数据收集都包含无法解决的冲突的错误日志。但是,此类约定不在Bayou存储系统的域中,可能会因应用程序而异。
当检测到冲突并且在处理冲突的过程中,Bayou依然对外提供数据的可访问性。
第4章 副本的一致性
Bayou系统保证通过反熵会话接收所有写操作,并且两个拥有相同写操作集合的服务器将具有相同的数据内容。但是,bayou不强制限制写传播延迟。即,bayou系统仅能够保证最终一致性。bayou保证所有的write操作将以定义好的顺序执行,且每次执行的效果是相同的,并且冲突检测和合并过程是确定的,以使对冲突的检测和解决的行为是一致的。
这里的内容核心是定义了写入操作的两种状态:提交(committed)和临时(tentative)。
对于所有不再被重做的写入操作称之为稳定(stability)。处于提交状态的写是稳定的。
4.1临时写
bayou的两个重要特性保证了最终一致性:
- 在所有服务器上,写操作都以相同的、定义良好的顺序执行;
- 冲突检测和合并过程是确定的,服务器将以同样的方式解决相同的冲突。
当Bayou服务器从客户端接受写入时最初被认为是试探性的。试探性写入根据其接受服务器分配给它们的时间戳进行排序。最终,通过后文描述的提交过程提交每个写入。已提交写的顺序是根据它们提交的时间和在任何临时写之前。
对于临时写,唯一的要求是在同一个服务器上被赋予单调递增的时间戳。这样通过<timestamp, WriteID>对就能对同一个服务器上的所有写操作进行排序。在不同的服务器之间没有要求对时钟进行同步(实际上在不稳定的移动式网络中也无法实现这一要求)。但是需要保持服务器之间的时钟需要接近。Bayou服务器维护了一个逻辑时钟,该逻辑时钟通常与它的实时系统进行同步。为了保证因果顺序,不同的服务器需要在反熵会话中对逻辑时钟进行修正。
由于服务器的本地写入操作已经执行,在通过反熵会话接收到其他服务器的写入操作之后,需要对先前的写入操作进行撤销操作,并与传来的写入操作合并以获取一个新的写入操作序列,并依据新的全序序列对写入操作进行重做。
Bayou通过限制合并过程只依赖于服务器当前的数据内容和合并过程本身提供的数据,确保在每个服务器上独立执行的合并过程产生一致的更新。特别是,合并过程不能访问时变的或特定于服务器的"环境"信息,如当前系统时钟或服务器名称。而且,由于超出资源使用限制而失败的合并过程必须确定地失败。这意味着所有服务器必须在分配给合并过程的CPU和内存资源上设置统一的边界,并且必须在执行期间一致地执行这些边界。一旦满足了这些条件,两个以相同副本开始的服务器将在执行Write后以相同副本结束。
4.2写稳定和提交
一个写入操作被称之为是稳定(stable)的,当且仅当该服务器上记录该写入操作的log被固定下来之后。因此临时写入操作是不稳定的,因为其log还没有固定。因为其他服务器可能具有该写入操作之前的写入操作,导致该写入操作需要被重做。
应用程序则引入了confirmation和commitment的概念来对应bayou系统的写稳定的概念。bayou提供了一套API以便于应用程序缺某个写入操作是否是稳定的。
判断写操作是否稳定的方法如下:
- 每个服务器在反熵期间不仅传送写入操作集合,还传送当前逻辑时钟的值通过适当假设写操作的传播顺序,服务器可以在时间戳低于所有服务器时钟的情况下确定写操作是稳定的。这种方法的主要缺点是,保持断开连接的服务器可能会阻止"写"的稳定,这可能导致服务器重新连接时回滚大量的"写";
- 为了加快更新的速度,在一个与某些服务器通信可能无法延长时间的环境中,Bayou系统使用了一个提交过程。也就是说,当一个Write被显式提交时,它就会变得稳定,事实上,在Bayou系统中,我们通常会交替使用术语"稳定"和"提交"。已提交的写(按提交顺序)放在每个服务器的写日志中任何暂定的写(Write)之前。这一点,再加上Bayou的反熵协议,确保服务器按照提交的顺序了解提交的写操作,从而提供了稳定性。
4.3 小结
bayou采取主服务器方案。这个方案要求指定某个server作为主服务器。在反熵过程中,主server将指定哪些写入是committed,以及它们该以什么样的顺序执行这些写入操作,并将这些信息传递给其他服务器。每个复制的数据集都可以指定不同的server作为主服务器。
bayou只是尽可能保证写入操作的提交是根据临时写顺序的时间戳进行排序的,但是并不保证这一点。
第5章 存储系统实现问题
bayou设计对底层存储系统有几点要求:
- 高效的日志记录
- 高效的撤销/重做
- 对committed和tentative数据提供不同的视图
- 支持服务器间的反熵操作
实现的三个组件:
-
写入日志
-
元组存储
执行写入操作和响应读取请求的数据库。运行在内存的关系型数据库。 -
撤销日志
5.1 元组存储
对外提供了两个视图,committed和full。临时写的效果反映在full视图,committed写的效果体现在committed视图和full视图。一个临时删除的操作可能会导致一个元组出现在committed视图但是full视图则不存在了。
元组数据库使用2bit来标记某个元组是属于full视图还是committed视图。这样可以减少存储空间(因为full视图和committed视图的数据是高度重合的),同时也使得对元组数据在full和committed视图之间的转换提供了方便,另外也便于查询操作。
为了支持反熵操作,维护了两个时间戳向量:C向量和F向量。
C向量表示最后一个committed Write执行完毕后的元组存储的时间戳,F向量表示writelog中最后一个临时写执行完毕后的元组存储的时间戳。用于指明哪些写操作需要在反熵中被传递。
为了防止在反熵期间重复接收相同的写操作,服务器维护了一个时间戳向量O向量(ommitted vector),每个服务器都存储从指定服务器接收到的最新的写操作的时间戳。
5.2 崩溃恢复
处于恢复的目的,完整的write log和tuple store的检查点都维护在外存上。undo log则只维护在内存中。
第6章 访问控制
授权粒度:一个数据集合。
权限:读权限、写权限、server权限(即创建一个新的数据集合副本)
每个用户都拥有一个公私钥对和一组访问控制证书。在启动时,用户从相应的用户获取密钥和证书。
Bayou使用三种证书来控制授予、委派和撤销对数据集的访问权:
- AC[PU, P, D] 对拥有公钥PU的用户在数据集D上授予P(read/writ/server)权限;
- D[PU, C, PY] 公钥为PY的用户使用相应的证书C将自己的权限也同样地委派给公钥为PU的用户
- R[C, PY] 公钥为PY的用户撤销所有通过C证书获取权限的用户的权限
第7章 Bayou的特点
- 冲突检测和解决需要引用程序参与
- 冲突检测与应用相关
- 每个write操作都包括一个合并过程,一旦写冲突则执行合并过程
- 局部和多对象更新。Bayou的Write操作可以自动地对一个或多个数据对象执行插入、部分修改和删除操作
- 临时写和稳定写
- 安全。有完整的身份验证和访问控制机制
第8章 思考
Bayou_Write的原子性问题
在前文对Bayou_Write的伪代码描述中,我们可以看到,在执行写操作之前会先进行冲突检查,如果检查到冲突,那么就需要执行合并过程解决冲突,最后再执行写操作。这是一个非常复杂的过程,如果Bayou_Write本身不提供原子性,那么很容易导致写写冲突。即,当检查冲突并解决冲突之后,由于进程切换,另一个进程对同一个数据进行了更改,这样在切换回原来的进程之后,这个进程认为是不存在冲突的(因为已经解决了),但是实际上已经发生了冲突。这只是其中的一种情况,更多情况请读者自行思考。因此考虑Bayou_Write的原子性是很有必要的。
如何设置主服务器
这个问题比较复杂,也是一个共识问题。这已经超出了本文讨论范围。
第9章 参考文献
TERRY D B, THEIMER M M, PETERSEN K, 等. Managing Update Conflicts in Bayou, a Weakly Connected Replicated Storage System[J]. [日期不详]: 12. . ↩︎