【AICon】 如何构建高效的 RAG 系统?RAG 技术在实际应用中遇到的挑战及应对策略?>>> 了解详情
写点什么

分布式数据库如何平衡一致性和读写延迟?

  • 2021-11-04
  • 本文字数:4109 字

    阅读完需:约 13 分钟

分布式数据库如何平衡一致性和读写延迟?

为了提供高可用能力、避免数据丢失,在分布式数据库或存储系统中需要设立数据副本机制,而副本的引入,可以说是分布式存储中的“万恶之源”。多副本之间应该满足强一致吗?强一致会导致请求延迟增加多少?强一致约束下能提供哪些可用性?诸如此类,种种问题,不一而足。

 

此外,分布式系统中的 CAP 原理可以被表述为:在网络分区存在的情况下,强一致与可用性是不可兼得的。由此发展出符合 BASE 标准的 NoSQL 数据库,在这类数据库中,以最终一致性取代强一致性。

 

那么,我们所说的强一致和最终一致究竟是指什么呢?

 

强一致意味着多副本数据间的绝对一致吗?显然,在分布式系统中,由于网络通信延迟的存在,多副本的严格一致是不可能的。

 

那是代表返回写入请求时多副本已经达到完全一致了吗?熟悉 Raft 的朋友会立即指出,不一定,Raft 就只需要在 quorum 中(超过半数)副本达成一致即可返回写入成功。

 

抑或是只需要 quorum 的一致即可吗?这取决于具体的算法,如果我们不限定读取操作只被 leader 处理,那么,达成 quorum 一致之后仍然可能读取到旧数据。

 

而在实际系统中,一致性问题的解法可能更加复杂,需要在一致性、读写延迟中做出权衡。

 

以时序数据库TDengine为例,我们为元数据的读写提供强一致性;时序数据在部分场景中则需要降低读写延迟、提高吞吐,仅需满足最终一致即可;而在另一些场景中,时序数据又需要有强一致保证。

 

为了更好地探讨一致性的相关问题,我们首先需要为不同的一致性级别下一个定义。

 

从并发模型中的一致性到分布式多副本一致性

 

