资源描述
分布式数据库如何平衡一致性和读写延迟?
为了提供高可用能力、防止数据丧失,在分布式数据库或存储系统中需要 设立数据副本机制,而副本的引入,可以说是分布式存储中的"万恶之源"。
多副本之间应该满足强一致吗?强一致会导致请求延迟增加多少?强一致 约束下能提供哪些可用性?诸如此类,种种问题,不一而足。
此外,分布式系统中的CAP原理可以被表述为:在网络分区存在的情况 下,强一致与可用性是不可兼得的。由此开展出符合BASE标准的NoSQL 数据库,在这类数据库中,以最终一致性取代强一致性。
那么,我们所说的强一致和最终一致究竟是指什么呢?
强一致意味着多副本数据间的绝对一致吗?显然,在分布式系统中,由于 网络通信延迟的存在,多副本的严格一致是不可能的。
那是代表返回写入请求时多副本已经到达完全一致了吗?熟悉Raft的朋 友会立即指出,不一定,Raft就只需要在quorum中(超过半数)副本 达成一致即可返回写入成功。
抑或是只需要quorum的一致即可吗?这取决于具体的算法,如果我们不 限定读取操作只被leader处理,那么,达成quorum 一致之后仍然可能 读取到旧数据。
而在实际系统中,一致性问题的解法可能更加复杂,需要在一致性、读写 延迟中做出权衡。
一种实现方式是,假如每个leader在提供读服务时都不做额外操作,那 么,如果多数派分区的leader已经完成了新的写入,少数派分区的 leader仍然提供读服务,就可能读到旧数据。
这个问题的一种解决方法是,让少数派分区的leader直接拒绝读服务。
这如何实现呢?让leader在与客户端交互,完成读操作前发送一个 no-op并至少得到半数回应,由于少数派分区的leader无法得到半数回 应,因此无法提供读服务。
关于如何在Raft中获得线性一致性的详细探讨可详见Raft论文[7]中第 8 节 Client Interaction,,
TDengine提供的一致性级别在上述的分析中可以看到,Ra代中实现线性一致性会为读操作和写操作 都带来至少2个RTT的延迟(client视角,从client到leader , 再由leader到follower);即使仅实现顺序一致性,也会在写时带来 至少2个RTT的延迟。
在TDengine中,为了降低写入数据的延迟、提高吞吐量,我们为元数据 (表数据、表的标签数据)提供强一致性,为时序数据提供最终一致性与强一致性两种可选的一致性级别。当用户选择最终一致同步,写入的延迟 可以被降低到1个RTT (从client到leader),这大大优于Raft这 类强一致复制协议提供的性能。 随着TDengine集群版的开源,用户数量与日俱增,TDengine被应用到 了多种多样复杂的环境中。当集群中存在网络分区、或节点连续宕机等异 常情况下,TDengine中可能无法保证严格的强一致性,因此,在即将到 来的3.0版本中,我们将以Raft算法为基础重构选主、强一致复制等一 系列流程,同时,仍然为时序数据提供最终一致与强一致两种同步模式, 给用户提供灵活的选择,帮助用户适应最复杂的业务场景需求。
以时序数据库TDengine为例,我们为元数据的读写提供强一致性”寸序 数据在局部场景中那么需要降低读写延迟、提高吞吐,仅需满足最终一致即 可;而在另一些场景中,时序数据又需要有强一致保证。
为了更好地探讨一致性的相关问题,我们首先需要为不同的一致性级别下 一个定义。
从并发模型中的一致性到分布式多副本一致性一致性模型(consistency model)最早是定义在并发模型上的[l]o常 用的一致性级别定义包括以下5种:
严格一致性(strict consistency )线性一致性(linearizability ,又译可线性化)
顺序一致性(sequential consistency )因果一致性(casual consistency )
FIFO 一致性(FIFO consistency,又称 PRAM consistency, pipelined RAM concsistency ) o
之所以能够从并发模型直接推广到分布式多副本系统,是因为它们都可以 建立在抽象的多读者、多写者寄存器(MWMR register, multiple writer multiple reader register)模型之上[2]0值得注意的是,这里的寄存器是抽象的概念,并不是硬件寄存器。它泛指 定义了读、写操作的一系列值的存储对象。
在并发系统中,读操作与写操作可能是多线程并发地在不同CPU上执行; 在分布式系统中,它们可能是多进程被分布在不同的物理节点上执行。但 相同的是,这两类系统中的读操作与写操作都有一定的延迟,不是瞬间完 成的。
为了便于理解,我们不会照搬形式化的定义,而是提供一些更符合直觉, 但不失准确的描述。
线性一致性:
假设在分布式系统中存在绝对的物理时钟(真实时间),进程与进程之间 不存在时间的漂移。那么,我们将读写操作的开始与结束的真实时间记录 下来,为每个操作从开始到结束的持续时间段中选择一个时刻点,视该操 作为在此时刻瞬间完成。如此,所有操作都可以被等效为一个进程在真实 时间轴上瞬时完成的读写操作。假设所有的读操作得到的都是上一次写操作 的结果,那么,该系统满足线性一致性。
Pl
Pl
/?.read() —► 1
/?.read() —► 2
图1
可以看到,进程p3的写操作R.write(3)开始和结束都分别晚于进程p2 的写操作R.write(2)的开始与结束的旦由于它们时间有交的,R.write(3) 的线性化点(操作的等效时刻)可以先于R.write(2)0因此,图1系统满 足线性一致性。
顺序一致性:
但是在实际的分布式系统中,并不存在绝对的真实时间[3]0因此,在外部 的观察者看来,任意单个进程的操作顺序是确定的,但考虑所有操作的某 种全序关系时,一个进程的读写操作可以被插入到其他进程的任意两个操 作之间。不同的插入方式,会生成不同的全序关系,只要能保证存在一个 合法的全序关系,那么满足顺序一致性。
所谓的合法是指:1.全序化后读操作必须读到上一个写操作的值;2.单个 进程内的操作先后顺序与在全序关系中的操作先后顺序一致。
Init x := 0
Pl
P2
Total order 3
0
1
Write x := 2
0
1
Write x := 2
Read x= 0
Read x= 2
0
1
Read x= 0
1
Read x= 2
1
Write x := 2
0
1
Read x= 0
1
Write x := 2
1
Read x= 2
Read x= 0
Read x= 2
Total order 1
Total order 2
Init x :二
Init x :=
Init x :=
Init x :=
图2
Init x := 0Read x= 1Read x= 2
Pl 11►
lnit x 一 °Write x := 2Write x := 1P2 11►
图3由于不存在绝对时间,我们不再要求画出读写操作的起止时刻,而将其视 为瞬间完成的操作。在图2中,全序1、2、3中,只有3是一个合法 的全序,因此图2中的读写满足顺序一致性。而在图3中,不存在一个 合法的全序,因此,图3中的读写不满足顺序一致性。
由此可见,线性一致性可以被看作顺序一致性在存在绝对时间的系统模型 下的特例。
因果一致性:
由于进程p2是读取了进程pl写入的结果之后写入,进程p2的写入值 和进程pl的写入值可能存在因果关系。那么,所有在同一个进程内的连 续读操作都不可以先读到进程p2的写入值,再读到进程pl的写入值。
Init x := 0Write x := 1
P1Init x := 0
Init x := 0
Read x= 1
Write x := 3
P2
Init x := 0
Read x= 1
Read x= 3
Init x := 0
Read x = 3
Read x = 1
Init x := 0Write x := 1
Init x := 0
PlWrite x := 3
P2 图4中,进程pl和p2 , p2先读到了 pl写x-1的结果,然后写 x: = 3。写x: = 3可能是由读x=l计算而来,因此存在因果关系。p3满足 了因果一致性,p4那么违背了因果一致性。
Init x := 0
Read x= 1
Read x= 3
P3
Init x := 0
Read x = 3
Read x = 1
P4
图5中,p2不再读x=l,因此不存在因果关系。该系统满足因果一致性, 但不满足顺序一致性。
FIFO 一致性:
FIFO 一致性不会考虑多个进程之间的操作排序。对任意一个进程的写操作 1与写操作2,假设写操作1先于写操作2完成,那么任何进程不可以先 读到写操作2的值,再读到写操作1的值。
图3就是一个违背FIFO 一致性的例子。图4与图5中,由于不存在 同一个进程中的多个写操作,因此都满足FIFO 一致性。
最终一致性(Eventual consistency ):
最终一致性,可以这样定义,假设系统中不再发生写操作,经过一段时间后, 所有的读操作都会得到同样的值。
Init x := 0Write x:=lRead x= 3
pl—I1'
In" x - 0 write x := 2Read x= 3P2 11►
Init x := 0,Write x:=3Read x= 3
p3 11-
Init x := 0,.,Read x = 2 Read x = 1 Read x = 3
P4 111k图6
以图6为例,最终一致的结果,既可能是1 ,又可能是2,还可能是3 (在不存在绝对时间的系统,甚至无法定义最后写入的值是1、2还是3), 但所有进程再足够长的时间后的读结果都必须保持一致。
多副本的一致性级别是由并发模型中的一致性级别直接应用到分布式系统 中的结果,但有少许差异。细心的朋友可能注意到,我们并没有提及严格 一致性,那是因为严格一致性要求数据被立即同步(比方在CPU的下一 个时钟周期可见),这只能存在于单机系统中,并不存在于存在消息延迟 的分布式系统中[4]0我们通常所说的强一致,通常是指线性一致性或顺序一致性[5] [6]。线性 一致性与顺序一致性之间的区别,也可以被理解为系统模型的区别,即系 统中是否存在绝对时间。弱于顺序一致性的一致性级别都可被称为弱一致, 而最终一致性是弱一致性的一种形式。 探讨一致性级别时,一个常犯的错误是与事务的隔离级别混淆,事实上, 虽然它们之间存在一定的联系,但并不是同一回事。当一个数据库在隔离 级别上满足可串行化(Serializable ),在一致性上满足线性一致性,那么称 为严格可串行化(Strict Serializable ),这是一个统一了一致性级别与隔 离级别的定义。
另一个常犯的错误是将一致性(consistency )与共识(consensus )混淆。
共识问题是在分布式系统中有明确定义的一类问题,解决共识问题的经典 算法是paxos ;强一致读写可以通过一系列的全序共识实现,Raft便是这 样一种算法。
以上定义的一致性级别可以被认为是以数据为中心(Data-centric )的一致 性级别,另一种定义方式是以客户端为中心(Client-centric ),还可以定 义出写后读、读后写、单调写、单调读的一致性,此处不再赘述[4]0
一致性级别分析:以Raft为例下面我们以Raft为例分析其一致性级别。在正常情况下,Raft是在 leader上完成读与写操作的,可以被看作单读单写的系统,假设这个系统 中将leader的时间视为绝对时间,那么可认为提供了线性一致性。
但是,我们还需要考虑异常情况,当消息延迟时,Raft可能出现短暂的双 主;假设出现网络分区,可能持续处于多主状态。
展开阅读全文