mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2026-05-11 04:04:34 +08:00
del
This commit is contained in:
185
极客时间专栏/geek/分布式数据库30讲/基础篇/01|什么是分布式数据库?.md
Normal file
185
极客时间专栏/geek/分布式数据库30讲/基础篇/01|什么是分布式数据库?.md
Normal file
@@ -0,0 +1,185 @@
|
||||
<audio id="audio" title="01|什么是分布式数据库?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d4/63/d40fbec874e34e5f3b3b85dd42ef6d63.mp3"></audio>
|
||||
|
||||
你好,我是王磊,你也可以叫我Ivan。
|
||||
|
||||
在这门课的第1讲,我想和你探讨一个最基本的问题:**什么是分布式数据库?**
|
||||
|
||||
回答这个问题,其实就是在给分布式数据库下定义。
|
||||
|
||||
分布式数据库和很多技术概念一样,没有权威机构来做这个定义,甚至对于哪些机构是权威机构,我们都很难有共识。
|
||||
|
||||
作为技术人员,我们常会提到一个概念,叫“事实标准”。当一个技术产品占据市场的主导位置时,它自然就成了同类产品的事实标准。例如,对于关系型数据库,可以说Oracle就是事实标准,因为所有数据库产品发布新版本时,都要拿自己的特性去和Oracle比一比。
|
||||
|
||||
很遗憾,分布式数据库作为一个新兴的基础软件,还没有一款产品占据“事实标准”的位置。既然没有参照,我们就自己动手,一起来定义分布式数据库这个概念吧。
|
||||
|
||||
由表及里、由外到内是人们认识事物的普遍规律,所以我们让也从内外部两个视角来观察。
|
||||
|
||||
## 外部视角:外部特性
|
||||
|
||||
外部视角,就是看看分布式数据库具备哪些特性,能解决什么问题。
|
||||
|
||||
通常,业务应用系统可以按照交易类型分为联机交易(OLTP)场景和联机分析(OLAP)场景两大类。OLTP是面向交易的处理过程,单笔交易的数据量小,但是要在很短的时间内给出结果,典型场景包括购物、缴费、转账等;而OLAP场景通常是基于大数据集的运算,典型场景包括生成个人年度账单和企业财务报表等。
|
||||
|
||||
OLTP与OLAP两种场景有很大的差异,所以很难在一款产品中完全满足两者的需求,因此在单体数据库时代就演化出了两个的不同技术体系,也就是两类不同的关系型数据库。向分布式架构演进后,两者在架构设计上也采用了完全不同的策略,很难在一个框架下说清楚。
|
||||
|
||||
所以,为了让你有更好的学习体验,**在这个课程中,我们先专注于讨论OLTP场景下的分布式数据库。**
|
||||
|
||||
说到这里,我想和你统一一下概念,如果没有特别说明,这个课程中出现的“数据库”都默认为“关系型数据库”,分布式数据库也都是指支持关系模型的分布式数据库。这就是说,NoSQL不是我们要讨论的核心内容。
|
||||
|
||||
你可能会说,NoSQL也很重要呀,MongoDB多火呀。
|
||||
|
||||
NoSQL当然很重要,MongoDB确实也在一些细分场景中取得了成功。但是,从整体看,关系型数据库由于支持SQL、提供ACID事务保障,显然具有更好的通用性,在更广泛的场景中无法被NoSQL取代。这一点通过NoSQL十余年的发展已经被证明。
|
||||
|
||||
事实上,分布式数据库的目标正是融合传统关系型数据库与NoSQL数据库的优势,而且已经取得了不错的效果。
|
||||
|
||||
### 定义1.0 OLTP关系型数据库
|
||||
|
||||
仅用“OLTP场景”作为定语显然不够精准,我们来进一步看看OLTP场景具体的技术特点。
|
||||
|
||||
OLTP场景的通常有三个特点:
|
||||
|
||||
- **写多读少**,而且读操作的复杂度较低,一般不涉及大数据集的汇总计算;
|
||||
- **低延时**,用户对于延时的容忍度较低,通常在500毫秒以内,稍微放大一些也就是秒级,超过5秒的延时通常是无法接受的;
|
||||
- **高并发**,并发量随着业务量而增长,没有理论上限。
|
||||
|
||||
我们是不是可以有这样一个结论:**分布式数据库是服务于写多读少、低延时、高并发的OLTP场景的数据库**。
|
||||
|
||||
### 定义2.0 +海量并发
|
||||
|
||||
你可能会说这个定义有问题,比如MySQL和Oracle这样的关系型数据库也是服务于OLTP场景的,但它们并不是分布式数据库。
|
||||
|
||||
你的感觉没错,确实有问题。
|
||||
|
||||
那么,相对于传统关系型数据库,分布式数据库最大的差异是什么呢?答案就是分布式数据库远高于前者的并发处理能力。
|
||||
|
||||
传统关系型数据库往往是单机模式,也就是主要负载运行在一台机器上。这样,数据库的并发处理能力与单机的资源配置是线性相关的,所以并发处理能力的上限也就受限于单机配置的上限。这种依靠提升单机资源配置来扩展性能的方式,被称为垂直扩展(Scale Up)。
|
||||
|
||||
在一台机器中,随随便便就能多塞进些CPU和内存来提升提性能吗?当然没那么容易。所以,物理机单机配置上限的提升是相对缓慢的。
|
||||
|
||||
这意味着,在一定时期内,依赖垂直扩展的数据库总会存在性能的天花板。很多银行采购小型机或大型机的原因之一,就是相比x86服务器,这些机器能够安装更多的CPU和内存,可以把天花板推高一些。
|
||||
|
||||
而分布式数据库就不同了,在维持关系型数据库特性不变的基础上,它可以通过水平扩展,也就是增加机器数量的方式,提供远高于单体数据库的并发量。这个并发量几乎不受单机性能限制,我将这个级别的并发量称为“海量并发”。
|
||||
|
||||
听到这里你可能还要追问,这个“海量并发”到底是多大呢,有没有一个数字?
|
||||
|
||||
很遗憾,据我所知并没有权威数字。虽然理论上是可以找一台世界上最好的机器来测试一下,但考虑到商业因素,这个数字不会有什么实际价值。不过,我可以给出一个经验值,这个“海量并发”的下限大致是10,000TPS。如果你有相关的经验,也欢迎你在评论区留言,我们一起讨论。
|
||||
|
||||
现在,基于这些理解,我们可以再得到一个2.0版本的定义:**分布式数据库是服务于写多读少、低延时、海量并发OLTP场景的关系型数据库**。
|
||||
|
||||
### 定义3.0 +高可靠
|
||||
|
||||
这个定义你觉得满意吗?
|
||||
|
||||
其实,这个2.0版本仍然有问题。
|
||||
|
||||
是不是没有海量并发需求,就不需要使用分布式数据库了呢?不是的,你还要考虑数据库的高可靠性。
|
||||
|
||||
一般来说,可靠性是与硬件设备的故障率有关的。
|
||||
|
||||
与银行不同,很多互联网公司和中小企业通常是采用x86服务器的。x86服务器有很多优势,但故障率会相对高一些,坊间流传的年故障率在5%左右。
|
||||
|
||||
一些更加可靠的数据来自Google的论文[**Failure Trends in a Large Disk Drive Population**](http://bnrg.eecs.berkeley.edu/~randy/Courses/CS294.F07/11.3.pdf),文中详细探讨了通用设备磁盘的故障情况。它给出的磁盘年度故障率的统计图,如下所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/5f/21/5fc9118207cb1f45f270bdd5f5090221.png" alt="">
|
||||
|
||||
可以看到,前三个月会超过2%的磁盘损坏率,到第二年这个数字会上升到8%左右。
|
||||
|
||||
你可能会说,这个数字也不是很高啊。
|
||||
|
||||
但你要知道,对金融行业的关键应用系统来说,通常是要求具备5个9的可靠性(99.999%),也就是说,一年中系统的服务中断时间不能超过5.26分钟(365`*`24`*`60`*`(1-99.999%) ≈ 5.26 )。
|
||||
|
||||
而且,不只是金融行业,随着人们对互联网的依赖,越来越多的系统都会有这样高的可靠性要求。
|
||||
|
||||
根据这两个数字,我们可以设想一下,如果你所在的公司有四、五个关键业务系统,十几台数据库服务器,磁盘数量一定会超过100个吧?那么我们保守估计,按照损坏率2%来算,一年中就会碰到2次磁盘损坏的情况,要达到5个9的可靠性你就只有5.26分钟,能处理完一次磁盘故障吗?这几乎是做不到的,可能你刚冲到机房,时间就用完了。
|
||||
|
||||
我猜你会建议用RAID(独立冗余磁盘阵列)来提高磁盘的可靠性。这确实是一个办法,但也会带来性能上的损耗和存储空间上的损失。分布式数据库的副本机制可以比RAID更好地平衡可靠性、性能和空间利用率三者的关系。副本机制就是将一份数据同时存储在多个机器上,形成多个物理副本。
|
||||
|
||||
回到数据库的话题上,可靠性还要更复杂一点,包括两个度量指标,恢复时间目标(Recovery Time Objective, RTO)和恢复点目标(Recovery Point Objective, RPO)。RTO是指故障恢复所花费的时间,可以等同于可靠性;RPO则是指恢复服务后丢失数据的数量。
|
||||
|
||||
数据库存储着重要数据,而金融行业的数据库更是关系到客户资产安全,不能容忍任何数据丢失。所以,数据库高可靠意味着RPO等于0,RTO小于5分钟。
|
||||
|
||||
传统上,银行通过两种方法配合来实现这个目标。
|
||||
|
||||
第一种还是采购小型机和大型机,因为它们的稳定性优于x86服务器。
|
||||
|
||||
第二种是引入专业存储方案,例如EMC的Symmetrix远程镜像软件(Symmetrix Remote Data Facility, SRDF)。数据库采用主备模式,在高端共享存储上保存数据库文件和日志,使数据库近似于无状态化。主库一旦出现问题,备库启动并加载共享存储的文件,继续提供服务。这样就可以做到RPO为零,RTO也比较小。
|
||||
|
||||
但是,这套方案依赖专用的软硬件,不仅价格昂贵,而且技术体系封闭。在去IOE(IBM小型机、Oracle数据库和EMC存储设备)的大背景下,我们必须另辟蹊径。分布式数据库则是一个很好的备选方案,它凭借节点之间的互为备份、自动切换的机制,降低了x86服务器的单点故障对系统整体的影响,提供了高可靠性保障。
|
||||
|
||||
令人兴奋的是,这种单点故障处理机制甚至可以延展到机房层面,通过远距离跨机房部署。如此一来,即使在单机房整体失效的情况下,系统仍然能够正常运行,数据库永不宕机。
|
||||
|
||||
至此,我们得出一个3.0版本的定义,**分布式数据库是服务于写多读少、低延时、海量并发OLTP场景的,高可靠的关系型数据库**。
|
||||
|
||||
### 定义4.0 +海量存储
|
||||
|
||||
还有没有4.0版本呢?
|
||||
|
||||
你猜的没错,我们还要补充一些存储能力的变化。
|
||||
|
||||
虽然单体数据库依靠外置存储设备可以扩展存储能力,但这种方式本质上不是数据库的能力。现在,借助分布式的横向扩展架构,通过物理机的本地磁盘就可以获得强大的存储能力,这让海量存储成为分布式数据库的标配。
|
||||
|
||||
最后,我们终于得到一个4.0终极版本的定义,**分布式数据库是服务于写多读少、低延时、海量并发OLTP场景的,具备海量数据存储能力和高可靠性的关系型数据库**。
|
||||
|
||||
## 内部视角:内部构成
|
||||
|
||||
只通过外部视角来看分布式数据库,已经足够了吗?其实,具有相同的外在特性和功效,未必就是同样的事物。
|
||||
|
||||
举个例子,哥白尼刚提出“日心说”来反驳“地心说”的时候,要用到34个圆周来解释天体的运动轨迹;而100多年后,开普勒只用7个椭圆就达到了同样的效果,彻底摧毁了“地心说”。从哥白尼到开普勒,效果近似,简洁程度却大不一样,这背后代表的是巨大的科学进步。
|
||||
|
||||
因此,讲完分布式数据库的外部特性之后,我们还要从内部视角来进行观察。
|
||||
|
||||
事实上,为了应对海量存储和海量并发,很多解决方案在效果上跟我们4.0版本的定义很相似。但是,它们向用户暴露了太多的内部复杂性。在我看来,对用户约束太多、使用过程太复杂、不够内聚的方案,不能称为成熟的产品。同时,业界的主流观点并不认为它们是分布式数据库,所以我们这门课也就不重点讨论了。
|
||||
|
||||
为了让你看清其中的差别,我将这些方案简单地分类介绍一下。
|
||||
|
||||
1. **客户端组件 + 单体数据库**
|
||||
|
||||
通过独立的逻辑层建立数据分片和路由规则,实现单体数据库的初步管理,使应用能够对接多个单体数据库,实现并发、存储能力的扩展。其作为应用系统的一部分,对业务侵入比较深。
|
||||
|
||||
这种客户端组件的典型产品是Sharding-JDBC。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/7a/da/7a86887e1f8f97f8a660c9434febc9da.jpg" alt="">
|
||||
|
||||
1. **代理中间件 + 单体数据库**
|
||||
|
||||
以独立中间件的方式,管理数据规则和路由规则,以独立进程存在,与业务应用层和单体数据库相隔离,减少了对应用的影响。随着代理中间件的发展,还会衍生出部分分布式事务处理能力。
|
||||
|
||||
这种中间件的典型产品是MyCat。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/88/ec/88728291d4c48a8a999bd56a04488cec.jpg" alt="">
|
||||
|
||||
1. **单元化架构 + 单体数据库**
|
||||
|
||||
单元化架构是对业务应用系统的彻底重构,应用系统被拆分成若干实例,配置独立的单体数据库,让每个实例管理一定范围的数据。例如对于银行贷款系统,可以为每个支行搭建独立的应用实例,管理支行各自的用户,当出现跨支行业务时,由应用层代码通过分布式事务组件保证事务的ACID特性。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4b/97/4b41ffef868c2277ae40580cd2044997.jpg" alt="">
|
||||
|
||||
根据不同的分布式事务模型,应用系统要配合改造,复杂性也相应增加。例如TCC模型下,应用必须能够提供幂等操作。
|
||||
|
||||
在分布式数据库出现前,一些头部互联网公司使用过这种架构风格,该方案的应用系统的改造量最大,实施难度也最高。
|
||||
|
||||
看过这三种方案,我相信你能够明白,它们共同的特点是单体数据库仍然能够被应用系统感知到。相反,分布式数据库则是**将技术细节收敛到产品内部,以一个整体面对业务应用。**
|
||||
|
||||
我猜,看到这里你一定很想知道,分布式数据库的内部架构到底长什么样呢?它跟这三种方案有什么区别呢?回答这个复杂的问题,就是我们这门课的使命了。这里你也可以先记下自己的答案,等学完这门课以后再回过头来做个对比,也是对自己学习效果的一种检验。
|
||||
|
||||
## 小结
|
||||
|
||||
好了,以上就是今天的主要内容了,我们来小结一下。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/8c/a4/8c0e6e7100efdfbf57412840a3fddda4.jpg" alt="">
|
||||
|
||||
我们通过逐层递进的方式,勾勒出分布式数据库的六个外部特性,分别是**写多读少、低延时、海量并发、海量存储、高可靠性、关系型数据库。**
|
||||
|
||||
同时,也存在一些与分布式数据库能力近似的解决方案,这些方案的不足之处是都需要对应用系统进行一定的改造,对应用的侵入程度更深;其优势则在于可以最大程度利用单体数据库的稳定可靠,毕竟这些特性已经历经无数次的考验。
|
||||
|
||||
最后,我想就分布式数据库的名称做一些延伸。
|
||||
|
||||
“分布式数据库”在字面上可以分解为“分布式”和“数据库”两部分,代表了它是跨学科的产物,它的理论基础来自两个领域。这同时也呼应了产品发展的两条不同路径,一些产品是从分布式存储系统出发,进而增加关系型数据库的能力;另外一些产品是从单体数据库出发,增加分布式技术元素。而随着分布式数据库的走向工业应用,在外部需求的驱动下,这两种发展思路又呈现出进一步融合的趋势。
|
||||
|
||||
## 思考题
|
||||
|
||||
在准备分布式数据库这门课的过程中,有的朋友建议我讲讲Aurora,但其实Aurora和这里说的分布式数据库还是有明显差别的,所以没有纳入正式课程。你了解Aurora或者它的同类产品吗?你觉得它和我所说的分布式数据库之间的差异是什么?那导致这种差异的原因又是什么呢?
|
||||
|
||||
欢迎你在评论区留言和我一起讨论,我会在答疑篇回复这个问题,如果感兴趣的同学很多,说不定会有加餐哦。
|
||||
|
||||
最后,如果你身边也有朋友对如何定义分布式数据库这个概念有困惑,欢迎你把今天这一讲分享给他,我们一起讨论。
|
||||
197
极客时间专栏/geek/分布式数据库30讲/基础篇/02|强一致性:那么多数据一致性模型,究竟有啥不一样?.md
Normal file
197
极客时间专栏/geek/分布式数据库30讲/基础篇/02|强一致性:那么多数据一致性模型,究竟有啥不一样?.md
Normal file
@@ -0,0 +1,197 @@
|
||||
<audio id="audio" title="02|强一致性:那么多数据一致性模型,究竟有啥不一样?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/25/21/2555365ececea98a3b996ec6238da521.mp3"></audio>
|
||||
|
||||
你好,我是王磊,你也可以叫我Ivan。
|
||||
|
||||
我们经常会听到说,分布式数据库的一个优势在于,它能够支持NoSQL做不到的强一致性。你怎么看待这件事儿呢?
|
||||
|
||||
显然,要来分析这个问题,我们首先得明白“强一致性”意味着什么。
|
||||
|
||||
我也问过很多身边的朋友,他们的答案都不太一样。有人说,只要使用了Paxos或者Raft算法,就可以实现强一致性;也有人说,根据CAP原理只能三选二,分区容忍性和高可用性又是必不可少的,所以分布式数据库是做不到强一致性的。可是,这些观点或多或少都是有问题的。
|
||||
|
||||
那么,今天我们就来讲讲什么是“强一致性”。
|
||||
|
||||
一直以来,在“分布式系统”和“数据库”这两个学科中,一致性(Consistency)都是重要概念,但它表达的内容却并不相同。
|
||||
|
||||
对于分布式系统而言,一致性是在探讨当系统内的一份逻辑数据存在多个物理的数据副本时,对其执行读写操作会产生什么样的结果,这也符合CAP理论对一致性的表述。
|
||||
|
||||
而在数据库领域,“一致性”与事务密切相关,又进一步细化到ACID四个方面。其中,I所代表的隔离性(Isolation),是“一致性”的核心内容,研究的就是如何协调事务之间的冲突。
|
||||
|
||||
因此,当我们谈论分布式数据库的一致性时,实质上是在谈论**数据一致性**和**事务一致性**两个方面。这一点,从Google Spanner对其外部一致性(External Consistency)的[论述](https://cloudplatform.googleblog.com/2018/01/why-you-should-pick-strong-consistency-whenever-possible.html)中也可以得到佐证。
|
||||
|
||||
## 数据一致性
|
||||
|
||||
今天,我会先介绍数据一致性,下一讲中,我再为你讲解事务一致性以及它们之间的关系。
|
||||
|
||||
包括分布式数据库在内的分布式存储系统,为了避免设备与网络的不可靠带来的影响,通常会存储多个数据副本。逻辑上的一份数据同时存储在多个物理副本上,自然带来了数据一致性问题。
|
||||
|
||||
讨论数据一致性还有一个前提,就是同时存在读操作和写操作,否则也是没有意义的。把两个因素加在一起,就是多副本数据上的一组读写策略,被称为“一致性模型”(Consistency Model)。一致性模型数量很多,让人难以分辨。为了便于你理解,我先建立一个简单的分析框架。
|
||||
|
||||
这里,我要借用论文“The many faces of consistency”中的两个概念,状态一致性(State Consistency)和操作一致性(Operation Consistency)。不要慌,这不是新的一致性模型,它们只是观察数据一致性的两个视角。
|
||||
|
||||
- 状态一致性是指,数据所处的客观、实际状态所体现的一致性;
|
||||
- 操作一致性是指,外部用户通过协议约定的操作,能够读取到的数据一致性。
|
||||
|
||||
## 状态视角
|
||||
|
||||
从状态的视角来看,任何变更操作后,数据只有两种状态,所有副本一致或者不一致。在某些条件下,不一致的状态是暂时,还会转换到一致的状态,而那些永远不一致的情况几乎不会去讨论,所以习惯上大家会把不一致称为“弱一致”。相对的,一致就叫做“强一致”了。
|
||||
|
||||
下面,我以MySQL为例来说明状态视角的“强一致”。
|
||||
|
||||
### 强一致性:MySQL全同步复制
|
||||
|
||||
现在有一个MySQL集群,由一主两备三个节点构成,那么在全同步复制(Fully Synchronous Replication)模式下,用户与MySQL交互的过程是这样的。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/eb/1d/eb572abd30b3f77cb001c339ba37851d.jpg" alt="">
|
||||
|
||||
在该模式下,主库与备库同步binlog时,主库只有在收到两个备库的成功响应后,才能够向客户端反馈提交成功。
|
||||
|
||||
显然,用户获得响应时,主库和备库的数据副本已经达成一致,所以后续的读操作肯定是没有问题的,但这种模式的副作用非常大,体现在以下两点。
|
||||
|
||||
第一,**性能差**。主库必须等到两个备库均返回成功后,才能向用户反馈提交成功。图中由于网络阻塞,“备库2”稍晚于“备库1”返回响应,增加了数据库整体的延时。而下一次,拖后腿的可能变成“备库1”。总之,主库的响应时间取决于两个备库中延时最长的那个。
|
||||
|
||||
第二,**可用性问题**。我们在第1讲提到过可用性概念,任何设备都有可能出现故障,尤其是x86这样的通用商业设备,故障率会更高。但在全同步复制模式下,集群中的三个节点被串联起来,如果单机可用性是95%,那么集群整体的可用性就是85.7%(95%*95%*95%=85.7%),跟单机相比反而降低了。
|
||||
|
||||
集群规模越大,这些问题就越严重,所以全同步复制模式在生产系统中也很少使用。更进一步说,在工程实践中,实现状态视角的强一致性需要付出的代价太大,尤其是与可用性有无法回避的冲突,所以很多产品选择了状态视角的弱一致性。
|
||||
|
||||
### 弱一致性:NoSQL最终一致性
|
||||
|
||||
NoSQL产品是应用弱一致性的典型代表,但对弱一致性的接受仍然是有限度的,这就是BASE理论中的E所代表的最终一致性(Eventually Consistency),弱于最终一致性的产品就几乎没有了。
|
||||
|
||||
对于最终一致性,你可以这样理解:在主副本执行写操作并反馈成功时,不要求其他副本与主副本保持一致,但在经过一段时间后这些副本最终会追上主副本的进度,重新达到数据状态的一致。
|
||||
|
||||
你再仔细推敲一下,是不是觉得这个定义还有点含糊?“经过一段时间”到底是多久呢?几秒还是几分钟?如果是一个不确定的数值,怎么在工程中使用呢?
|
||||
|
||||
这就需要我们从操作视角来分析了。
|
||||
|
||||
## 操作视角
|
||||
|
||||
最终一致性,在语义上包含了很大的不确定性,所以很多时候并不是直接使用,而是加入一些限定条件,也就衍生出了若干种一致性模型。因为它们是在副本不一致的情况下,进行操作层面的封装来对外表现数据的状态,所以都可以纳入操作视角。
|
||||
|
||||
接下来,我会挑选5个常见的一致性模型逐一讲解。
|
||||
|
||||
### 写后读一致性
|
||||
|
||||
首先来说**“写后读一致性”**(Read after Write Consistency),它也称为“读写一致性”,或“读自己写一致性”(Read My Writes Consistency)。你可能觉得最后一个名字听上去有些奇怪,但它却最准确地描述了这种一致性模型的使用效果。
|
||||
|
||||
我还是用一个例子来说明。
|
||||
|
||||
小明很喜欢在朋友圈分享自己的生活。这天是小明和女友小红的相识纪念日,小明特意在朋友圈分享了一张两人的情侣照。小明知道小红会很在意,特意又刷新了一下朋友圈,确认照片分享成功。
|
||||
|
||||
你是否意识到这个过程中系统已经实现了“写后读一致性”?我画了张流程图来表示这个过程。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/7d/dc/7df116a9b10b1d9ce0b5a8400c00eedc.jpg" alt="">
|
||||
|
||||
小明发布照片的延时极短,用户体验很好。这是因为数据仅被保存在主副本R1上,就立即反馈保存成功。而其他副本在后台异步更新,由于网络的关系每个副本更新速度不同,在T2时刻上海的两个副本达成一致。从过程来看,这与前面所说的“最终一致性”完全相符。
|
||||
|
||||
要特别注意的是,小明有一个再次刷新朋友圈的动作,这时如果访问副本R2,由于其尚未完成同步,情侣照将会消失,小明就会觉得自己的照片被弄丢了。此处,我们假定系统可以通过某种策略由写入节点的主副本R1负责后续的读取操作,这样就实现了写后读一致性,可以保证小明再次读取到照片。
|
||||
|
||||
自己写入成功的任何数据,下一刻一定能读取到,其内容保证与自己最后一次写入完全一致,这就是“读自己写一致性”名字的由来。当然,从旁观者角度看,可以称为“读你写一致性”(Read Your Writes Consistency),有些论文确实采用了这个名称。
|
||||
|
||||
### 单调读一致性
|
||||
|
||||
但是,小明发完朋友圈之后,小红一定能看到照片吗?会不会发生异常呢?
|
||||
|
||||
这次确实出问题了。
|
||||
|
||||
此时,小红也在刷朋友圈,看到了小明刚刚分享的照片,非常开心。然后,小红收到一条信息,简单回复了一下,又回到朋友圈再次刷新,发现照片竟然不见了!小红很生气,打电话质问小明,为什么这么快就把照片删掉?小明听了一脸蒙,心想我没有删除呀。
|
||||
|
||||
你猜这中间发生了什么呢?我用另一张流程图来演示这种异常。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b1/a8/b138fa2ebe6c6cc60bd7dcbf4fa06da8.jpg" alt="">
|
||||
|
||||
在小明发布照片后的瞬间,小红也刷新了朋友圈,此时读取到副本R1,所以小红看到了照片;片刻之后,小红再次刷新,此时读取到的副本是R2,于是照片消失了。小红以为小明删除了照片,但实际上这完全是程序错误造成的,数据向后回滚,出现了“时光倒流”。
|
||||
|
||||
想要排除这种异常,系统必须实现**单调读一致性**(Monotonic Read Consistency)。关于单调读一致性的定义,常见的解释是这样的:一个用户一旦读到某个值,不会读到比这个值更旧的值。
|
||||
|
||||
是不是感觉有点蒙?让我来解释一下。
|
||||
|
||||
假如,变量X被赋值三次,依次是10、20、30;之后读取变量X,如果第一次读到了20,那下一次只有读到20或30才是合理的。因为在第一次读到20的一刻,意味着10已经是过期数据,没有意义了。
|
||||
|
||||
实现单调读一致性的方式,可以是将用户与副本建立固定的映射关系,比如使用哈希算法将用户ID映射到固定副本上,这样避免了在多个副本中切换,也就不会出现上面的异常了。
|
||||
|
||||
### 前缀一致性
|
||||
|
||||
但是,在一些更复杂的场景下还是会出现时间的扭曲。我再用一个例子来说明。
|
||||
|
||||
这天小明去看CBA总决赛,刚开球小明就拍了一张现场照片发到朋友圈,想要炫耀一下。小红也很喜欢篮球,但临时有事没有去现场,就在评论区问小明:“现在比分是多少?”小明回复:“4:2。”
|
||||
|
||||
小明的同学,远在加拿大的小刚,却看到了一个奇怪的现象,评论区先出现了小明的回复“4:2。”,而后才刷到小红的评论“现在比分是多少?”。难道小明能够预知未来吗?
|
||||
|
||||
这是什么原因呢?我们还是看图说话。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/26/23/2635951c5260b0b97de5fed08f368a23.jpg" alt="">
|
||||
|
||||
小明和小红的评论分别写入了节点N1和N2,但是它们与N3同步数据时,由于网络传输的问题,N3节点接收数据的顺序与数据写入的顺序并不一致,所以小刚是先看到答案后看到问题。
|
||||
|
||||
显然,问题与答案之间是有因果关系的,但这种关系在复制的过程中被忽略了,于是出现了异常。
|
||||
|
||||
保持这种因果关系的一致性,被称为**前缀读**或**前缀一致性**(Consistent Prefix)。要实现这种一致性,可以考虑在原有的评论数据上增加一种显式的因果关系,这样系统可以据此控制在其他进程的读取顺序。
|
||||
|
||||
### 线性一致性
|
||||
|
||||
在“前缀一致性”的案例中,问题与答案之间存在一种显式声明,但在现实中,多数场景的因果关系更加复杂,也不可能要求全部做显式声明。
|
||||
|
||||
比如对于分布式数据库来说,它无法要求应用系统在每次变更操作时附带声明一下,这次变更是因为读取了哪些数据而导致的。
|
||||
|
||||
那么,在显式声明无法奏效的情况下,如何寻找因果关系呢?
|
||||
|
||||
不知道你有没有听过这句话,“你所经历的一切,造就了现在的你。”是不是有一点哲学的味道?一切对原因的推测都是主观的,之前发生的一切都可能是原因。
|
||||
|
||||
所以,更可靠的方式是将自然语意的因果关系转变为事件发生的先后顺序。
|
||||
|
||||
线性一致性(Linearizability)就是建立在事件的先后顺序之上的。在线性一致性下,整个系统表现得好像只有一个副本,所有操作被记录在一条时间线上,并且被原子化,这样任意两个事件都可以比较先后顺序。
|
||||
|
||||
这些事件一起构成的集合,在数学上称为具有“全序关系”的集合,而“全序”也称为“线性序”。我想,线性一致性大概就是因此得名。
|
||||
|
||||
但是,集群中的各个节点不能做到真正的时钟同步,这样节点有各自的时间线。那么,如何将操作记录在一条时间线上呢?这就需要一个绝对时间,也就是**全局时钟**。
|
||||
|
||||
从产品层面看,主流分布式数据库大多以实现线性一致性为目标,在设计之初或演进过程中纷纷引入了全局时钟,比如Spanner、TiDB、OceanBase、GoldenDB和巨杉等等。
|
||||
|
||||
工程实现上,多数产品采用单点授时(TSO),也就是从一台时间服务器获取时间,同时配有高可靠设计; 而Spanner以全球化部署为目标,因为TSO有部署范围上的限制,所以Spanner的实现方式是通过GPS和原子钟实现的全局时钟,也就是TrueTime,它可以保证在全球范围内任意节点能同时获得的一个绝对时间,误差在7毫秒以内。
|
||||
|
||||
但是,对于线性一致性,学术界其实是有争议的。反对者的论据来自爱因斯坦的相对论的一个重要结论,“时间是相对的”。没有绝对时间,也就不存在全序的事件顺序,不同的观察者可能对于哪个事件先发生是无法达成一致的。因此,线性一致性是有局限性的。
|
||||
|
||||
当然,从工程角度看,因为我们的应用场景都在经典物理学适用范围内,所以线性一致性也是适用的。
|
||||
|
||||
### 因果一致性
|
||||
|
||||
既然线性一致性不够完美,那么有没有不依赖绝对时间的方法呢?
|
||||
|
||||
当然是有的,这就是**因果一致性**(Causal Consistency)。
|
||||
|
||||
因果一致性的基础是**偏序关系**,也就是说,部分事件顺序是可以比较的。至少一个节点内部的事件是可以排序的,依靠节点的本地时钟就行了;节点间如果发生通讯,则参与通讯的两个事件也是可以排序的,接收方的事件一定晚于调用方的事件。
|
||||
|
||||
基于这种偏序关系,Leslie Lamport在论文“Time, Clocks, and the Ordering of Events in a Distributed System”中提出了**逻辑时钟**的概念。
|
||||
|
||||
借助逻辑时钟仍然可以建立全序关系,当然这个全序关系是不够精确的。因为如果两个事件并不相关,那么逻辑时钟给出的大小关系是没有意义的。
|
||||
|
||||
多数观点认为,因果一致性弱于线性一致性,但在并发性能上具有优势,也足以处理多数的异常现象,所以因果一致性也在工业界得到了应用。
|
||||
|
||||
具体到分布式数据库领域,CockroachDB和YugabyteDB都在设计中采用了**逻辑混合时钟**(Hybrid Logical Clocks),这个方案源自Lamport的逻辑时钟,也取得了不错的效果。因此,这两个产品都没有实现线性一致性,而是接近于因果一致性,其中CockroachDB将自己的一致性模型称为“No Stale Reads”。
|
||||
|
||||
时间对于任何一种分布式系统来说都是非常重要的,在分布式数据库中还会牵扯到数据一致性以外的很多话题,所以有关时间、全局时钟和逻辑时钟的内容,我还会在后续课程中提到并作详细讨论。
|
||||
|
||||
## 小结
|
||||
|
||||
好了,今天的内容就到这里。我们一起学习了数据一致性,希望你能够记住以下几点:
|
||||
|
||||
1. 一致性模型林林总总,数量繁多,但我们总可以从状态和操作这两个视角来观察,进而梳理出其读写操作的不同策略。
|
||||
1. 从状态视角看,数据一致性只有两种状态,强一致或弱一致,而在实际系统中强一致是非常少见的,最终一致性是弱一致性的特殊形式;
|
||||
1. 从操作视角看,最终一致性可以被封装成多种一致性模型,甚至是最强的线性一致性。
|
||||
1. 分布式数据库主要应用了线性一致性或因果一致性。线性一致性必须要有全局时钟,全局时钟可能来自授时服务器或者特殊物理设备(如原子钟),全局时钟的实现方式会影响到集群的部署范围;因果一致性可以通过逻辑时钟实现,不依赖于硬件,不会限制集群的部署范围。
|
||||
|
||||
今天介绍的几种一致性模型,用一致性强度来衡量的话:线性一致性强于因果一致性;而写后读一致性、单调读一致性、前缀一致性弱于前两者,但这三者之间无法比较强弱。还有一种常被提及的顺序一致性(Sequentially Consistent),其强度介于线性一致性与因果一致性之间,由于较少在分布式数据库中使用,所以并没有介绍。
|
||||
|
||||
综上所述,我们提到的一致性模型强度排序如下:
|
||||
|
||||
线性一致性 > 顺序一致性 > 因果一致性 > { 写后读一致性,单调一致性,前缀一致性 }
|
||||
|
||||
此外,还有一些常见的弱一致性模型今天并没有提到,包括有限旧一致性(Bounded Staleness)、会话一致性(Session Consistency)、单调写一致性(Monotonic Write Consistency)和读后写一致性(Write Follows Read Consistency)等。如果你感兴趣,可以在Azure Cosmos DB的[官方文档](https://docs.microsoft.com/en-us/azure/cosmos-db/consistency-levels)找到非常详细的说明。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/27/af/27155b05c028b261yyc2d3c3469a3faf.jpg" alt="">
|
||||
|
||||
## 思考题
|
||||
|
||||
课程的最后,我要给你留一道思考题。我们今天集中讨论了数据一致性,但是并没有特别强调Paxos的作用。这等于是说,Paxos不是实现强一致性的必要条件。可是,有些时候大家又会将Paxos称为一致性协议。你觉得这个“一致性协议”和数据一致性又是什么关系呢?
|
||||
|
||||
欢迎你在评论区留言和我一起讨论,我会在答疑篇回复这个问题。最后,谢谢你的收听,如果你身边的朋友也对强一致性或者数据一致性这个话题感兴趣,欢迎你把今天这一讲分享给他,我们一起讨论。
|
||||
187
极客时间专栏/geek/分布式数据库30讲/基础篇/03|强一致性:别再用BASE做借口,来看看什么是真正的事务一致性.md
Normal file
187
极客时间专栏/geek/分布式数据库30讲/基础篇/03|强一致性:别再用BASE做借口,来看看什么是真正的事务一致性.md
Normal file
@@ -0,0 +1,187 @@
|
||||
<audio id="audio" title="03|强一致性:别再用BASE做借口,来看看什么是真正的事务一致性" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/12/03/12ae6a6efd3c888a57c2d99a880e5e03.mp3"></audio>
|
||||
|
||||
你好,我是王磊,你也可以叫我Ivan。
|
||||
|
||||
在上一讲的开头,我提了一个问题:对分布式数据库来说,“强一致性”意味着什么?我们经过分析后得出的结论是这个强一致性,包括数据一致性和事务一致性两个方面。然后,我们介绍了数据一致性是怎么回事儿。那么,今天我们会继续这个话题,谈谈事务一致性。
|
||||
|
||||
每次,我和熟悉NoSQL同学聊到事务这个话题时,都会提到ACID和BASE。甚至,不少同学会觉得ACID有些落伍了,以BASE为理论基础的NoSQL,才是当下的潮流。
|
||||
|
||||
那我们来看看BASE是什么?其实,它代表了三个特性,BA表示基本可用性(Basically Available),S表示软状态(Soft State),E表示最终一致性(Eventual Consistency):
|
||||
|
||||
- 基本可用性,是指某些部分出现故障,那么系统的其余部分依然可用。
|
||||
- 软状态或柔性事务,是指数据处理过程中,存在数据状态暂时不一致的情况,但最终会实现事务的一致性。
|
||||
- 最终一致性,是指单数据项的多副本,经过一段时间,最终达成一致。这个,我们在第2讲已经详细说过了。
|
||||
|
||||
总体来说,BASE是一个很宽泛的定义,所做的承诺非常有限。我认为,BASE的意义只在于放弃了ACID的一些特性,从而更简单地实现了高性能和可用性,达到一个新的平衡。但是,架构设计上的平衡往往都是阶段性的,随着新技术的突破,原来的平衡点也自然会改变。你看,不用说分布式数据库,就连不少NoSQL也开始增加对事务的支持了。
|
||||
|
||||
所以说,风水轮流转,今天ACID已经是新的后浪了。
|
||||
|
||||
## 事务的ACID特性
|
||||
|
||||
在数据库中,“事务”是由多个操作构成的序列。1970年詹姆斯 · 格雷(Jim Gray)提出了事务的ACID四大特性,将广义上的事务一致性具化到了原子性、一致性、隔离性和持久性这4个方面。我们先来看一下他在 **Transaction Processing Concepts and Techniques** 中给出的定义:
|
||||
|
||||
>
|
||||
**Atomicity**: **Either all the changes from the transaction occur (writes, and messages sent), or none occur.**
|
||||
|
||||
|
||||
>
|
||||
**Consistency**: **The transaction preserves the integrity of stored information.**
|
||||
|
||||
|
||||
>
|
||||
**Isolation**: **Concurrently executing transactions see the stored information as if they were running serially (one after another).**
|
||||
|
||||
|
||||
>
|
||||
**Durability**: **Once a transaction commits, the changes it made (writes and messages sent) survive any system failures.**
|
||||
|
||||
|
||||
翻译过来的意思就是:
|
||||
|
||||
>
|
||||
原子性:事务中的所有变更要么全部发生,要么一个也不发生。
|
||||
|
||||
|
||||
>
|
||||
一致性:事务要保持数据的完整性。
|
||||
|
||||
|
||||
>
|
||||
隔离性:多事务并行执行所得到的结果,与串行执行(一个接一个)完全相同。
|
||||
|
||||
|
||||
>
|
||||
持久性:一旦事务提交,它对数据的改变将被永久保留,不应受到任何系统故障的影响。
|
||||
|
||||
|
||||
虽然ACID名义上并列为事务的四大特性,但它们对于数据库的重要程度并不相同。我用一张图来表示它们的关系。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e7/73/e7571fa45b9337f2541a35d8c82b3873.jpg" alt="">
|
||||
|
||||
我们依次来看下。
|
||||
|
||||
第一个是一致性,它无疑是其中存在感最低的特性,可以看作是对 “事务”整体目标的阐述。它并没有提出任何具体的功能需求,所以在数据库中也很难找到针对性的设计。
|
||||
|
||||
第二个是持久性,它不仅是对数据库的基本要求。如果你仔细琢磨下持久性的定义,就会发现它的核心思想就是要应对系统故障。怎么理解系统故障呢?我们可以把故障分为两种。
|
||||
|
||||
<li>
|
||||
存储硬件无损、可恢复的故障。这种情况下,主要依托于预写日志(Write Ahead Log, WAL)保证第一时间存储数据。WAL采用顺序写入的方式,可以保证数据库的低延时响应。WAL是单体数据库的成熟技术,NoSQL和分布式数据库都借鉴了过去。
|
||||
</li>
|
||||
<li>
|
||||
存储硬件损坏、不可恢复的故障。这种情况下,需要用到日志复制技术,将本地日志及时同步到其他节点。实现方式大体有三种:第一种是单体数据库自带的同步或半同步的方式,其中半同步方式具有一定的容错能力,实践中被更多采用;第二种是将日志存储到共享存储系统上,后者会通过冗余存储保证日志的安全性,亚马逊的Aurora采用了这种方式,也被称为Share Storage;第三种是基于Paxos/Raft的共识算法同步日志数据,在分布式数据库中被广泛使用。无论采用哪种方式,目的都是保证在本地节点之外,至少有一份完整的日志可用于数据恢复。
|
||||
</li>
|
||||
|
||||
第三个是原子性,是数据库区别于其他存储系统的重要标志。在单体数据库时代,原子性问题已经得到妥善解决,但随着向分布式架构的转型,在引入不可靠的网络因素后,原子性又成为一个新的挑战。
|
||||
|
||||
要在分布式架构下支持原子性并不容易,所以不少NoSQL产品都选择绕过这个问题,聚焦到那些对原子性不敏感的细分场景。例如,大名鼎鼎的Google BigTable甚至是不支持跨行事务的。但是,这种妥协也造成了NoSQL的通用性不好。
|
||||
|
||||
我们在[开篇词](https://time.geekbang.org/column/article/271369)就说过,这门课程讨论的分布式数据库是在分布式架构上实现的关系型数据库,那么就必须支持事务,首先就要支持原子性。原子性,在实现机制上较为复杂,目标却很简单,和分成多个级别的隔离性不同,原子性就只有支持和不支持的区别。有关原子性的实现机制,我将在第9讲中专门介绍。
|
||||
|
||||
最后一个是隔离性,它是事务中最复杂的特性。隔离性分为多个隔离级别,较低的隔离级别就是在正确性上做妥协,将一些异常现象交给应用系统的开发人员去解决,从而获得更好的性能。
|
||||
|
||||
可以说,事务模型的发展过程就是在隔离性和性能之间不断地寻找更优的平衡点。我觉得,甚至可以说事务的核心就是隔离性。而不同产品在事务一致性上的差别,也完全体现在隔离性的实现等级上,所以我们必须搞清楚隔离等级具体是指什么。
|
||||
|
||||
## ANSI SQL-92:对隔离级别最早、最正式的定义
|
||||
|
||||
最早、最正式的对隔离级别的定义,是ANSI SQL-92(简称SQL-92),它定义的隔离级别和异常现象如下所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ed/44/ed89859eb0f1108600e0d5f0db343544.jpg" alt="">
|
||||
|
||||
SQL-92定义了四个隔离级别和三种异常现象,这些内容网上很多文章都说得比较清楚,我就不再啰嗦了。如果还不放心,我推荐你去看林晓斌老师的课程《MySQL实战45讲》。
|
||||
|
||||
不过,虽然SQL-92得到了广泛应用,不少数据库也都遵照这个标准来命名自己的隔离级别,但它对异常现象的分析还是过于简单了。所以在不久之后的1995年,Jim Gray等人发表了论文“[A Critique of ANSI SQL Isolation Levels](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/tr-95-51.pdf)”(以下简称Critique),对于事务隔离性进行了更加深入的分析。我要特别提示一下,Critique是数据库领域的经典论文,强烈推荐你阅读原文。
|
||||
|
||||
## Critique:更严谨的隔离级别
|
||||
|
||||
### 幻读和写倾斜
|
||||
|
||||
Critique丰富和细化了SQL-92的内容,定义了六种隔离级别和八种异常现象。其中,我们最关注的是快照隔离(Snapshot Isolation, SI)级别。为什么呢?这是因为在SQL-92中可重复读(Repeatable Read, RR)与可串行化(Serializable)两个隔离级别的主要差别是对幻读(Phantom)的处理。这似乎是说,解决幻读问题的就是可串行化。但随着Critique的发表,快照隔离被明确提出,这个说法就不适用了,因为快照隔离能解决幻读的问题,但却无法处理写倾斜(Write Skew)问题,也不符合可串行化要求。因为翻译的原因,有时写倾斜也被称为写偏序,都是一个意思。
|
||||
|
||||
因此,今天,使用最广泛的隔离级别有四个,就是已提交读、可重复读、快照隔离、可串行化。
|
||||
|
||||
而幻读和写倾斜无疑则是通往最高隔离级别的两座大山,那么让我来给你详细解释一下它们到底是什么异常现象。
|
||||
|
||||
Critique对幻读的描述大致是这样的,事务T1使用特定的查询条件获得一个结果集,事务T2插入新的数据,并且这些数据符合T1刚刚执行的查询条件。T2 提交成功后,T1再次执行同样的查询,此时得到的结果集会增大。这种异常现象就是幻读。
|
||||
|
||||
不少人会将幻读与不可重复读混淆,这是因为它们在自然语义上非常接近,都是在一个事务内用相同的条件查询两次,但两次的结果不一样。差异在于,对不可重复读来说,第二次的结果集相对第一次,有些记录被修改(Update)或删除(Delete)了;而幻读是第二次结果集里出现了第一次结果集没有的记录(Insert)。一个更加形象的说法,幻读是在第一次结果集的记录“间隙”中增加了新的记录。所以,MySQL将防止出现幻读的锁命名为间隙锁(Gap Lock)。
|
||||
|
||||
跟幻读相比,写倾斜要稍微复杂一点,我用一个黑白球的例子来说明。
|
||||
|
||||
首先,箱子里有三个白球和三个黑球,两个事务(T1,T2)并发修改,不知道对方的存在。T1要让6个球都变成白色;T2则希望6个球都变成黑色。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/91/fa/91e75e61d921fb21cebfdba8879806fa.jpg" alt="">
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/dd/be/ddce93423da417ef495b2bbc7c3090be.jpg" alt="">
|
||||
|
||||
你看,最终的执行结果是,盒子里仍然有三个黑球和三个白球。如果你还没有发现问题,可以看看下面我画的串行执行的效果图,比较一下有什么不同。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/85/83/8502cf4cf0f6fe61db1692bd1a945883.jpg" alt="">
|
||||
|
||||
如果先执行T1再执行T2,6个球都会变成黑色;调换T1与T2的顺序,则6个球都是白色。
|
||||
|
||||
根据可串行化的定义,“多事务并行执行所得到的结果,与串行执行(一个接一个)完全相同”。比照两张图,很容易发现事务并行执行没有达到串行的同等效果,所以这是一种异常现象。也可以说,写倾斜是一种更不易察觉的更新丢失。
|
||||
|
||||
好了,为了让你搞清Critique中六种隔离级别的强弱关系以及相互间的差距,我截取了原论文的一张配图。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/0d/aa/0d81415e08f4507d5f3f3ff6f99a99aa.jpg" alt="">
|
||||
|
||||
你可以看到“快照隔离”与“可重复读”在强度上并列,“已提交读”则弱于这两者。事实上,今天大多数数据库支持的隔离级别就在这三者之中。
|
||||
|
||||
### 快照隔离 & MVCC
|
||||
|
||||
你可能会问,既然“快照隔离”这么重要,为什么会被SQL-92漏掉呢?
|
||||
|
||||
这是由于SQL-92主要考虑了基于锁(Lock-base)的并发控制,而快照隔离的实现基础则是多版本并发控制(MVCC),很可能是由于当时MVCC的应用还不普遍。当然,后来,MVCC成为一项非常重要的技术,一些经典教材会将MVCC作为一种独立的选择,与乐观并发控制和悲观并发控制并列。其实,在现代数据库中MVCC已经成为一种底层技术,用于更高效地实现乐观或悲观并发控制。有了MVCC这个基础,快照隔离就成为一个普遍存在的隔离级别了。有关MVCC的话题,我会在第11讲中继续展开。
|
||||
|
||||
## 隔离性的产品实现
|
||||
|
||||
还有一个问题也许你一直想问,为什么不支持最高级别的可串行化呢?
|
||||
|
||||
答案可能会让你有点沮丧,那就是在很长一段时间内,学术界都没有找到足够高效的并发控制技术。可能你熟悉的很多数据库声称提供了“可串行化”级别,但这往往只是一种形象工程,因为它们都采用的是两阶段封锁协议,导致性能无法满足生产环境的要求。不过,有些消息让人振奋,虽然不是普适的方案,但少数产品的尝试已经取得进展。
|
||||
|
||||
这种尝试来自两个方向。
|
||||
|
||||
第一个方向是,用真正的串行化实现“可串行化”隔离。我们往往认为多线程并发在性能上更优,但Redis和VoltDB确实通过串行化执行事务的方式获得了不错的性能。考虑到VoltDB作为一款分布式数据库的复杂度,其成功就更为难得了。我想,其中部分原因可能在于内存的大量使用,加速了数据计算的过程。另外,VoltDB以存储过程为逻辑载体的方式,也使得事务有了更多的优化机会。
|
||||
|
||||
如果说第一个方向有点剑走偏锋,那第二个方向就是硬桥硬马了。没错,还是在并发技术上继续做文章。PostgreSQL在2008年提出了Serializable Snapshot Isolation (SSI),这实际就是可串行化。而后,兼容PostgreSQL生态的CockroachDB,也同样选择支持SSI,而且是唯一支持的隔离级别。
|
||||
|
||||
这两个方向的尝试都很有趣,我还会在后续的课程中与你深入探讨。
|
||||
|
||||
## 分布式数据库的强一致性
|
||||
|
||||
到这里,我们用两讲的篇幅分别介绍了数据一致性和事务一致性,它们共同构成了分布式数据库的强一致性这个概念。我借用一张图来体现三者的关系。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c2/c2/c291e740e57dbedc2e20f18fd62b1ec2.jpg" alt="">
|
||||
|
||||
图片原始出处是论文“Highly Available Transactions: Virtues and Limitations”,此处引用的是[Jepsen网站的简化版](https://jepsen.io/consistency)。
|
||||
|
||||
这幅图展现了一个树状结构,左右两个分支上体现事务一致性和数据一致性的各个级别及强弱关系,根节点则体现了分布式数据库的一致性来自两者的融合。图中使用了不同颜色,简单来说,这是区别不同的一致性级别所需付出的性能代价。
|
||||
|
||||
对分布式数据而言,最高级别的一致性是严格串行化(Strict Serializable),Spanner实现的“外部数据一致性”可以被视为与 “Strict Serializable” 等效。但由于两条路径上各自实现难度及性能上的损耗,少有分布式数据库在顶端汇合。即使强大的Spanner也提供了有界旧一致性(Bounded Stale),用于平衡性能和一致性之间的冲突。
|
||||
|
||||
下面,我总结了一些分布式数据库产品的“一致性”实现情况供你参考。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e5/d9/e58acbd91d1f25fa4086eb8yyc9decd9.jpg" alt="">
|
||||
|
||||
比较特别的是,OceanBase在2.2版本还增加了对“可串行化”的支持,但这是一个被Oracle重新定义的“可串行化”,在这个级别OceanBase和Oracle一样都会出现写倾斜。所以,这不是我们标准的隔离级别,也就没有体现在表格中。
|
||||
|
||||
## 小结
|
||||
|
||||
好了,有关事务一致性就讨论到这里,最后让我们来回顾一下今天的重点内容。
|
||||
|
||||
1. 数据一致性关注的是单对象、单操作在多副本上的一致性,事务一致性则是关注多对象、多操作在单副本上的一致性,分布式数据库的一致性是数据一致性与事务一致性的融合。
|
||||
1. 广义上的事务一致性被细化为ACID四个方面,其中原子性的实现依赖于隔离性的并发控制技术和持久性的日志技术。
|
||||
1. 隔离性是事务的核心。降低隔离级别,其实就是在正确性上做妥协,将一些异常现象交给应用系统的开发人员去解决,从而获得更好的性能。所以,除“可串行化”以外的隔离级别,都有无法处理的异常现象。
|
||||
1. 研究人员将隔离级别分为六级,你需要重点关注其中四个,分别是已提交读、可重复读、快照隔离、可串行化。前三者是单体数据库或分布式数据库中普遍提供的,可串行化仅在少数产品中提供。
|
||||
|
||||
好了,到这里,加上前一节“数据一致性”,我们用了两讲阐述了分布式数据“强一致性”的含义。在严格意义上,分布式数据库的“强一致性”意味着严格串行化(Strict Serializable),目前我们熟知的产品中只有Spanner达到了这个标准,其同时也带来了性能上的巨大开销。如果我们稍稍放松标准,那么“数据一致性”达到因果一致性且“事务一致性”达到已提交读,即可认为是相对的“强一致性”。还有一点非常重要,分布式数据一致性并不是越高越好,还要与可用性、性能指标结合,否则就成了形象工程。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c5/ed/c57e399d116cd88e1062184fb97d3aed.jpg" alt="">
|
||||
|
||||
## 思考题
|
||||
|
||||
课程的最后,我要留给你一道思考题。
|
||||
|
||||
我们在事务持久性部分提到了预写日志(WAL),它可以保证在系统发生故障时,数据也不会丢失。但是,如果写日志成功,而写数据表失败,又要如何处理呢?你可以根据自己的经验,讲讲该如何设计这个过程吗?
|
||||
|
||||
欢迎你在评论区留言和我一起讨论,我会在答疑篇回复这个问题。如果你身边的朋友也对事务一致性这个话题感兴趣,你也可以把今天这一讲分享给他,我们一起讨论。
|
||||
112
极客时间专栏/geek/分布式数据库30讲/基础篇/04 | 架构风格:NewSQL和PGXC到底有啥不一样?.md
Normal file
112
极客时间专栏/geek/分布式数据库30讲/基础篇/04 | 架构风格:NewSQL和PGXC到底有啥不一样?.md
Normal file
@@ -0,0 +1,112 @@
|
||||
<audio id="audio" title="04 | 架构风格:NewSQL和PGXC到底有啥不一样?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f3/0e/f3e3aa1eb9655c5f2e23df361418130e.mp3"></audio>
|
||||
|
||||
你好,我是王磊,你也可以叫我Ivan。
|
||||
|
||||
分布式数据库已经是技术新潮流了,所以产品也越来越多,如果你要做技术选型或者想要学习,该如何下手呢?怎么能更高效地了解不同产品的特点呢?这就需要你把它们分分类,有些差不多的产品,熟悉了其中的一个,剩下的我们只要记下差异点就可以了。那下面的问题就是如何分类了,这个其实很简单,因为业界已经有共识,把产品按照架构风格划分到不同的阵营。
|
||||
|
||||
总的来说,分布式数据库大多可以分为两种架构风格,一种是NewSQL,它的代表系统是Google Spanner;另一种是从单体数据库中间件基础上演进出来的,被称为Prxoy风格,没有公认的代表系统。我觉得Prxoy这个名字太笼统,没有反映架构的全貌,还是要有一个具体的架构模板,才能便于你理解,所以我选了一个出现较早的产品来指代这种风格,这就是PostgreSQL-XC(下文简称PGXC)。
|
||||
|
||||
我在后面的课程中讲述分布式数据库的特性和原理的时候,也会沿着这两种架构风格的思路,帮助你去迅速抓住不同产品的要点。因此,我们今天就先用一讲来学习下这两种架构风格。
|
||||
|
||||
## 数据库的基本架构
|
||||
|
||||
要搞清楚分布式数据库的架构风格,就要先了解“数据库”的架构。当然,我们这里说的数据库仍然默认是关系型数据库。我们先通过一张架构图看看数据库的全貌。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/02/66/0224c515b201a42725a5ed3ce9a3c366.jpg" alt="">
|
||||
|
||||
这张图从约瑟夫 · 海勒斯坦(Joseph M. Hellerstein)等人的论文“[Architecture of a Database System](https://dsf.berkeley.edu/papers/fntdb07-architecture.pdf)”中翻译而来。文中将数据库从逻辑上拆分为5个部分,分别是客户端通讯管理器(Client Communications Manager)、查询处理器(Relational Query Processor)、事务存储管理器(Transactional Storage Manager)、进程管理器(Process Manager)和共享组件与工具(Shared Components and Utilities),每个部分下面又可以拆分成一些组件。
|
||||
|
||||
你在各种数据库产品中都能找到这5个部分的对应实现,比如Oracle、DB2、SQL Server和MySQL,无一例外。下面,我依次介绍下这5个部分的功能。
|
||||
|
||||
<li>
|
||||
**客户端通讯管理器。**这是应用开发者能够直观感受到的模块,通常我们使用JDBC或者ODBC协议访问数据库时,连接的就是这个部分。
|
||||
</li>
|
||||
<li>
|
||||
**进程管理器。**连接建好了,数据库会为客户端分配一个进程,客户端后续发送的所有操作都会通过对应的进程来执行。当然,这里的进程只是大致的说法。事实上,Oracle和PostgreSQL是进程的方式,而MySQL使用的则是线程。还有,进程与客户也不都是简单的一对一关系,但这部分功能不会影响你对分布式数据库的理解,可以略过。
|
||||
</li>
|
||||
<li>
|
||||
**查询处理器。**它包括四个部分,功能上是顺序执行的。首先是解析器,它将接收到的SQL解析为内部的语法树。然后是查询重写(Query Rewrite),它也被称为逻辑优化,主要是依据关系代数的等价变换,达到简化和标准化的目的,比如会消除重复条件或去掉一些无意义谓词 ,还有将视图替换为表等操作。再往后就是查询算法优化(Query Optimizer),它也被称为物理优化,主要是根据表连接方式、连接顺序和排序等技术进行优化,我们常说的基于规则优化(RBO)和基于代价优化(CBO)就在这部分。最后就是计划执行器(Plan Executor),最终执行查询计划,访问存储系统。
|
||||
</li>
|
||||
<li>
|
||||
**事务存储管理器。**它包括四个部分,其中访问方式(Access Methods)是指数据在磁盘的具体存储形式。锁管理(Lock Manager)是指并发控制。日志管理(Log Manager)是确保数据的持久性。缓存管理(Buffer Manager)则是指I/O操作相关的缓存控制。
|
||||
</li>
|
||||
<li>
|
||||
**共享组件和工具。**在整个过程中还会涉及到的一些辅助操作,当然它们对于数据库的运行也是非常重要的。例如编目数据管理器(Catalog Manager)会记录数据库的表、字段、视图等元数据信息,并根据这些信息来操作具体数据内容。复制机制(Replication)也很重要,它是实现系统高可靠性的基础,在单体数据库中,通过主备节点复制的方式来实现数据的复制。
|
||||
</li>
|
||||
|
||||
到这里,你应该对数据库的运行过程有了一个大致的理解,这样就能够串接起后续要讲到的PGXC和NewSQL两种架构风格的关键功能了。当然,数据库本身的运行机制是比较复杂的,就算只是其中的一个具体模块,我们用整整一讲都不一定能够说清楚。如果你希望进一步了解的话,可以仔细研读约瑟夫 · 海勒斯坦的这篇论文。
|
||||
|
||||
## PGXC:单体数据库的自然演进
|
||||
|
||||
单体数据库的功能看似已经很完善了,但在面临高并发场景的时候,还是会碰到写入性能不足的问题,很难解决。因此,也就有了向分布式数据库演进的动力。要解决写入性能不足的问题,大家首先想到的,最简单直接的办法就是分库分表。
|
||||
|
||||
分库分表方案就是在多个单体数据库之前增加代理节点,本质上是增加了SQL路由功能。这样,代理节点首先解析客户端请求,再根据数据的分布情况,将请求转发到对应的单体数据库。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/1b/91/1b2f74aa08e35b6fa326065fc5527391.jpg" alt="">
|
||||
|
||||
代理节点需要实现三个主要功能,它们分别是客户端接入、简单的查询处理器和进程管理中的访问控制。
|
||||
|
||||
另外,分库分表方案还有一个重要的功能,那就是分片信息管理,分片信息就是数据分布情况,是区别于编目数据的一种元数据。不过考虑到分片信息也存在多副本的一致性的问题,大多数情况下它会独立出来,更详细的原因我在第7讲中展开说明。
|
||||
|
||||
显然,如果把每一次的事务写入都限制在一个单体数据库内,业务场景就会很受局限。因此,跨库事务成为必不可少的功能,但是单体数据库是不感知这个事情的,所以我们就要在代理节点增加分布式事务组件。
|
||||
|
||||
同时,简单的分库分表不能满足全局性的查询需求,因为每个数据节点只能看到一部分数据,有些查询运算是无法处理的,比如排序、多表关联等。所以,代理节点要增强查询计算能力,支持跨多个单体数据库的查询。
|
||||
|
||||
随着分布式事务和跨节点查询等功能的加入,代理节点已经不再只是简单的路由功能,更多时候会被称为协调节点。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/fa/2f/fa871c7ecb1b2f327e1261775a512f2f.jpg" alt="">
|
||||
|
||||
很多分库分表方案会演进到这个阶段,比如MyCat。这时离分布式数据库还差重要的一步,就是全局时钟。我们在[第2讲](https://time.geekbang.org/column/article/272104)已经介绍了全局时钟的意义,它是实现数据一致性的必要条件。
|
||||
|
||||
加上这最后一块拼图,PGXC区别于单体数据库的功能也就介绍完整了,它们是分片、分布式事务、跨节点查询和全局时钟。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/2a/4e/2a698e380e08621a2e3b7196ebdcf54e.jpg" alt="">
|
||||
|
||||
协调节点与数据节点,实现了一定程度上的计算与存储分离,这也是所有分布式数据库的一个架构基调。但是,因为PGXC的数据节点本身就是完整的单体数据库,所以也具备很强的计算能力。
|
||||
|
||||
说了这么多,PGXC风格的分布式数据库到底包括哪些产品呢?PGXC(PostgreSQL-XC)的本意是指一种以PostgreSQL为内核的开源分布式数据库。因为PostgreSQL的影响力和开放的软件版权协议(类似BSD),很多厂商在PGXC上二次开发,推出自己的产品。不过,这些改动都没有变更主体架构风格,所以我把这类产品统称为PGXC风格,其中包括TBase、GuassDB 300和AntDB等。当然,这里所说的PGXC并不限于以PostgreSQL为内核,那些以MySQL为内核的产品往往也会采用同样的架构,例如GoldenDB,所以我把它们也归入了PGXC风格。
|
||||
|
||||
## NewSQL:革命性的新架构
|
||||
|
||||
相对于PGXC,NewSQL有着完全不同的发展路线。NewSQL也叫原生分布式数据库,我觉得这个名字能更准确地体现这类架构风格的特点,就是说它的每个组件在设计之初都是基于分布式架构的,不像PGXC那样带有明显的单体架构痕迹。
|
||||
|
||||
NewSQL的基础是NoSQL,更具体地说,是类似BigTable的分布式键值(K/V)系统。分布式键值系统选择做了一个减法,完全放弃了数据库事务处理能力,然后将重点放在对存储和写入能力的扩展上,这个能力扩展的基础就是分片。引入分片的另一个好处是,系统能够以更小的粒度调度数据,实现各节点上的存储平衡和访问负载平衡。
|
||||
|
||||
分布式键值系统由于具备这些鲜明的特点,所以在不少细分场景获得了成功(比如电商网站对于商品信息的存储),但在面对大量的事务处理场景时就无能为力了(比如支付系统)。这种状况直到Google Spanner横空出世才被改变,因为Spanner基于BigTable构建了新的事务能力。
|
||||
|
||||
除了上述内容,NewSQL还有两个重要的革新,分别出现在高可靠机制和存储引擎的设计上。
|
||||
|
||||
高可靠机制的变化在于,放弃了粒度更大的主从复制,转而以分片为单位采用Paxos或Raft等共识算法。这样,NewSQL就实现了更小粒度的高可靠单元,获得了更高的系统整体可靠性。存储引擎层面,则是使用LSM-Tree模型替换B+ Tree模型,大幅提升了写入性能。
|
||||
|
||||
由于NewSQL在架构上的革新性,产品实现的难度比PGXC要大,所以产品就相对少一些。Spanner是NewSQL的开山鼻祖,这个不用说了;其他知名度比较高的产品有CockroachDB、TiDB和YugabyteDB,这三款数据库都宣称设计灵感来自Spanner;另外就是阿里自研的OceanBase,因为它有一个代理层,有时会被同行质疑,但是从整体架构风格看,我还是愿意把它归为NewSQL。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/67/87/67b03095173a1cf570cdeec485b7aa87.jpg" alt="">
|
||||
|
||||
从系统架构上看,我个人认为,NewSQL的设计思想更加领先,具有里程碑意义,而PGXC的架构偏于保守。但PGXC的优势则在于稳健,直接采用单机数据库作为数据节点,大幅降低了工程开发的工作量,也减少了引入风险的机会。总的来说,NewSQL的长处在架构设计,PGXC的长处则在工程实现。
|
||||
|
||||
当然,NewSQL的架构设计也不是完美无缺。比如,作为一个计算与存储分离得更加彻底的架构,NewSQL的计算节点需要借助网络才能与存储节点通讯,这意味着要花费更大的代价来传输数据。随着NewSQL分布式数据库的应用实践越来越多,很多产品为了获得更好的计算性能,会尽量将更多计算下压到存储节点执行。这种架构上的修正,似乎也可以理解为,NewSQL朝PGXC的方向做了一点回拨。
|
||||
|
||||
## 小结
|
||||
|
||||
关于分布式数据库的两种架构风格,我们今天就先学到这里了。最后,我们再一起复习下今天的重点内容。
|
||||
|
||||
1. 从架构上,数据库可以被拆分为5个部分,分别是客户端通讯管理器、进程管理器、查询处理器、事务存储管理器和共享组件与工具。分布式数据库在此基础上增加四个主要功能,包括分片信息管理、分布式事务管理、跨节点查询和全局时钟。
|
||||
1. PGXC架构是从分库分表方案演进而来的。它设置了协调节点,在代理功能的基础上增加了分布式事务管理、跨节点查询功能;原有的单体数据继续作为数据节点;新增了全局时钟和分片信息管理两个功能,这两个功能又有两种实现情况,一是拆分为两个独立角色节点,例如GoldenDB,二是合并为一个角色节点,例如TBase。
|
||||
1. NewSQL架构是原生分布式数据库,架构中的每个层次的设计都是以分布式为目标。NewSQL是从分布式键值系统演进而来,主要的工作负载由计算节点和存储节点承担,另外由管理节点承担全局时钟和分片信息管理功能。不过,这三类节点是逻辑功能上划分,在设计实现层面是可分可合的。比如,TiDB是分为独立节点,CockroachDB则是对等的P2P架构。
|
||||
1. NewSQL在架构上更加领先,而PGXC最大程度复用了单体数据库的工程实现,更加稳健。
|
||||
|
||||
今天我们从单体数据库架构出发,简单介绍了PGXC和NewSQL两种架构。为了帮助你迅速地把握要点,在内容上,我专门挑选了那些最能体现与单体数据库差异的部分。不过,这些内容尚不足以完全解释数据库的整体运作原理,但对于你理解两种架构风格的分布式数据库产品的基本框架足够了。如果你想更彻底、更全面地了解数据库架构,我建议你仔细研读“Architecture of a Database System”和另一本非常值得阅读的经典教材《数据库系统实现》。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ba/77/bac0b877eb2dd6abf0f6921a28d76f77.jpg" alt="">
|
||||
|
||||
## 思考题
|
||||
|
||||
按照惯例,最后是思考题时间。今天我们介绍了两种不同的架构风格,你会将自己熟悉的分布式数据库归入哪一类呢?或者如果你有熟悉的NoSQL产品,可以和NewSQL比较一下,谈谈它们架构上的差异。
|
||||
|
||||
欢迎你在评论区留言和我一起讨论,我会在答疑篇和你继续探讨这个问题。如果你身边的朋友也对分布式数据库的架构风格感兴趣,你也可以把今天这一讲分享给他,我们一起讨论。
|
||||
|
||||
## 学习资料
|
||||
|
||||
Joseph M. Hellerstein et al.:[**Architecture of a Database System**](https://dsf.berkeley.edu/papers/fntdb07-architecture.pdf)
|
||||
|
||||
加西亚-莫利纳 等:[《数据库系统实现》](https://book.douban.com/subject/4838430/)
|
||||
176
极客时间专栏/geek/分布式数据库30讲/基础篇/05 | 全局时钟:物理时钟和逻辑时钟你Pick谁?.md
Normal file
176
极客时间专栏/geek/分布式数据库30讲/基础篇/05 | 全局时钟:物理时钟和逻辑时钟你Pick谁?.md
Normal file
@@ -0,0 +1,176 @@
|
||||
<audio id="audio" title="05 | 全局时钟:物理时钟和逻辑时钟你Pick谁?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/99/dc/99df47f2645a3d0769e2df95dd6465dc.mp3"></audio>
|
||||
|
||||
你好,我是王磊,你也可以叫我Ivan。
|
||||
|
||||
今天,我想和你聊聊时间的话题。
|
||||
|
||||
“时光一去永不回,往事只能回味”,这种咏叹时光飞逝的歌曲,你一定听过很多。但是,在计算机的世界里,时间真的是一去不回吗?还真不一定。
|
||||
|
||||
还记得我在[第2讲](https://time.geekbang.org/column/article/272104)提到的TrueTime吗?作为全局时钟的一种实现形式,它是Google通过 GPS和原子钟两种方式混合提供的授时机制,误差可以控制在7毫秒以内。正是在这7毫秒内,时光是可能倒流的。
|
||||
|
||||
为什么我们这么关注时间呢?是穿越剧看多了吗?其实,这是因为分布式数据库的很多设计都和时间有关,更确切地说是和全局时钟有关。比如,我们在第2讲提到的线性一致性,它的基础就是全局时钟,还有后面会讲到的多版本并发控制(MVCC)、快照、乐观协议与悲观协议,都和时间有关。
|
||||
|
||||
## 常见授时方案
|
||||
|
||||
那既然有这么多分布式数据库,授时机制是不是也很多,很复杂呢?其实,要区分授时机制也很简单,抓住三个要素就可以了。
|
||||
|
||||
1. 时间源:单个还是多个
|
||||
1. 使用的时钟类型:物理时钟还是混合逻辑时钟
|
||||
1. 授时点:一个还是多个
|
||||
|
||||
根据排列组合,一共产生了8种可能性,其中NTP(Network Time Protocol)误差大,也不能保证单调递增,所以就没有单独使用NTP的产品;还有一些方案在实践中则是不适用的(N/A)。因此常见的方案主要只有4类,我画了张表格,总结了一下。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/85/f4/85d161f3cbf5a162b78ayydf318cbdf4.jpg" alt="">
|
||||
|
||||
### 1. TrueTime
|
||||
|
||||
Spanner采用的方案是TrueTime。它的时间源是GPS和原子钟,所以属于多时间源和物理时钟,同时它也采用了多点授时机制,就是说集群内有多个时间服务器都可以提供授时服务。
|
||||
|
||||
就像这一讲开头说的,TrueTime是会出现时光倒流的。例如,A、B两个进程先后调用TrueTime服务,各自拿到一个时间区间,如果在其中随机选择,则可能出现B的时间早于A的时间。不只是TrueTime,任何物理时钟都会存在时钟偏移甚至回拨。
|
||||
|
||||
单个物理时钟会产生误差,而多点授时又会带来整体性的误差,那TrueTime为什么还要这么设计呢?
|
||||
|
||||
因为它也有两个显著的优势:首先是高可靠高性能,多时间源和多授时点实现了完全的去中心化设计,不存在单点;其次是支持全球化部署,客户端与时间服务器的距离也是可控的,不会因为两者通讯延迟过长导致时钟失效。
|
||||
|
||||
### 2. HLC
|
||||
|
||||
CockroachDB和YugabyteDB也是以高性能高可靠和全球化部署为目标,不过Truetime是Google的独门绝技,它依赖于特定硬件设备的思路,不适用于开源软件。所以,它们使用了混合逻辑时钟(Hybrid Logical Clock,HLC),同样是多时间源、多点授时,但时钟采用了物理时钟与逻辑时钟混合的方式。HLC在实现机制上也是蛮复杂的,而且和TrueTime同样有整体性的时间误差。
|
||||
|
||||
对于这个共性问题,Spanner和CockroachDB都会通过一些容错设计来消除时间误差,我会在第12讲中具体介绍相关内容。
|
||||
|
||||
### 3. TSO
|
||||
|
||||
其他的分布式数据库大多选择了单时间源、单点授时的方式,承担这个功能的组件在NewSQL风格架构中往往被称为TSO(Timestamp Oracle),而在PGXC风格架构中被称为全局事务管理器(Golobal Transcation Manager,GTM)。这就是说一个单点递增的时间戳和全局事务号基本是等效的。这种授时机制的最大优点就是实现简便,如果能够保证时钟单调递增,还可以简化事务冲突时的设计。但缺点也很明显,集群不能大范围部署,同时性能也有上限。TiDB、OceanBase、GoldenDB和TBase等选择了这个方向。
|
||||
|
||||
### 4. STP
|
||||
|
||||
最后,还有一些小众的方案,比如巨杉的STP(SequoiaDB Time Protoco)。它采用了单时间源、多点授时的方式,优缺点介于HLC和TSO之间。
|
||||
|
||||
到这里,我已经介绍了4种方案在技术路线上大致的区别。其中TrueTime是基于物理设备的外部授时方案,所以Spanner直接使用就可以了,自身不需要做专门的设计。而对于其他3种方案,如果我们想要深入理解,那么还得结合具体的产品来看。
|
||||
|
||||
## 中心化授时:TSO(TiDB)
|
||||
|
||||
首先,我们从最简单的TSO开始。
|
||||
|
||||
最早提出TSO的,大概是Google的论文“ [Large-scale Incremental Processing Using Distributed Transactions and Notifications](https://www.cs.princeton.edu/courses/archive/fall10/cos597B/papers/percolator-osdi10.pdf)”。这篇论文主要是介绍分布式存储系统Percolator的实现机制,其中提到通过一台Oracle为集群提供集中授时服务,称为Timestamp Oracle。所以,后来的很多分布式系统也用它的缩写来命名自己的单点授时机制,比如TiDB和Yahoo的Omid。
|
||||
|
||||
考虑到TiDB的使用更广泛些,这里主要介绍TiDB的实现方式。
|
||||
|
||||
TiDB的全局时钟是一个数值,它由两部分构成,其中高位是物理时间,也就是操作系统的毫秒时间;低位是逻辑时间,是一个18位的数值。那么从存储空间看,1毫秒最多可以产生262,144个时间戳(2^18),这已经是一个很大的数字了,一般来说足够使用了。
|
||||
|
||||
单点授时首先要解决的肯定是单点故障问题。TiDB中提供授时服务的节点被称为Placement Driver,简称PD。多个PD节点构成一个Raft组,这样通过共识算法可以保证在主节点宕机后马上选出新主,在短时间内恢复授时服务。
|
||||
|
||||
那问题来了,如何保证新主产生的时间戳一定大于旧主呢?那就必须将旧主的时间戳存储起来,存储也必须是高可靠的,所以TiDB使用了etcd。但是,每产生一个时间戳都要保存吗?显然不行,那样时间戳的产生速度直接与磁盘I/O能力相关,会存在瓶颈的。
|
||||
|
||||
如何解决性能问题呢?TiDB采用预申请时间窗口的方式,我画了张图来表示这个过程。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/3c/d6/3c9703cafb44f53596b673d9293e12d6.jpg" alt="">
|
||||
|
||||
当前PD(主节点)的系统时间是103毫秒,PD向etcd申请了一个“可分配的时间窗口”。要知道时间窗口的跨度是可以通过参数指定的,系统的默认配置是3毫秒,示例采用了默认配置,所以这个窗口的起点是PD当前时间103,时间窗口的终点就在106毫秒处。。写入etcd成功后,PD将得到一个从103到106的“可分配时间窗口”,在这个时间窗口内PD可以使用系统的物理时间作为高位,拼接自己在内存中累加的逻辑时间,对外分配时间戳。
|
||||
|
||||
上述设计意味着,所有PD已分配时间戳的高位,也就是物理时间,永远小于etcd存储的最大值。那么,如果PD主节点宕机,新主就可以读取etcd中存储的最大值,在此基础上申请新的“可分配时间窗口”,这样新主分配的时间戳肯定会大于旧主了。
|
||||
|
||||
此外,为了降低通讯开销,每个客户端一次可以申请多个时间戳,时间戳数量作为参数,由客户端传给PD。但要注意的是,一旦在客户端缓存,多个客户端之间时钟就不再是严格单调递增的,这也是追求性能需要付出的代价。
|
||||
|
||||
## 分布式授时:HLC(CockroachDB)
|
||||
|
||||
前面已经说过TrueTime依赖Google强大的工程能力和特殊硬件,不具有普适性。相反,HLC作为一种纯软的实现方式,更加灵活,所以在CockroachDB、YugabyteDB和很多分布式存储系统得到了广泛使用。
|
||||
|
||||
HLC不只是字面上的意思, TiDB的TSO也混合了物理时钟与逻辑时钟,但两者截然不同。HLC代表了一种计时机制,它的首次提出是在论文“[Logical Physical Clocks and Consistent Snapshots in Globally Distributed Databases](https://cse.buffalo.edu/~demirbas/publications/hlc.pdf)”中,CockroachDB和YugabyteDB的设计灵感都来自于这篇论文。下面,我们结合图片介绍一下这个机制。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/1c/6b/1c40af51f993yyc296635ef27de7e26b.jpg" alt="">
|
||||
|
||||
假如我们有ABCD四个节点,方框是节点上发生的事件,方框内的三个数字依次是节点的本地物理时间(简称本地时间,Pt)、HLC的高位(简称L值)和HLC的低位(简称C值)。
|
||||
|
||||
A节点的本地时间初始值为10,其他节点的本地时间初始值都是0。四个节点的第一个事件都是在节点刚启动的一刻发生的。首先看A1,它的HLC应该是(10,0),其中高位直接取本地时间,低位从0开始。同理,其他事件的HLC都是(0,0)。
|
||||
|
||||
然后我们再看一下,随着时间的推移,接下来的事件如何计时。
|
||||
|
||||
事件D2发生时,首先取上一个事件D1的L值和本地时间比较。L值等于0,本地时间已经递增变为1,取最大值,那么用本地时间作为D2的L值。高位变更了,低位要归零,所以D2的HLC就是(1,0)。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/7b/52/7b34a1c812284bc7049f9ece2323bd52.jpg" alt="">
|
||||
|
||||
如果你看懂了D2的计时逻辑就会发现,D1其实是一样的,只不过D1没有上一个事件的L值,只能用0代替,是一种特殊情况。
|
||||
|
||||
如果节点间有调用关系,计时逻辑会更复杂一点。我们看事件B2,要先判断B2的L值,就有三个备选:
|
||||
|
||||
1. 本节点上前一个事件B1的L值
|
||||
1. 当前本地时间
|
||||
1. 调用事件A1的L值,A1的HLC是随着函数调用传给B节点的
|
||||
|
||||
这三个值分别是0、1和10。按照规则取最大值,所以B2的L值是10,也就是A1的L值,而C值就在A1的C值上加1,最终B2的HLC就是(10,1)。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e6/a4/e6de74cb1b9d2a92cb3bcb711120c3a4.jpg" alt="">
|
||||
|
||||
B3事件发生时,发现当前本地时间比B2的L值还要小,所以沿用了B2的L值,而C值是在B2的C值上加一,最终B3的HLC就是(10,2)。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d0/55/d0473eb6c84a264d2be5104a62d77655.jpg" alt="">
|
||||
|
||||
论文中用伪码表述了完整的计时逻辑,我把它们复制在下面,你可以仔细研究。
|
||||
|
||||
```
|
||||
Initially l:j := 0; c:j := 0
|
||||
Send or local event
|
||||
l’:j := l:j;
|
||||
l:j := max(l’:j; pt:j);
|
||||
If (l:j=l’:j) then c:j := c:j + 1
|
||||
Else c:j := 0;
|
||||
Timestamp with l:j; c:j
|
||||
Receive event of message m
|
||||
l’:j := l:j;
|
||||
l:j := max(l’:j; l:m; pt:j);
|
||||
If (l:j=l’:j=l:m) then c:j := max(c:j; c:m)+1
|
||||
Elseif (l:j=l’:j) then c:j := c:j + 1
|
||||
Elseif (l:j=l:m) then c:j := c:m + 1
|
||||
Else c:j := 0
|
||||
Timestamp with l:j; c:j
|
||||
|
||||
```
|
||||
|
||||
其中,对于节点J,l.j表示L值,c.j表示C值,pt.j表示本地物理时间。
|
||||
|
||||
在HLC机制下,每个节点会使用本地时钟作为参照,但不受到时钟回拨的影响,可以保证单调递增。本质上,HLC还是Lamport逻辑时钟的变体,所以对于不同节点上没有调用关系的两个事件,是无法精确判断先后关系的。比如,上面例子中的C2和D2有同样的HLC,但从上帝视角看,C2是早于D2发生的,因为两个节点的本地时钟有差异,就没有体现这种先后关系。HLC是一种松耦合的设计,所以不会去校正节点的本地时钟,本地时钟是否准确,还要靠NTP或类似的协议来保证。
|
||||
|
||||
## 多层级中心化授时:STP(巨杉)
|
||||
|
||||
巨杉采用了单时间源、多点授时机制,它有自己的全局时间协议,称为STP(Serial Time Protocol),是内部逻辑时间同步的协议,并不依赖于NTP协议。
|
||||
|
||||
下面是STP体系下各角色节点的关系。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c4/ca/c463c3aa47d625a964422yy3df7d2cca.jpg" alt="">
|
||||
|
||||
STP是独立于分布式数据库的授时方案,该体系下的各角色节点与巨杉的其他角色节点共用机器,但没有必然的联系。
|
||||
|
||||
STP下的所有角色统称为STP Node,具体分为两类:
|
||||
|
||||
1. **STP Server。**多个STP Server构成STP Server组,组内根据协议进行选主,主节点被称为Primary,对外提供服务。
|
||||
1. **STP Client。**按照固定的时间间隔,从Primary Server 同步时间。
|
||||
|
||||
巨杉数据库的其他角色节点,如编目节点(CATALOG)、协调节点(COORD)和数据节点(DATA)等,都从本地的STP Node节点获得时间。
|
||||
|
||||
STP与 TSO一样都是单时间源,但通过增加更多的授时点,避免了单点性能瓶颈,而负副作用是多点授时就会造成全局性的时间误差,因此和HLC一样需要做针对性设计。
|
||||
|
||||
## 小结
|
||||
|
||||
好了,今天的内容就到这里了,我们一起回顾下这节课的重点。
|
||||
|
||||
1. 分布式数据库有多种授时机制,它们的区别主要看三个维度。一,是单时间源还是多时间源;二,时间源采用的是物理时钟还是混合逻辑时钟;三,授时点是一个还是多个。
|
||||
1. TrueTime是多时间源、多授时点方案,虽然仍存在时间误差的问题,但实现了高可靠高性能,能够支持Spanner做到全球化部署,是一种非常强悍的设计方案。TrueTime是GPS加原子钟的整合方案,可以看作为一种物理时钟,它完全独立于Spanner的授时服务,不需要Spanner做专门的设计。
|
||||
1. HLC同样是多时间源、多授时点,由于是纯软方案,所以具有更好的通用性。CockroachDB和YugabyteDB都采用了这种方案,也都具备全球化部署能力。HLC的设计基础是Lamport逻辑时钟,对NTP的时间偏移有一定的依赖。
|
||||
1. TSO是典型的单时间源、单点授时方案,实现简便,所以成为多数分布式数据库的选择。如果TSO能够做到单调递增,会简化读写冲突时候的处理过程,但缺点是集群部署范围受到极大的限制。
|
||||
1. 还有一些小众的方案,比如巨杉的STP,也试图在寻求新的平衡点。
|
||||
|
||||
有关时间的话题我们就聊到这了。时间误差是普遍存在的,只不过长期在单体应用系统下开发,思维惯性让我们忽略了它,但随着分布式架构的普及,我相信更多的架构设计中都要考虑这个因素。我建议你收藏今天的内容,因为即使抛开分布式数据库不谈,这些设计依然是值得借鉴的。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/1a/b1/1a6ccbf7fa3801216468c311363a9fb1.jpg" alt="">
|
||||
|
||||
## 思考题
|
||||
|
||||
最后,今天留给你的思考题还是关于时间的。在后续课程没有展开之前,我们不妨先来开放式地讨论一下,你觉得时间对于分布式数据库的影响是什么?或者你也可以谈谈在其他分布式系统中曾经遇到的关于时间的问题。
|
||||
|
||||
欢迎你在评论区留言和我一起讨论,我会在答疑篇回复这个问题。如果你身边的朋友也对全局时钟或者分布式架构下如何同步时间这个话题感兴趣,你也可以把今天这一讲分享给他,我们一起讨论。
|
||||
|
||||
## 学习资料
|
||||
|
||||
Daniel Peng and Frank Dabek: [**Large-scale Incremental Processing Using Distributed Transactions and Notifications**](https://www.cs.princeton.edu/courses/archive/fall10/cos597B/papers/percolator-osdi10.pdf)<br>
|
||||
Sandeep S. Kulkarni et al.: [**Logical Physical Clocks and Consistent Snapshots in Globally Distributed Databases**](https://cse.buffalo.edu/~demirbas/publications/hlc.pdf)
|
||||
161
极客时间专栏/geek/分布式数据库30讲/基础篇/06 | 分片机制:为什么说Range是更好的分片策略?.md
Normal file
161
极客时间专栏/geek/分布式数据库30讲/基础篇/06 | 分片机制:为什么说Range是更好的分片策略?.md
Normal file
@@ -0,0 +1,161 @@
|
||||
<audio id="audio" title="06 | 分片机制:为什么说Range是更好的分片策略?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/5y/22/5yyc30e6949e39b5cb83f5530a13b122.mp3"></audio>
|
||||
|
||||
你好,我是王磊,你也可以叫我Ivan。
|
||||
|
||||
在这一讲的开头,我想请你思考一个问题,你觉得在大规模的业务应用下,单体数据库遇到的主要问题是什么?对,首先就是写入性能不足,这个我们在[第4讲](https://time.geekbang.org/column/article/274200)也说过,另外还有存储方面的限制。而分片就是解决性能和存储这两个问题的关键设计,甚至不仅是分布式数据库,在所有分布式存储系统中,分片这种设计都是广泛存在的。
|
||||
|
||||
所以今天,就让我们好好了解一下,分片到底是怎么回事儿。
|
||||
|
||||
## 什么是分片
|
||||
|
||||
分片在不同系统中有各自的别名,Spanner和YugabyteDB中被称为Tablet,在HBase和TiDB中被称为Region,在CockraochDB中被称为Range。无论叫什么,概念都是一样的,分片是一种水平切分数据表的方式,它是数据记录的集合,也是数据表的组成单位。
|
||||
|
||||
分布式数据库的分片与单体数据库的分区非常相似,区别在于:分区虽然可以将数据表按照策略切分成多个数据文件,但这些文件仍然存储在单节点上;而分片则可以进一步根据特定规则将切分好的文件分布到多个节点上,从而实现更强大的存储和计算能力。
|
||||
|
||||
分片机制通常有两点值得关注:
|
||||
|
||||
1. 分片策略
|
||||
|
||||
主要有Hash(哈希)和Range(范围)两种。你可能还听到过Key和List,其实Key和List可以看作是Hash和Range的特殊情况,因为机制类似,我们这里就不再细分了。
|
||||
|
||||
1. 分片的调度机制
|
||||
|
||||
分为静态与动态两种。静态意味着分片在节点上的分布基本是固定的,即使移动也需要人工的介入;动态则是指通过调度管理器基于算法在各节点之间自动地移动分片。
|
||||
|
||||
我把分片机制的两个要点与[第5讲](https://time.geekbang.org/column/article/274908)提到的两种架构风格对应了一下,放到下面的表格中,希望能给你带来更直观的感受。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/69/ab/691fd31dd60df190cc725ae3f2d9ccab.jpg" alt="">
|
||||
|
||||
从表格中可以看出,PGXC只支持静态的Hash分片和Range分片,实现机制较为简单,所以,我就从这里开始展开吧。
|
||||
|
||||
## PGXC
|
||||
|
||||
### Hash分片
|
||||
|
||||
Hash分片,就是按照数据记录中指定关键字的Hash值将数据记录映射到不同的分片中。我画了一张图来表示Hash分片的过程。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/yy/60/yycace2bf5c6d8930ac68b1d6c197060.jpg" alt="">
|
||||
|
||||
图中的表格部分显示了一个社交网站的记录表,包括主键、用户ID、分享内容和分享时间等字段。假设以用户ID作为关键字进行分片,系统会通过一个Hash函数计算用户ID的Hash值而后取模,分配到对应的分片。模为4的原因是系统一共有四个节点,每个节点作为一个分片。
|
||||
|
||||
因为Hash计算会过滤掉数据原有的业务特性,所以可以保证数据非常均匀地分布到多个分片上,这是Hash分片最大的优势,而且它的实现也很简洁。但示例中采用的分片方法直接用节点数作为模,如果系统节点数量变动,模也随之改变,数据就要重新Hash计算,从而带来大规模的数据迁移。显然,这种方式对于扩展性是非常不友好的。
|
||||
|
||||
那接下来的问题就是,我们需要找一个方法提升系统的扩展性。你可能猜到了,这就是一致性Hash,该算法首次提出是在论文“[Consistent Hashing and Random Trees : Distributed Caching Protocols for Relieving Hot Spots on the World Wide Web](http://cs.brown.edu/courses/cs296-2/papers/consistent.pdf)”当中。
|
||||
|
||||
要在工业实践中应用一致性Hash算法,首先会引入虚拟节点,每个虚拟节点就是一个分片。为了便于说明,我们在这个案例中将分片数量设定为16。但实际上,因为分片数量决定了集群的最大规模,所以它通常会远大于初始集群节点数。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b9/ed/b931084d4f365f133765199dbddda9ed.jpg" alt="">
|
||||
|
||||
16个分片构成了整个Hash空间,数据记录的主键和节点都要通过Hash函数映射到这个空间。这个Hash空间是一个Hash环。我们换一种方式画图,可以看得更清楚些。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b4/2a/b4376b54f26b73f66d2bd20b53652e2a.jpg" alt="">
|
||||
|
||||
节点和数据都通过Hash函数映射到Hash环上,数据按照顺时针找到最近的节点。
|
||||
|
||||
当我们新增一台服务器,即节点E时,受影响的数据仅仅是新服务器到其环空间中前一台服务器(即沿着逆时针方向的第一台服务器)之间数据。结合我们的示例,只有小红分享的消息从节点B被移动到节点E,其他节点的数据保持不变。此后,节点B只存储Hash值6和7的消息,节点E存储Hash值4和5的消息。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/27/b7/27f8f563bc6d598abdd6b08509dd69b7.jpg" alt="">
|
||||
|
||||
Hash函数的优点是数据可以较为均匀地分配到各节点,并发写入性能更好。
|
||||
|
||||
本质上,Hash分片是一种静态分片方式,必须在设计之初约定分片的最大规模。同时,因为Hash函数已经过滤掉了业务属性,也很难解决访问业务热点问题。所谓业务热点,就是由于局部的业务活跃度较高,形成系统访问上的热点。这种情况普遍存在于各类应用中,比如电商网站的某个商品卖得比较好,或者外卖网站的某个饭店接单比较多,或者某个银行网点的客户业务量比较大等等。
|
||||
|
||||
### Range静态分片
|
||||
|
||||
与Hash分片不同,Range分片的特点恰恰是能够加入对于业务的预估。例如,我们用“Location”作为关键字进行分片时,不是以统一的行政级别为标准。因为注册地在北京、上海的用户更多,所以这两个区域可以按照区县设置分片,而海外用户较少,可以按国家设置为分片。这样,分片间的数据更加平衡。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/dc/5f/dcbda640005656263e8fc02d2c06295f.jpg" alt="">
|
||||
|
||||
但是,这种方式依然是静态的,如果海外业务迅速增长,服务海外用户的分片将承担更大的压力,可能导致性能下降,用户体验不佳。
|
||||
|
||||
相对Hash分片,Range分片的适用范围更加广泛。其中一个非常重要的原因是,Range分片可以更高效地扫描数据记录,而Hash分片由于数据被打散,扫描操作的I/O开销更大。但是,PGXC的Range分片受限于单体数据库的实现机制,很难随数据变动和负载变化而调整。
|
||||
|
||||
虽然有些PGXC同时支持两种分片方式,但Hash分片仍是主流,比如GoldenDB默认使用Hash分片,而TBase仅支持Hash分片。
|
||||
|
||||
## NewSQL
|
||||
|
||||
总体上,NewSQL也是支持Hash和Range两种分片方式的。具体就产品来说,CockroachDB和YugabyteDB同时支持两种方式,TiDB仅支持Range分片。
|
||||
|
||||
NewSQL数据库的Hash分片也是静态的,所以与PGXC差别不大,这里就不再赘述了。接下来,我们重点学习下Range动态分片。
|
||||
|
||||
### Range动态分片
|
||||
|
||||
NewSQL的Range分片,多数是用主键作为关键字来分片的,当然主键可以是系统自动生成的,也可以是用户指定的。既然提供了用户指定主键的方式,那么理论上可以通过设定主键的产生规则,控制数据流向哪个分片。但是,主键必须保证唯一性,甚至是单调递增的,导致这种控制就会比较复杂,使用成本较高。所以,我们基本可以认为,分片是一个系统自动处理的过程,用户是感知不到的。这样做的好处显然是提升了系统的易用性。
|
||||
|
||||
我们将NewSQL的Range分片称为动态分片,主要有两个原因:
|
||||
|
||||
1. **分片可以自动完成分裂与合并**
|
||||
|
||||
当单个分片的数据量超过设定值时,分片可以一分为二,这样就可以保证每个分片的数据量较为均衡。多个数据量较少的分片,会在一定的周期内被合并为一个分片。
|
||||
|
||||
还是回到我们社交网站这个例子,根据消息的数量来自动分片,我们可以得到R1、R2、R3三个分片。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d7/bc/d793d5ded136bfba12ae1996f06285bc.jpg" alt="">
|
||||
|
||||
分片也会被均衡地调度到各个节点上,节点间的数据量也保持总体平衡。
|
||||
|
||||
1. **可以根据访问压力调度分片**
|
||||
|
||||
我们看到系统之所以尽量维持分片之间,以及节点间的数据量均衡,存储的原因外,还可以更大概率地将访问压力分散到各个节点上。但是,有少量的数据可能会成为访问热点,就是上面提到的业务热点,从而打破这种均衡。比如,琦琦和静静都是娱乐明星,有很多粉丝关注她们分享的内容,其访问量远超过普通人。这时候,系统会根据负载情况,将R2和R3分别调度到不同的节点,来均衡访问压力。
|
||||
|
||||
**存储均衡**和**访问压力均衡**,是NewSQL分片调度机制普遍具备的两项能力。此外,还有两项能力在[Spanner论文](https://www.cs.princeton.edu/courses/archive/fall13/cos518/papers/spanner.pdf)中被提及,但在其他产品中没有看到工程化实现。
|
||||
|
||||
第一是**减少分布式事务**。
|
||||
|
||||
对分布式数据库来说,有一个不争的事实,那就是分布式事务的开销永远不会小于单节点本地事务的开销。因此,所有分布式数据库都试图通过减少分布式事务来提升性能。
|
||||
|
||||
Spanner在Tablet,也就是Range分片,之下增加了目录(Directory),作为数据调度的最小单位,它的调度范围是可以跨Tablet的。通过调度Directory可以将频繁参与同样事务的数据,转移到同一个Tablet下,从而将分布式事务转换为本地事务。
|
||||
|
||||
第二是**缩短服务延时**。
|
||||
|
||||
对于全球化部署的分布式数据库,数据可能存储在相距很远的多个数据中心,如果用户需要访问远端机房的数据,操作延时就比较长,这受制于数据传输速度。而Spanner可以将Directory调度到靠近用户的数据中心,缩短数据传输时间。当然,这里的调度对象都是数据的主副本,跨中心的数据副本仍然存在,负责保证系统整体的高可靠性。
|
||||
|
||||
Directory虽然带来新的特性,但显然也削弱了分片的原有功能,分片内的记录不再连续,扫描要付出更大成本。而减少分布式事务和靠近客户端位置这本身就是不能兼顾的,再加上存储和访问压力,分片调度机制要在四个目标间进行更复杂的权衡。
|
||||
|
||||
Spanner的这种设计能达到什么样的实际效果呢?我们现在还需要继续等待和观察。
|
||||
|
||||
## 分片与高可靠的关系
|
||||
|
||||
高可靠是分布式数据库的重要特性,分片是数据记录的最小组织单位,也必须是高可靠的。
|
||||
|
||||
NewSQL与PGXC的区别在于,对于NewSQL来说,分片是高可靠的最小单元;而对于PGXC,分片的高可靠要依附于节点的高可靠。
|
||||
|
||||
NewSQL的实现方式是复制组(Group)。在产品层面,通常由一个主副本和若干个副本组成,通过Raft或Paxos等共识算法完成数据同步,称为Raft Group或Paxos Group,所以我们简称这种方式为Group。因为不相关的数据记录会被并发操作,所以同一时刻有多个Group在工作。因此,NewSQL通常支持Multi Raft Group或者Multi Paxos Group。这里,我们先忽略Multi Paxos的另一个意思。
|
||||
|
||||
每个Group是独立运行的,只是共享相同的网络和节点资源,所以不同复制组的主副本是可以分布在不同节点的。
|
||||
|
||||
PGXC的最小高可靠单元由一个主节点和多个备节点组成,我们借用TDSQL中的术语,将其称为Set。一个PGXC是由多个Set组成。Set的主备节点间复制,多数采用半同步复制,平衡可靠性和性能。这意味着,所有分片的主副本必须运行在Set的主节点上。
|
||||
|
||||
从架构设计角度看,Group比Set更具优势,原因主要有两个方面。首先,Group的高可靠单元更小,出现故障时影响的范围就更小,系统整体的可靠性就更高。其次,在主机房范围内,Group的主副本可以在所有节点上运行,资源可以得到最大化使用,而Set模式下,占大多数的备节点是不提供有效服务的,资源白白浪费掉。
|
||||
|
||||
## 小结
|
||||
|
||||
好吧,今天的内容就到这里了,我们一起回顾下这节课的重点。
|
||||
|
||||
1. 分片是分布式数据库的关键设计,以此实现多节点的存储和访问能力。
|
||||
1. 分片机制的两个要点是分片策略和调度机制,分片策略包括Hash和Range两种,调度机制则分为静态和动态。
|
||||
1. PGXC使用单体数据库作为数据节点,往往只实现了静态分片。它的分片策略支持Hash和Range两种,其中Hash一般是指一致性Hash,可以最大程度规避节点扩缩带来的影响。Hash分片写性能出众,但查询性能差,Range则相反。
|
||||
1. NewSQL的默认分片策略通常是Range分片。分片调度机制为了实现存储平衡和访问压力平衡的目标,会将分片动态调度到各个节点。Spanner的设计又将在分片下拓展了Directory,通过对Directory的调度实现减少分布式事务和缩短延时的目标,但在其他分布式数据库中尚未看到对应的实现。
|
||||
1. NewSQL架构下,分片采用Paxos或Raft算法可以构成复制组,这种复制机制相比PGXC的主备节点复制,提供了更高的可靠性,资源使用也更加高效。
|
||||
|
||||
到这里你应该已经大体了解了分布式数据库分片机制。我们说Range是更好的分片策略,就是因为Range分片有条件做到更好的动态调度,只有动态了,才能自适应各种业务场景下的数据变化,平衡存储、访问压力、分布式事务和访问链路延时等多方面的诉求。从我个人的观点来说,NewSQL的Range分片方式更加优雅,随着单体数据库底层数据同步机制的改进,未来PGXC可能也会向这种方式靠拢。
|
||||
|
||||
如果你想更深入地了解Range分片机制,可以研究下[BigTable的论文](https://www2.cs.duke.edu/courses/cps399.28/spring08/papers/osdi06-ChangDeanEtAl-bigtable.pdf)。同时,因为HBase是业界公认的BigTable开源实现,所以你在它的[官方文档](https://hbase.apache.org/book.html#arch.overview)也能找到很多有用的内容。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/55/88/556ca784ab375b295716c8ef17897288.jpg" alt="">
|
||||
|
||||
## 思考题
|
||||
|
||||
Range分片的优势是动态调度,这就是说分片存储在哪个节点上是不断变化的。这时,客户端首先要知道分片的位置,就要先访问分片的元数据。你觉得这些元数据应该如何存储呢?是存储在某个中心点,还是分散在所有节点上呢?如果有多个副本,又该如何同步呢?
|
||||
|
||||
如果你想到了答案,又或者是触发了你对相关问题的思考,都可以在评论区和我聊聊,我会在答疑篇更系统地回复这个问题。如果你身边的朋友也对数据的分片机制,这个话题感兴趣,你也可以把今天这一讲分享给他,我们一起讨论。
|
||||
|
||||
## 学习资料
|
||||
|
||||
David Karge et al.: [**Consistent Hashing and Random Trees: Distributed Caching Protocols for Relieving Hot Spots on the World Wide Web**](http://cs.brown.edu/courses/cs296-2/papers/consistent.pdf)
|
||||
|
||||
Fay Chang et al.: [**Bigtable: A Distributed Storage System for Structured Data**](https://www2.cs.duke.edu/courses/cps399.28/spring08/papers/osdi06-ChangDeanEtAl-bigtable.pdf)
|
||||
|
||||
HBase: [**Apache HBase ™ Reference Guide**](https://hbase.apache.org/book.html#arch.overview)
|
||||
|
||||
James C. Corbett et al.: [**Spanner: Google’s Globally-Distributed Database**](https://www.cs.princeton.edu/courses/archive/fall13/cos518/papers/spanner.pdf)
|
||||
139
极客时间专栏/geek/分布式数据库30讲/基础篇/07 | 数据复制:为什么有时候Paxos不是最佳选择?.md
Normal file
139
极客时间专栏/geek/分布式数据库30讲/基础篇/07 | 数据复制:为什么有时候Paxos不是最佳选择?.md
Normal file
@@ -0,0 +1,139 @@
|
||||
<audio id="audio" title="07 | 数据复制:为什么有时候Paxos不是最佳选择?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/10/df/10ab50cebc79f04dd09fdabd360f57df.mp3"></audio>
|
||||
|
||||
你好,我是王磊,你也可以叫我Ivan。今天,我们要学习的是数据复制。
|
||||
|
||||
数据复制是一个老生常谈的话题了,典型的算法就是Paxo和Raft。只要你接触过分布式,就不会对它们感到陌生。经过从业者这些年的探索和科普,网上关于Paxos和Raft算法的高质量文章也是一搜一大把了。
|
||||
|
||||
所以,今天这一讲我不打算全面展开数据复制的方方面面,而是会聚焦在与分布式数据库相关的,比较重要也比较有意思的两个知识点上,这就是分片元数据的存储和数据复制的效率。
|
||||
|
||||
## 分片元数据的存储
|
||||
|
||||
我们知道,在任何一个分布式存储系统中,收到客户端请求后,承担路由功能的节点首先要访问分片元数据(简称元数据),确定分片对应的节点,然后才能访问真正的数据。这里说的元数据,一般会包括分片的数据范围、数据量、读写流量和分片副本处于哪些物理节点,以及副本状态等信息。
|
||||
|
||||
从存储的角度看,元数据也是数据,但特别之处在于每一个请求都要访问它,所以元数据的存储很容易成为整个系统的性能瓶颈和高可靠性的短板。如果系统支持动态分片,那么分片要自动地分拆、合并,还会在节点间来回移动。这样,元数据就处在不断变化中,又带来了多副本一致性(Consensus)的问题。
|
||||
|
||||
下面,让我们看看,不同的产品具体是如何存储元数据的。
|
||||
|
||||
### 静态分片
|
||||
|
||||
最简单的情况是静态分片。我们可以忽略元数据变动的问题,只要把元数据复制多份放在对应的工作节点上就可以了,这样同时兼顾了性能和高可靠。TBase大致就是这个思路,直接将元数据存储在协调节点上。即使协调节点是工作节点,随着集群规模扩展,会导致元数据副本过多,但由于哈希分片基本上就是静态分片,也就不用考虑多副本一致性的问题。
|
||||
|
||||
但如果要更新分片信息,这种方式显然不适合,因为副本数量过多,数据同步的代价太大了。所以对于动态分片,通常是不会在有工作负载的节点上存放元数据的。
|
||||
|
||||
那要怎么设计呢?有一个凭直觉就能想到的答案,那就是专门给元数据搞一个小规模的集群,用Paxos协议复制数据。这样保证了高可靠,数据同步的成本也比较低。
|
||||
|
||||
TiDB大致就是这个思路,但具体的实现方式会更巧妙一些。
|
||||
|
||||
### TiDB:无服务状态
|
||||
|
||||
在TiDB架构中,TiKV节点是实际存储分片数据的节点,而元数据则由Placement Driver节点管理。Placement Driver这个名称来自Spanner中对应节点角色,简称为PD。
|
||||
|
||||
在PD与TiKV的通讯过程中,PD完全是被动的一方。TiKV节点定期主动向PD报送心跳,分片的元数据信息也就随着心跳一起报送,而PD会将分片调度指令放在心跳的返回信息中。等到TiKV下次报送心跳时,PD就能了解到调度的执行情况。
|
||||
|
||||
由于每次TiKV的心跳中包含了全量的分片元数据,PD甚至可以不落盘任何分片元数据,完全做成一个无状态服务。这样的好处是,PD宕机后选举出的新主根本不用处理与旧主的状态衔接,在一个心跳周期后就可以工作了。当然,在具体实现上,PD仍然会做部分信息的持久化,这可以认为是一种缓存。
|
||||
|
||||
我将这个通讯过程画了下来,希望帮助你理解。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/94/61/946f37b234790208a6643b5703e65d61.jpg" alt="">
|
||||
|
||||
三个TiKV节点每次上报心跳时,由主副本(Leader)提供该分片的元数据,这样PD可以获得全量且没有冗余的信息。
|
||||
|
||||
虽然无状态服务有很大的优势,但PD仍然是一个单点,也就是说这个方案还是一个中心化的设计思路,可能存在性能方面的问题。
|
||||
|
||||
有没有完全“去中心化”的设计呢?当然是有的。接下来,我们就看看P2P架构的CockroachDB是怎么解决这个问题的。
|
||||
|
||||
### CockroachDB:去中心化
|
||||
|
||||
CockroachDB的解决方案是使用Gossip协议。你是不是想问,为什么不用Paxos协议呢?
|
||||
|
||||
这是因为Paxos协议本质上是一种广播机制,也就是由一个中心节点向其他节点发送消息。当节点数量较多时,通讯成本就很高。
|
||||
|
||||
CockroachDB采用了P2P架构,每个节点都要保存完整的元数据,这样节点规模就非常大,当然也就不适用广播机制。而Gossip协议的原理是谣言传播机制,每一次谣言都在几个人的小范围内传播,但最终会成为众人皆知的谣言。这种方式达成的数据一致性是 “最终一致性”,即执行数据更新操作后,经过一定的时间,集群内各个节点所存储的数据最终会达成一致。
|
||||
|
||||
看到这,你可能有点晕。我们在[第2讲](https://time.geekbang.org/column/article/272104)就说过分布式数据库是强一致性的,现在搞了个最终一致性的元数据,能行吗?
|
||||
|
||||
这里我先告诉你结论,**CockroachDB真的是基于“最终一致性”的元数据实现了强一致性的分布式数据库**。我画了一张图,我们一起走下这个过程。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/3a/23/3afa38b9a29e2ecfa17f6b809c1e2123.jpg" alt="">
|
||||
|
||||
1. 节点A接到客户端的SQL请求,要查询数据表T1的记录,根据主键范围确定记录可能在分片R1上,而本地元数据显示R1存储在节点B上。
|
||||
1. 节点A向节点B发送请求。很不幸,节点A的元数据已经过时,R1已经重新分配到节点C。
|
||||
1. 此时节点B会回复给节点A一个非常重要的信息,R1存储在节点C。
|
||||
1. 节点A得到该信息后,向节点C再次发起查询请求,这次运气很好R1确实在节点C。
|
||||
1. 节点A收到节点C返回的R1。
|
||||
1. 节点A向客户端返回R1上的记录,同时会更新本地元数据。
|
||||
|
||||
可以看到,CockroachDB在寻址过程中会不断地更新分片元数据,促成各节点元数据达成一致。
|
||||
|
||||
看完TiDB和CockroachDB的设计,我们可以做个小结了。复制协议的选择和数据副本数量有很大关系:如果副本少,参与节点少,可以采用广播方式,也就是Paxos、Raft等协议;如果副本多,节点多,那就更适合采用Gossip协议。
|
||||
|
||||
## 复制效率
|
||||
|
||||
说完了元数据的存储,我们再看看今天的第二个知识点,也就是数据复制效率的问题,具体来说就是Raft与Paxos在效率上的差异,以及Raft的一些优化手段。在分布式数据库中,采用Paxos协议的比较少,知名产品就只有OceanBase,所以下面的差异分析我们会基于Raft展开。
|
||||
|
||||
### Raft的性能缺陷
|
||||
|
||||
我们可以在网上看到很多比较Paxos和Raft的文章,它们都会提到在复制效率上Raft会差一些,主要原因就是Raft必须“顺序投票”,不允许日志中出现空洞。在我看来,顺序投票确实是影响Raft算法复制效率的一个关键因素。
|
||||
|
||||
接下来,我们就分析一下为什么“顺序投票”对性能会有这么大的影响。
|
||||
|
||||
我们先看一个完整的Raft日志复制过程:
|
||||
|
||||
1. Leader 收到客户端的请求。
|
||||
1. Leader 将请求内容(即Log Entry)追加(Append)到本地的Log。
|
||||
1. Leader 将Log Entry 发送给其他的 Follower。
|
||||
1. Leader 等待 Follower 的结果,如果大多数节点提交了这个 Log,那么这个Log Entry就是Committed Entry,Leader就可以将它应用(Apply)到本地的状态机。
|
||||
1. Leader 返回客户端提交成功。
|
||||
1. Leader 继续处理下一次请求。
|
||||
|
||||
以上是单个事务的运行情况。那么,当多事务并行操作时,又是什么样子的呢?我画了张图来演示这个过程。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c4/2b/c46f44da1ae27ffe6545c3d00964ba2b.jpg" alt="">
|
||||
|
||||
我们设定这个Raft组由5个节点组成,T1到T5是先后发生的5个事务操作,被发送到这个Raft组。
|
||||
|
||||
事务T1的操作是将X置为1,5个节点都Append成功,Leader节点Apply到本地状态机,并返回客户端提交成功。事务T2执行时,虽然有一个Follower没有响应,但仍然得到了大多数节点的成功响应,所以也返回客户端提交成功。
|
||||
|
||||
现在,轮到T3事务执行,没有得到超过半数的响应,这时Leader必须等待一个明确的失败信号,比如通讯超时,才能结束这次操作。因为有顺序投票的规则,T3会阻塞后续事务的进行。T4事务被阻塞是合理的,因为它和T3操作的是同一个数据项,但是T5要操作的数据项与T3无关,也被阻塞,显然这不是最优的并发控制策略。
|
||||
|
||||
同样的情况也会发生在Follower节点上,第一个Follower节点可能由于网络原因没有收到T2事务的日志,即使它先收到T3的日志,也不会执行Append操作,因为这样会使日志出现空洞。
|
||||
|
||||
Raft的顺序投票是一种设计上的权衡,虽然性能有些影响,但是节点间日志比对会非常简单。在两个节点上,只要找到一条日志是一致的,那么在这条日志之前的所有日志就都是一致的。这使得选举出的Leader与Follower同步数据非常便捷,开放Follower读操作也更加容易。要知道,我说的可是保证一致性的Follower读操作,它可以有效分流读操作的访问压力。这一点我们在24讲再详细介绍。
|
||||
|
||||
### Raft的性能优化方法(TiDB)
|
||||
|
||||
当然,在真正的工程实现中,Raft主副本也不是傻傻地挨个处理请求,还是有一些优化手段的。TiDB的官方文档对Raft优化说得比较完整,我们这里引用过来,着重介绍下它的四个优化点。
|
||||
|
||||
1. **批操作(Batch)。**Leader 缓存多个客户端请求,然后将这一批日志批量发送给 Follower。Batch的好处是减少的通讯成本。
|
||||
1. **流水线(Pipeline)。**Leader本地增加一个变量(称为NextIndex),每次发送一个Batch后,更新NextIndex记录下一个Batch的位置,然后不等待Follower返回,马上发送下一个Batch。如果网络出现问题,Leader重新调整NextIndex,再次发送Batch。当然,这个优化策略的前提是网络基本稳定。
|
||||
1. **并行追加日志(Append Log Parallelly)。**Leader将Batch发送给Follower的同时,并发执行本地的Append操作。因为Append是磁盘操作,开销相对较大,而标准流程中Follower与Leader的Append是先后执行的,当然耗时更长。改为并行就可以减少部分开销。当然,这时Committed Entry的判断规则也要调整。在并行操作下,即使Leader没有Append成功,只要有半数以上的Follower节点Append成功,那就依然可以视为一个Committed Entry,Entry可以被Apply。
|
||||
1. **异步应用日志(Asynchronous Apply)。**Apply并不是提交成功的必要条件,任何处于Committed状态的Log Entry都确保是不会丢失的。Apply仅仅是为了保证状态能够在下次被正确地读取到,但多数情况下,提交的数据不会马上就被读取。因此,Apply是可以转为异步执行的,同时读操作配合改造。
|
||||
|
||||
其实,Raft算法的这四项优化并不是TiDB独有的,CockroachDB和一些Raft库也做了类似的优化。比如,SOFA-JRaft也实现了Batch和Pipeline优化。
|
||||
|
||||
不知道你有没有听说过etcd,它是最早的、生产级的Raft协议开源实现,TiDB和CockroachDB都借鉴了它的设计。甚至可以说,它们选择Raft就是因为etcd提供了可靠的工程实现,而Paxos则没有同样可靠的工程实现。既然是开源,为啥不直接用呢?因为etcd是单Raft组,写入性能受限。所以,TiDB和CockroachDB都改造成多个Raft组,这个设计被称为Multi Raft,所有采用Raft协议的分布式数据库都是Multi Raft。这种设计,可以让多组并行,一定程度上规避了Raft的性能缺陷。
|
||||
|
||||
同时,Raft组的大小,也就是分片的大小也很重要,越小的分片,事务阻塞的概率就越低。TiDB的默认分片大小是96M,CockroachDB的分片不超过512M。那么,TiDB的分片更小,就是更好的设计吗?也未必,因为分片过小又会增加扫描操作的成本,这又是另一个权衡点了。
|
||||
|
||||
## 小结
|
||||
|
||||
好了,今天的内容就到这里。我们一起回顾下这节课的重点。
|
||||
|
||||
1. 分片元数据的存储是分布式数据库的关键设计,要满足性能和高可靠两方面的要求。静态分片相对简单,可以直接通过多副本分散部署的方式实现。
|
||||
1. 动态分片,满足高可靠的同时还要考虑元数据的多副本一致性,必须选择合适的复制协议。如果搭建独立的、小规模元数据集群,则可以使用Paxos或Raft等协议,传播特点是广播。如果元数据存在工作节点上,数量较多则可以考虑Gossip协议,传播特点是谣言传播。虽然Gossip是最终一致性,但通过一些寻址过程中的巧妙设计,也可以满足分布式数据的强一致性要求。
|
||||
1. Paxos和Raft是广泛使用的复制协议,也称为共识算法,都是通过投票方式动态选主,可以保证高可靠和多副本的一致性。Raft算法有“顺序投票”的约束,可能出现不必要的阻塞,带来额外的损耗,性能略差于Paxos。但是,etcd提供了优秀的工程实现,促进了Raft更广泛的使用,而etcd的出现又有Raft算法易于理解的内因。
|
||||
1. 分布式数据库产品都对Raft做了一定的优化,另外采用Multi Raft设计实现多组并行,再通过控制分片大小,降低事务阻塞概率,提升整体性能。
|
||||
|
||||
讲了这么多,回到我们最开始的问题,为什么有时候Paxos不是最佳选择呢?一是架构设计方面的原因,看参与复制的节点规模,规模太大就不适合采用Paxos,同样也不适用其他的共识算法。二是工程实现方面的原因,在适用共识算法的场景下,选择Raft还是Paxos呢?因为Paxos没有一个高质量的开源实现,而Raft则有etcd这个不错的工程实现,所以Raft得到了更广泛的使用。这里的深层原因还是Paxos算法本身过于复杂,直到现在,实现Raft协议的开源项目也要比Paoxs更多、更稳定。
|
||||
|
||||
有关分片元数据的存储,在我看来,TiDB和CockroachDB的处理方式都很优雅,但是TiDB的方案仍然建立在PD这个中心点上,对集群的整体扩展性,对于主副本跨机房、跨地域部署,有一定的局限性。
|
||||
|
||||
关于Raft的优化方法,大的思路就是并行和异步化,其实这也是整个分布式系统中常常采用的方法,在第10讲原子协议的优化中我们还会看到类似的案例。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/1a/6e/1a8c2e11b0072edd80c3bd3e5f4dca6e.jpg" alt="">
|
||||
|
||||
## 思考题
|
||||
|
||||
最后是今天的思考题时间。我们在[第1讲](https://time.geekbang.org/column/article/271373)就提到过分布式数据库具备海量存储能力,那么你猜,这个海量有上限吗?或者说,你觉得分布式数据库的存储容量会受到哪些因素的制约呢?欢迎你在评论区留言和我一起讨论,我会在答疑篇回复这个问题。
|
||||
|
||||
你是不是也经常听到身边的朋友讨论数据复制的相关问题呢,而且得出的结论有可能是错的?如果有的话,希望你能把今天这一讲分享给他/她,我们一起来正确地理解分布式数据库的数据复制是怎么一回事。
|
||||
95
极客时间专栏/geek/分布式数据库30讲/基础篇/08 | 基础篇大串讲:重难点回顾+思考题答疑+知识全景图.md
Normal file
95
极客时间专栏/geek/分布式数据库30讲/基础篇/08 | 基础篇大串讲:重难点回顾+思考题答疑+知识全景图.md
Normal file
@@ -0,0 +1,95 @@
|
||||
<audio id="audio" title="08 | 基础篇大串讲:重难点回顾+思考题答疑+知识全景图" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f9/eb/f974040af518fd15d1aea7ab2cc66feb.mp3"></audio>
|
||||
|
||||
你好,我是王磊,你也可以叫我Ivan。
|
||||
|
||||
这一讲是我们课程的答疑篇,我会集中讨论前7讲布置的思考题,以及留言区中大家关注的一些内容。
|
||||
|
||||
## 第1讲:分布式数据库的定义
|
||||
|
||||
在[第1讲](https://time.geekbang.org/column/article/271373)中,我们通过层层递进式的分析,给这门课程要讨论的“分布式数据库”下了一个定义:分布式数据库是服务于写多读少、低延时、海量并发OLTP场景的,具有海量数据存储能力和高可靠性的关系型数据库。在“内部构成”这一节,我们还着重讨论了几种不属于分布式数据库的解决方案。
|
||||
|
||||
在这一讲的思考题部分,我们聊到了Aurora,我说“Aurora和这里说的分布式数据库还是有明显差别的”,想看看大家的理解。在留言中,我看到有些同学是持不同观点的,理由是Aurora也基于分布式存储的。
|
||||
|
||||
那么,为什么我说它不是分布式数据库呢?主要原因就是Aurora依然是不支持写入能力的水平扩展。
|
||||
|
||||
Aurora是亚马逊推出的云原生数据库,它采用计算与存储分离的思想,计算能力垂直扩展,存储能力水平扩展。究其原因,它的存储系统是直接架设在自家的分布式存储系统(S3)之上的;而计算节点仍然是单节点,所以是垂直扩展。当然Aurora也像MySQL一样是支持一写多读的,根据亚马逊的官方说明,可以配置15个备节点来分流读操作的压力。由于Aurora的元数据会缓存在主节点上的,在发生变更时,主备同步数据有一个小的延迟(小于100毫秒),这就造成备节点不能承接写入功能,读也不能保证严格的数据一致性。
|
||||
|
||||
我们在定义中强调了海量并发和写多读少,这其实就是要求分布式数据库的写入能力必须是可水平扩展的。
|
||||
|
||||
“开心哥”的留言中,提到了Aurora是不能支持多写的,准确地抓住了它与NewSQL的重要差别。而“南国”同学的留言中还提到了Aurora的论文。这篇论文是2017年,亚马逊在SIGMOD上发表的,论文题目叫做”[Amazon Aurora: Design Considerations for High Throughput Cloud-Native Relational Databases](https://media.amazonwebservices.com/blog/2017/aurora-design-considerations-paper.pdf)”,其中披露了系统架构的设计细节,推荐有兴趣的同学阅读。其实阅读顶会论文是非常不错的学习方法,给“南国”同学点赞,希望大家也尝试一下。
|
||||
|
||||
最后,“xy”同学的留言还提到了另外两款同架构的产品,阿里polarDB,腾讯CynosDB,说明“xy”同学很关注对系统的横向比较,这也是非常好的学习习惯。我这里再补充一点,华为的Taurus也采用了类似Aurora的架构。
|
||||
|
||||
## 第2讲:数据一致性
|
||||
|
||||
[第2讲](https://time.geekbang.org/column/article/272104)中,我们首先明确了强一致性包含数据一致性和事务一致性两个方面,而后展开介绍了数据一致性。我们的讲解方式是先给出一个分析框架,也就是状态和操作双视角,并从状态视角引出了最终一致性这个概念。而后,我们在最终一致性的基础上介绍了5种不同强度的一致性模型,其中线性一致性和因果一致性是分布式数据库中普遍应用的。
|
||||
|
||||
思考题部分则是“你觉得Paxos这个一致性协议和数据一致性又是什么关系呢?”
|
||||
|
||||
这个答案嘛,很显然它们是不同的概念。可为什么不同的概念,都叫做**一致性**呢?就像“峰”同学说的,这个问题其实是翻译造成的。数据一致性对应是Consistency,而一致性协议对应的则是Consensus,这个单词更多时候被翻译成共识,就是我们常说的共识算法。
|
||||
|
||||
我认为,Paxos本质上是一种复制协议,约定了副本之间的同步策略,就像我们谈到的最终一致性,同样也只是描述了副本之间同步情况。再看看我们具体介绍的5个数据一致性模型,它们都在多副本的基础上又约定了读写策略,所以这两点都是一致性模型(Consistency Model)必不可少的内容。
|
||||
|
||||
我在留言中发现有的同学对Paxos这样的共识算法认识很深刻,谈了多副本的一致性,讲得很好,但是会忽略了读写策略的作用。“chenchukun”和“tt”同学的留言则抓住了这两个点,点赞。
|
||||
|
||||
## 第3讲:事务一致性
|
||||
|
||||
[第3讲](https://time.geekbang.org/column/article/272999)谈的事务一致性也是强一致性的组成部分,它具体又细化为ACID四个特性,其中的一致性比较宽泛,持久性的实现机制比较稳定,而原子性在分布式架构下面临挑战,最后的隔离性则非常复杂。即使在单体数据库下,工业界也没找到公认的处理隔离性问题的完美方法,很难实现最高级别的可串行化。所以,在分布式架构下,多数产品依然需要在性能与正确性之间进行权衡。
|
||||
|
||||
关于原子性和隔离性,我们还有比较多的篇幅展开讨论,所以课程的最后我留了一道关于持久性的思考题,就是预写日志(WAL)写成功,但是数据表写失败,要怎么处理?
|
||||
|
||||
在留言中,我发现很多同学都对WAL有深刻的认识,也都了解基于日志恢复数据的运作原理。其实,我这个问题是想让大家思考,联机写入的那一刻,除了记录WAL,数据库还干了什么。这也是一个与WAL有关的设计,也很有意思。
|
||||
|
||||
事实上,对大多数的数据库来说,实时写入数据时,并不是真的将数据写入数据表在磁盘中的对应文件里,因为数据表的组织形式复杂,不像WAL那样只是在文件尾部追加,所以I/O操作的延迟太长。因此,写入过程往往是这样的,记录WAL日志,同时将数据写入内存,两者都成功就返回客户端了。这些内存中的数据,在Oracle和MySQL中都被称为脏页,达到一定比例时会批量写入磁盘。而NewSQL所采用的LSM-Tree存储模型也是大致的思路,只不过在磁盘的数据组织上不同。
|
||||
|
||||
写入内存和WAL这两个操作构成了一个事务,必须一起成功或失败。
|
||||
|
||||
## 第4讲:两种架构风格
|
||||
|
||||
[第4讲](https://time.geekbang.org/column/article/274200)我们谈了分布式数据库的两种架构风格NewSQL和PGXC。PGXC是从代理中间件演化而来,以单体数据库作为数据节点,它的优势是工程实现更稳定。NewSQL则是以分布式键值系统为基础,引入了很多新技术,这些技术都会在我们的课程中逐步介绍。NewSQL的代表系统是Google的Spanner,而它的优势就是架构的先进性。
|
||||
|
||||
其实关于架构风格的讨论,往往是百家争鸣,各持观点,所以我们的思考题也是一个开放性话题,请大家聊聊自己熟悉的分布式数据库,或者其他分布式系统的架构。
|
||||
|
||||
在留言区,“xy”和“赵见跃”同学都提到了TDSQL,它是不是也属于PGXC风格呢?我认为目前腾讯输出的TDSQL还不是典型的PGXC,因为它没有全局时钟,也没有等效的设计去解决全局一致性问题。当然,说它不是,我也是有点纠结的,在2019年TDSQL的技术演讲中,腾讯的研发人员深入地分析了缺失全局时钟带来的一致性问题,同时也提及了正在进行的技术尝试。所以,我相信TDSQL很快会在新版本中增加类似的特性。
|
||||
|
||||
“南国”同学还提出了一个新问题:NewSQL与PGXC的界限似乎很模糊,是不是差别就在存储层面,NewSQL只能存储,而PGXC是完整的数据库呢?我认为这只是一个表象,最关键的差异其实是分片设计,或者说是两种架构对数据组织形式上的根本差别。PGXC的数据是相对固定的,而NewSQL的数据是能够更加灵活移动的,移动意味着解锁了数据与节点的关系,有点像灵魂和躯体的关系。如果灵魂不被限制在一个躯体里,那是不是就可以实现永生。解锁了数据与节点的依赖关系,系统也更加鲁棒。总的来说,我认为能够适应变化,在各种意外情况下,都能生存下来,这是设计分布式系统的核心思想。
|
||||
|
||||
## 第5讲:全局时钟
|
||||
|
||||
[第5讲](https://time.geekbang.org/column/article/274908),我们介绍了全局时钟的不同实现方式,包括物理时钟和逻辑时钟两种方式,物理时钟的难点首先是要做到足够高的精度,其次是在使用时如何处理时钟误差,学术一点的说法叫做时钟的置信区间。逻辑时钟实际上是混合逻辑时钟,还是会引入物理时钟作为参考,但主要通过逻辑控制来保证时钟的单调递增。有同学问是不是可以不用物理时钟,我要说的是,对于多时间源是不行的,因为这样会造成不相关事件的时钟偏差太大,也就是偏序拼接的全序失真太大。如果是单时间源的混合逻辑时钟,它的好处是不用处理误差,简化了其他模块的设计。而HLC这样多时间源的混合逻辑时钟,则依然有时钟误差的问题。
|
||||
|
||||
这一讲的思考题是让大家思考一下“时间对于分布式数据库的影响是什么?”我发现大家的留言对这个问题的讨论并不多。其实,时间在很多分布式系统都是存在的,比如HBase对于各节点的时钟偏移也是有限制,只不过它的容忍度更高,可以达到几十秒。而在分布式数据库中与时间有关的功能主要体现在事务并发控制,比如MVCC、读写冲突。既然留言讨论不多,我这里就先不做点评,卖个关子,在第11讲、第12讲中我们再来详细聊聊。
|
||||
|
||||
## 第6讲:数据分片
|
||||
|
||||
[第6讲](https://time.geekbang.org/column/article/275696),我们介绍了分布式数据库中一个非常重要的概念“分片”。分片机制的两个关键点是分片策略和分片调度机制。分片策略包括Hash和Range,调度机制则包括静态和动态两种。分片机制的实现和架构有很大的关系,PGXC架构基本上都是静态分片,是以Hash分片为主,有的产品也同时支持Range分片。关于NewSQL架构,我们主要介绍了最有代表性的动态Range分片。
|
||||
|
||||
这一讲的思考题,就是在问分片元数据的存储方案。
|
||||
|
||||
分析这个问题,首先要看元数据会不会变更,比如静态分片就不会变更,那么就可以把它复制多份部署在所有工作节点上,如果会变更,那就要考虑变更带来的多副本一致性问题,这里其实是和后面的07讲相呼应的。现在读完07讲,你自然应该知道,如果是少数节点集中存储元数据,那么可以采用Paxos协议保证一致性。如果是P2P架构,因为节点规模太大,那就适合采用Gossip协议。设计的权衡点主要是在于节点规模大小对传播效率的影响。
|
||||
|
||||
“开心哥”和“真名不叫黄金”两位同学都回答其中的一种情况,就是基于etcd或PD(基于etcd)来存储元数据,而etcd是Raft协议的开源实现。
|
||||
|
||||
## 第7讲:数据复制
|
||||
|
||||
[第7讲](https://time.geekbang.org/column/article/277028),我们讨论的话题是数据复制,这和分片一样是非常基础和重要的内容。这一讲我们介绍了两个知识点,其中第一个就是分片元数据的存储方案,刚刚我们已经说过了,第二个知识点是数据复制的效率问题。Raft由于顺序投票的限制,在复制效率上比Paxos稍差。但是因为Raft有高质量的开源实现项目etcd,而Paxos因为算法复杂没有稳定的开源能实现,所有TiDB和CockroachDB还是选择了Raft协议。同时,TiDB和CockroachDB采用了Multi Raft的方式,让多分片并行处理提升性能。两者在Raft协议实现上也进行了若干改进。这些改进思路很有普适性,一些独立的Raft项目也同样实现了,比如SOFA-JRaft。
|
||||
|
||||
这一讲的思考题,我们讨论的是分布式数据库的存储上限。你一定有点疑惑,既然分布式数据库是一个水平扩展的系统,可以不断地增加节点。那么为什么还有存储上限呢?事实上,不仅分布式数据库,绝大多数分布式存储系统都是有上限的。因为有了这个限制,可以简化系统架构设计,而这个上限当然也是一个很大的数值,能够满足绝大多数业务场景的需求。
|
||||
|
||||
以CockroachDB为例,它的存储容量大致是4EB,而这个限制是由元数据的存储方式决定的。
|
||||
|
||||
在CockroachDB中存储分片元数据的数据结构叫做Meta ranges,它是一个两层索引结构,第一层Meta1存储了第二层Meta2的地址,第二层Meta2则指向了具体分片。每个节点会保存Meta1的定位,而且Meta1是不会分拆的,这样就更好的稳定性。Meta1和Meta2的长度都是18位,所以CockroachDB中最多只能有2^36个分片。CockroachDB默认分片初始大小是64M,那么可以算出一个总存储量是4EB,2^36*64M。从这个意义上说,CockroachDB的最大存储容量是4EB。当然,如果分片增大整体容量还会增加,但第6讲我们介绍过分片过大是有副作用的,所以不能无限制增加,系统的容量还是有上限的。
|
||||
|
||||
## 小结
|
||||
|
||||
最后,要特别感谢“Monday”同学,他建议我们增加一张分布式数据库的全景图,让知识的组织更加系统。我觉得这是个好主意,和编辑商量了一下,最后决定在每个答疑篇都会增量补充这个全景图,在最后的第30讲大家就能看到完整的全景图了。这样安排还有一个好处,就是帮助大家阶段性地复习前面课程。
|
||||
|
||||
## 分布式数据全景图1/4
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/fb/61/fb98977aea0413fddbe477643f1f3661.jpg" alt="">
|
||||
|
||||
如果你对今天的内容有任何疑问,欢迎在评论区留言和我一起讨论。要是你身边的朋友也对分布式数据库这个话题感兴趣,你也可以把今天这一讲分享给他,我们一起讨论。
|
||||
|
||||
## 学习资料
|
||||
|
||||
Alexandre Verbitski et al.: [**Amazon Aurora: Design Considerations for High Throughput Cloud-Native Relational Databases**](https://media.amazonwebservices.com/blog/2017/aurora-design-considerations-paper.pdf)
|
||||
Reference in New Issue
Block a user