一致性模型(consistency model)最早是定义在并发模型上的[1]。常用的一致性级别定义包括以下 5 种:

  • 严格一致性(strict consistency)

  • 线性一致性(linearizability,又译可线性化)

  • 顺序一致性(sequential consistency)

  • 因果一致性(casual consistency)

  • FIFO 一致性(FIFO consistency, 又称 PRAM consistency, pipelined RAM concsistency

 

之所以能够从并发模型直接推广到分布式多副本系统,是因为它们都可以建立在抽象的多读者、多写者寄存器(MWMR register, multiple writer multiple reader register)模型之上[2]。

 

值得注意的是,这里的寄存器是抽象的概念,并不是硬件寄存器。它泛指定义了读、写操作的一系列值的存储对象。

 

在并发系统中,读操作与写操作可能是多线程并发地在不同 CPU 上执行;在分布式系统中,它们可能是多进程被分布在不同的物理节点上执行。但相同的是,这两类系统中的读操作与写操作都有一定的延迟,不是瞬间完成的。

 

为了便于理解,我们不会照搬形式化的定义,而是提供一些更符合直觉,但不失准确的描述。

 

  • 线性一致性:

 

假设在分布式系统中存在绝对的物理时钟(真实时间),进程与进程之间不存在时间的漂移。那么,我们将读写操作的开始与结束的真实时间记录下来,为每个操作从开始到结束的持续时间段中选择一个时刻点,视该操作为在此时刻瞬间完成。如此,所有操作都可以被等效为一个进程在真实时间轴上瞬时完成的读写操作。若所有的读操作得到的都是上一次写操作的结果,那么,该系统满足线性一致性。

 

图 1


可以看到,进程 p3 的写操作 R.write(3)开始和结束都分别晚于进程 p2 的写操作 R.write(2)的开始与结束,但由于它们时间有交叠,R.write(3)的线性化点(操作的等效时刻)可以先于 R.write(2)。因此,图 1 系统满足线性一致性。

 

  • 顺序一致性:

 

但是在实际的分布式系统中,并不存在绝对的真实时间[3]。因此,在外部的观察者看来,任意单个进程的操作顺序是确定的,但考虑所有操作的某种全序关系时,一个进程的读写操作可以被插入到其他进程的任意两个操作之间。不同的插入方式,会生成不同的全序关系,只要能保证存在一个合法的全序关系,则满足顺序一致性。

 

所谓的合法是指:1.全序化后读操作必须读到上一个写操作的值;2.单个进程内的操作先后顺序与在全序关系中的操作先后顺序一致。

 

图 2 


图 3

 

由于不存在绝对时间,我们不再要求画出读写操作的起止时刻,而将其视为瞬间完成的操作。在图 2 中,全序 1、2、3 中,只有 3 是一个合法的全序,因此图 2 中的读写满足顺序一致性。而在图 3 中,不存在一个合法的全序,因此,图 3 中的读写不满足顺序一致性。

 

由此可见,线性一致性可以被看作顺序一致性在存在绝对时间的系统模型下的特例。

 

  • 因果一致性:

 

由于进程 p2 是读取了进程 p1 写入的结果之后写入,进程 p2 的写入值和进程 p1 的写入值可能存在因果关系。那么,所有在同一个进程内的连续读操作都不可以先读到进程 p2 的写入值,再读到进程 p1 的写入值。


图 4 


图 5

 

图 4 中,进程 p1 和 p2,p2 先读到了 p1 写 x:=1 的结果,然后写 x:=3。写 x:=3 可能是由读 x=1 计算而来,因此存在因果关系。p3 满足了因果一致性,p4 则违背了因果一致性。

 

图 5 中,p2 不再读 x=1,因此不存在因果关系。该系统满足因果一致性,但不满足顺序一致性。

 

  • FIFO 一致性:

 

FIFO 一致性不会考虑多个进程之间的操作排序。对任意一个进程的写操作 1 与写操作 2,若写操作 1 先于写操作 2 完成,那么任何进程不可以先读到写操作 2 的值,再读到写操作 1 的值。

 

图 3 就是一个违背 FIFO 一致性的例子。图 4 与图 5 中,由于不存在同一个进程中的多个写操作,因此都满足 FIFO 一致性。

 

  • 最终一致性(Eventual consistency):

 

最终一致性,可以这样定义,若系统中不再发生写操作,经过一段时间后,所有的读操作都会得到同样的值。

 

图 6

 

以图 6 为例,最终一致的结果,既可能是 1,又可能是 2,还可能是 3(在不存在绝对时间的系统,甚至无法定义最后写入的值是 1、2 还是 3),但所有进程再足够长的时间后的读结果都必须保持一致。

 

多副本的一致性级别是由并发模型中的一致性级别直接应用到分布式系统中的结果,但有少许差别。细心的朋友可能注意到,我们并没有提及严格一致性,那是因为严格一致性要求数据被立即同步(比如在 CPU 的下一个时钟周期可见),这只能存在于单机系统中,并不存在于存在消息延迟的分布式系统中[4]。

 

我们通常所说的强一致,通常是指线性一致性或顺序一致性[5][6]。线性一致性与顺序一致性之间的区别,也可以被理解为系统模型的区别,即系统中是否存在绝对时间。弱于顺序一致性的一致性级别都可被称为弱一致,而最终一致性是弱一致性的一种形式。

 

探讨一致性级别时,一个常犯的错误是与事务的隔离级别混淆,事实上,虽然它们之间存在一定的联系,但并不是同一回事。当一个数据库在隔离级别上满足可串行化(Serializable),在一致性上满足线性一致性,则称为严格可串行化(Strict Serializable),这是一个统一了一致性级别与隔离级别的定义。

 

另一个常犯的错误是将一致性(consistency)与共识(consensus)混淆。共识问题是在分布式系统中有明确定义的一类问题,解决共识问题的经典算法是 paxos;强一致读写可以通过一系列的全序共识实现,Raft 便是这样一种算法。

 

以上定义的一致性级别可以被认为是以数据为中心(Data-centric)的一致性级别,另一种定义方式是以客户端为中心(Client-centric),还可以定义出写后读、读后写、单调写、单调读的一致性,此处不再赘述[4]。

一致性级别分析:以 Raft 为例

 

下面我们以 Raft 为例分析其一致性级别。在正常情况下,Raft 是在 leader 上完成读与写操作的,可以被看作单读单写的系统,若这个系统中将 leader 的时间视为绝对时间,则可认为提供了线性一致性。

 

但是,我们还需要考虑异常情况,当消息延迟时,Raft 可能出现短暂的双主;若出现网络分区,可能持续处于多主状态。

 

一种实现方式是,假如每个 leader 在提供读服务时都不做额外操作,那么,如果多数派分区的 leader 已经完成了新的写入,少数派分区的 leader 仍然提供读服务,就可能读到旧数据。

 

这个问题的一种解决方法是,让少数派分区的 leader 直接拒绝读服务。这如何实现呢?让 leader 在与客户端交互,完成读操作前发送一个 no-op 并至少得到半数回应,由于少数派分区的 leader 无法得到半数回应,因此无法提供读服务。

 

关于如何在 Raft 中获得线性一致性的详细探讨可详见 Raft 论文[7]中第 8 节 Client Interaction。

TDengine 提供的一致性级别

 

在上述的分析中可以看到,Raft 中实现线性一致性会为读操作和写操作都带来至少 2 个 RTT 的延迟(client 视角,从 client 到 leader,再由 leader 到 follower);即使仅实现顺序一致性,也会在写时带来至少 2 个 RTT 的延迟。

 

在 TDengine 中,为了降低写入数据的延迟、提高吞吐量,我们为元数据(表数据、表的标签数据)提供强一致性,为时序数据提供最终一致性与强一致性两种可选的一致性级别。当用户选择最终一致同步,写入的延迟可以被降低到 1 个 RTT(从 client 到 leader),这大大优于 Raft 这类强一致复制协议提供的性能。

 

随着 TDengine 集群版的开源,用户数量与日俱增,TDengine 被应用到了多种多样复杂的环境中。当集群中存在网络分区、或节点连续宕机等异常情况下,TDengine 中可能无法保证严格的强一致性,因此,在即将到来的 3.0 版本中,我们将以 Raft 算法为基础重构选主、强一致复制等一系列流程,同时,仍然为时序数据提供最终一致与强一致两种同步模式,给用户提供灵活的选择,帮助用户适应最复杂的业务场景需求。

 

参考文献:


[1]Lamport, Leslie (Sep 1979). "How to make a multiprocessor computer that correctly executes multiprocess programs". IEEE Transactions on ComputersC-28 (9): 690–691. doi:10.1109/TC.1979.1675439.


[2]RAYNAL, M. (2018). FAULT-TOLERANT MESSAGE-PASSING DISTRIBUTED SYSTEMS: an algorithmic approach.


[3]Leslie Lamport. 1978. Time, clocks, and the ordering of events in a distributed system. Commun. ACM 21, 7 (July 1978), 558–565. doi:10.1145/359545.359563


[4]Sukumar Ghosh. 2014. Distributed Systems: An Algorithmic Approach, Second Edition (2nd. ed.). Chapman & Hall/CRC.


[5]Kemme, B., Schiper, A., Ramalingam, G., & Shapiro, M. (2014). Dagstuhl seminar review: Consistency in distributed systems. ACM SIGACT News45(1), 67-89.


[6]Kleppmann, M. (2015). Designing data-intensive applications.


[7]Ongaro, D., & Ousterhout, J. (2013). In search of an understandable consensus algorithm (extended version).

2021-11-04 16:134760

评论 5 条评论

发布
用户头像
我感觉这样叙述的方式让问题变得更难理解了,比如顺序一致性,为什么p1的两次读操作,一次结果是0,一次结果是2,不应该是全序的顺序不一样导致不同的读写流结果,然后对比不同的读写流结果是否满足顺序一致性么?怎么反过来直接预设结果,调整顺序再来解释不符合顺序一致性...
2021-11-05 17:07
回复
在系统本身没有全局时间的情况下,全序不是唯一的。
不妨这么理解,各个客户端的时间是存在漂移的,甚至每两次操作之间的漂移程度都不一样,但是每次在客户端处每次发起读与写都会产生一个log,由于每个客户端的本地时间都不可信,因此我们可以无法排除各种可能的真实顺序。
因此,只要找到一个合法的全序关系,那么就满足顺序一致性。
2021-11-05 23:41
回复
ljc回复ljc
客户端这种描述不好。假设是一个多主甚至无主的系统,每个节点都可以独立完成读写而不与其他节点通信,那么,在时间漂移不定的情况下,全局时间是不存在的。
2021-11-05 23:48
回复
用户头像
线性一致性那张图是不是有误?p3的最后一步操作应该观测到R为3吧?按照瞬时执行的角度去观测,如果得到的结果是2,应该不满足线性一致性才对啊
2021-11-05 17:01
回复
请读一下原文“进程 p3 的写操作 R.write(3)开始和结束都分别晚于进程 p2 的写操作 R.write(2)的开始与结束,但由于它们时间有交叠,R.write(3)的线性化点(操作的等效时刻)可以先于 R.write(2)”。
所以故意找了这么一个容易混淆的case。你不妨这样理解,各个线程都是客户端,发送消息到服务端生效:p2发起调用的时间早于p3,但由于网络延迟,反而是p3的调用先于p2到达,因此在服务器端,p3的调用先生效,p2的效用后生效。而客户端p3的请求,虽然先于p2的请求完成了,但请求的回应延迟了,因此在客户端处反而p2先收到回应。因此,在客户端p2处的调用无论是发起还是收到回应都早于p3,但在服务器端却是p3的请求先于p2处理了,p2的写入才是最后的结果。
这同样是满足线性一致性的。
展开
2021-11-05 23:38
回复
没有更多了
发现更多内容

如何帮助技术员工高效成长?这几家企业的做法值得借鉴

极客时间企业版

研发管理 研发团队培训

寻找握剑的手,青睐懂行的人

脑极体

腾讯面了五轮,面委挂了,挂的原因让大家唏嘘...

程序员生活志

腾讯 面试

单例模式的几种写法你用的哪种?

Java小咖秀

Java 设计模式 23种设计模式

我想模糊删除redis key🤔

山中兰花草

Java lua redis 面试 批量任务

猿灯塔:spring Boot Starter开发及源码刨析(七)

猿灯塔

技术科普丨服务发现和负载均衡的来龙去脉

华为云开发者联盟

负载均衡 微服务 开发者工具 服务端 服务

Python的四种作用域及调用顺序

BigYoung

Python 局部作用域 全局作用域

第六周作业

Geek_a327d3

以中立性的立场看Severless的目标和流派

韩超

云原生 serverles

第六周总结

Geek_a327d3

知乎,挣钱?果然有长尾效应

非著名程序员

程序员 副业 副业赚钱 知乎 好物推荐

如何进行需求梳理及埋点方案设计

易观大数据

一文快速掌握华为云IPv6基础知识及使用指南

华为云开发者联盟

物联网中台 物联网 网络 华为云

linux上强大的字符串匹配工具详解-grep

X先生

Shell grep

数十家技术社区联名推荐的GeekOnline来了!

Geek_116789

朱嘉明:区块链成为经济转型、形成产业新业态的技术手段

CECBC

聊聊Dubbo(二):简单入门

猿灯塔

Week 06 命题作业

Jeremy

计算机网络基础(四)---网络层-ARP协议与RARP协议

书旅

laravel 计算机网络 网络协议 计算机基础 网络层

变性手术后,产品总监和当当网打起了官司

赵新龙

法律 判决书 案例

区块链加持的家用摄像头能拯救你的隐私吗?

CECBC

图解:如何实现最小生成树

淡蓝色

Java 数据结构 算法

解决方案|智能消防预警系统突破高层楼房限制

华为云开发者联盟

AI 物联网 边缘计算 华为云

《北京市政务服务领域区块链应用创新蓝皮书(第一版)》正式发布

CECBC

探索无限潜能,英特尔神经拟态计算除了有“嗅觉”还能有“触觉”

最新动态

信创舆情一线--十五部门印发指导意见进一步促进服务型制造发展

统小信uos

CAP原理

jason

可读代码编写炸鸡六 - 控制流尽量向前奔涌就好,不要分心

多选参数

代码 代码优化 代码规范 可读代码编写 可读代码

数据分析师完整的指标体系构建 (干货)

博文视点Broadview

数据挖掘 读书笔记 数据分析 数据 求职

有趣的条漫版 HashMap,25岁大爷都能看懂

古时的风筝

hashmap

分布式数据库如何平衡一致性和读写延迟?_开源_刘继聪_InfoQ精选文章