2021-06-01

七、分布式数据库之基础和架构

 

1、定义

 

外部特性上看,分布式数据库是服务于写多读少、低延时、海量并发 OLTP 场景的,具备海量数据存储能力和高可靠性关系型数据库

 

传统上,银行通过两种方法配合来实现高可靠。

 

  • 第一种还是采购小型机和大型机,因为它们的稳定性优于 x86 服务器。
  • 第二种是引入专业存储方案,例如 EMC 的 Symmetrix 远程镜像软件(Symmetrix Remote Data Facility, SRDF)。数据库采用主备模式,在高端共享存储上保存数据库文件和日志,使数据库近似于无状态化。主库一旦出现问题,备库启动并加载共享存储的文件,继续提供服务。

 

另外,从内部视角上看,为了应对海量存储和海量并发,真正的分布式数据库出现之前的方案可以分为如下几类(分布式数据库的内部架构见第2小节):

 

1.1、客户端组件 + 单体数据库

 

典型产品是 Sharding-JDBC。其作为应用系统的一部分,对业务侵入比较深。

 

 

1.2、代理中间件 + 单体数据库

 

以独立中间件的方式,管理数据规则和路由规则,以独立进程存在,与业务应用层和单体数据库相隔离,减少了对应用的影响。典型产品是MyCat

 

 

1.3、单元化架构 + 单体数据库

 

单元化架构是对业务应用系统的彻底重构,应用系统被拆分成若干实例,配置独立的单体数据库,让每个实例管理一定范围的数据。例如对于银行贷款系统,可以为每个支行搭建独立的应用实例,管理支行各自的用户,当出现跨支行业务时,由应用层代码通过分布式事务组件保证事务的 ACID 特性。

 

 

类似于在数据存储层分库的基础上对服务层也作了拆分,好像微服务。和微服务的不同是这个拆分的服务是同一类性的,而且有状态。在更高的视角上看,就是计算和存储分层,但是仍然是驻在一起

 

1.4、小结

 

 

2、分布式数据库架构风格

 

总的来说,分布式数据库大多可以分为两种架构风格,一种是 NewSQL,它的代表系统是 Google Spanner;另一种是从单体数据库中间件基础上演进出来的,被称为 Prxoy 风格,没有公认的代表系统。对于Prxoy 风格,原文使用PostgreSQL-XC作为代表,简称PGXC

 

2.1、数据库基本架构

 

 

2.2、PGXC:单体数据库的自然演进

 

要解决写入性能不足的问题,最简单直接的办法就是分库分表。

 

分库分表方案就是在多个单体数据库之前增加代理节点,本质上是增加了 SQL 路由功能。这样,代理节点首先解析客户端请求,再根据数据的分布情况,将请求转发到对应的单体数据库。

 

 

  • 分片信息就是数据分布情况,是区别于编目数据的一种元数据。不过考虑到分片信息也存在多副本的一致性的问题,大多数情况下它会独立出来。
  • 在代理节点增加分布式事务组件。
  • 代理节点要增强查询计算能力,支持跨多个单体数据库的查询。

 

随着分布式事务和跨节点查询等功能的加入,代理节点已经不再只是简单的路由功能,更多时候会被称为协调节点。

 

 

再加上全局时钟,进演进成了PGXC风格

 

 

协调节点与数据节点,实现了一定程度上的计算与存储分离,这也是所有分布式数据库的一个架构基调。但是,因为 PGXC 的数据节点本身就是完整的单体数据库,所以也具备很强的计算能力。

 

2.3、NewSQL:革命性的新架构

 

NewSQL 也叫原生分布式数据库。

 

NewSQL 的基础是 NoSQL,更具体地说,是类似 BigTable 的分布式键值(K/V)系统。分布式键值系统选择做了一个减法,完全放弃了数据库事务处理能力,然后将重点放在对存储和写入能力的扩展上,这个能力扩展的基础就是分片。引入分片的另一个好处是,系统能够以更小的粒度调度数据,实现各节点上的存储平衡和访问负载平衡。

 

NewSQL 还有两个重要的革新,分别出现在高可靠机制和存储引擎的设计上。

 

高可靠机制的变化在于,放弃了粒度更大的主从复制,转而以分片为单位采用 Paxos 或 Raft 等共识算法。这样,NewSQL 就实现了更小粒度的高可靠单元,获得了更高的系统整体可靠性。存储引擎层面,则是使用 LSM-Tree 模型替换 B+ Tree 模型,大幅提升了写入性能。

 

 

2.4、小结

 

 

3、分片

 

分片是一种水平切分数据表的方式,它是数据记录的集合,也是数据表的组成单位。

 

分布式数据库的分片与单体数据库的分区非常相似区别在于:分区虽然可以将数据表按照策略切分成多个数据文件,但这些文件仍然存储在单节点上;而分片则可以进一步根据特定规则将切分好的文件分布到多个节点上,从而实现更强大的存储和计算能力。

 

3.1、分片的种类

 

从以下两个维度进行区分:

 

  • 分片策略:主要有 Hash(哈希)和 Range(范围)两种。你可能还听到过 Key 和 List,其实 Key 和 List 可以看作是 Hash 和 Range 的特殊情况。
  • 分片的调度机制:分为静态与动态两种。静态意味着分片在节点上的分布基本是固定的,即使移动也需要人工的介入;动态则是指通过调度管理器基于算法在各节点之间自动地移动分片。

 

 

3.2、HASH分片

 

参见《分布式系统——分布式数据存储》。

 

本质上,Hash 分片是一种静态分片方式,必须在设计之初约定分片的最大规模。同时,因为 Hash 函数已经过滤掉了业务属性,也很难解决访问业务热点问题。

 

3.3、Range 静态分片

 

与 Hash 分片不同,Range 分片的特点恰恰是能够加入对于业务的预估。例如,我们用“Location”作为关键字进行分片时,不是以统一的行政级别为标准。因为注册地在北京、上海的用户更多,所以这两个区域可以按照区县设置分片,而海外用户较少,可以按国家设置为分片。这样,分片间的数据更加平衡。

 

相对 Hash 分片,Range 分片的适用范围更加广泛。其中一个非常重要的原因是,Range 分片可以更高效地扫描数据记录,而 Hash 分片由于数据被打散,扫描操作的 I/O 开销更大

 

PGXC 同时支持两种分片方式,但 Hash 分片仍是主流。

 

3.4、Range 动态分片

 

NewSQL 的 Range 分片,多数是用主键作为关键字来分片的,当然主键可以是系统自动生成的,也可以是用户指定的。既然提供了用户指定主键的方式,那么理论上可以通过设定主键的产生规则,控制数据流向哪个分片。但是,主键必须保证唯一性,甚至是单调递增的,导致这种控制就会比较复杂,使用成本较高。所以,我们基本可以认为,分片是一个系统自动处理的过程,用户是感知不到的。这样做的好处显然是提升了系统的易用性

 

动态分片的两个特征:

 

3.4.1、分片可以自动完成分裂与合并

 

当单个分片的数据量超过设定值时,分片可以一分为二,这样就可以保证每个分片的数据量较为均衡。多个数据量较少的分片,会在一定的周期内被合并为一个分片。

 

3.4.2、可以根据访问压力调度分片

 

系统之所以尽量维持分片之间,以及节点间的数据量均衡,是因为可以更大概率地将访问压力分散到各个节点上。

 

3.5、分片与高可靠的关系

 

高可靠是分布式数据库的重要特性,分片是数据记录的最小组织单位,也必须是高可靠的。

 

NewSQL 与 PGXC 的区别在于,对于 NewSQL 来说,分片是高可靠的最小单元;而对于 PGXC,分片的高可靠要依附于节点的高可靠。

 

NewSQL 的实现方式是复制组(Group)。在产品层面,通常由一个主副本和若干个副本组成,通过 Raft 或 Paxos 等共识算法完成数据同步,称为 Raft Group 或 Paxos Group,所以我们简称这种方式为 Group。因为不相关的数据记录会被并发操作,所以同一时刻有多个 Group 在工作。因此,NewSQL 通常支持 Multi Raft Group 或者 Multi Paxos Group。

 

PGXC 的最小高可靠单元由一个主节点和多个备节点组成,我们借用 TDSQL 中的术语,将其称为 Set。一个 PGXC 是由多个 Set 组成。Set 的主备节点间复制,多数采用半同步复制,平衡可靠性和性能。这意味着,所有分片的主副本必须运行在 Set 的主节点上。

 

从架构设计角度看,Group 比 Set 更具优势,原因主要有两个方面。首先,Group 的高可靠单元更小,出现故障时影响的范围就更小,系统整体的可靠性就更高。其次,在主机房范围内,Group 的主副本可以在所有节点上运行,资源可以得到最大化使用,而 Set 模式下,占大多数的备节点是不提供有效服务的,资源白白浪费掉。

 

3.6、小结

 

 

4、数据复制

 

4.1、分片元数据的存储

 

在任何一个分布式存储系统中,收到客户端请求后,承担路由功能的节点首先要访问分片元数据(简称元数据),确定分片对应的节点,然后才能访问真正的数据。这里说的元数据,一般会包括分片的数据范围、数据量、读写流量和分片副本处于哪些物理节点,以及副本状态等信息。

 

从存储的角度看,元数据也是数据,但特别之处在于每一个请求都要访问它,所以元数据的存储很容易成为整个系统的性能瓶颈和高可靠性的短板。如果系统支持动态分片,那么分片要自动地分拆、合并,还会在节点间来回移动。这样,元数据就处在不断变化中,又带来了多副本一致性(Consensus)的问题。

 

4.1.1、静态分片

 

最简单的情况是静态分片。我们可以忽略元数据变动的问题,只要把元数据复制多份放在对应的工作节点上就可以了,这样同时兼顾了性能和高可靠。

 

即使协调节点是工作节点,随着集群规模扩展,会导致元数据副本过多,但由于哈希分片基本上就是静态分片,也就不用考虑多副本一致性的问题。

 

如果要更新分片信息,当副本数量过多,数据同步的代价太大了。所以对于动态分片,通常是不会在有工作负载的节点上存放元数据的。所以也会专门给元数据搞一个小规模的集群,用 Paxos 协议复制数据。

 

TiDB 大致就是这个思路。

 

4.1.2、TiDB:无服务状态(动态分片)

 

在 TiDB 架构中,TiKV 节点是实际存储分片数据的节点,而元数据则由 Placement Driver 节点管理,简称为PD。

 

在 PD 与 TiKV 的通讯过程中,PD 完全是被动的一方。TiKV 节点定期主动向 PD 报送心跳,分片的元数据信息也就随着心跳一起报送,而 PD 会将分片调度指令放在心跳的返回信息中。等到 TiKV 下次报送心跳时,PD 就能了解到调度的执行情况。

 

由于每次 TiKV 的心跳中包含了全量的分片元数据,PD 甚至可以不落盘任何分片元数据,完全做成一个无状态服务。

 

 

上图可以看到,PD 仍然是一个单点,也就是说这个方案还是一个中心化的设计思路,可能存在性能方面的问题。

 

4.1.3、CockroachDB:去中心化(动态分片)

 

CockroachDB 的解决方案是使用 Gossip 协议。为什么不用 Paxos 协议呢?这是因为 Paxos 协议本质上是一种广播机制,也就是由一个中心节点向其他节点发送消息。当节点数量较多时,通讯成本就很高。

 

CockroachDB 采用了 P2P 架构,每个节点都要保存完整的元数据,这样节点规模就非常大,当然也就不适用广播机制。即CockroachDB 在元数据的存储上采取了最终一致性。

 

但分布式数据库是强一致性的,CockroachDB 真的是基于“最终一致性”的元数据实现了强一致性的分布式数据库。

 

 

  1. 节点 A 接到客户端的 SQL 请求,要查询数据表 T1 的记录,根据主键范围确定记录可能在分片 R1 上,而本地元数据显示 R1 存储在节点 B 上。
  2. 节点 A 向节点 B 发送请求。很不幸,节点 A 的元数据已经过时,R1 已经重新分配到节点 C。
  3. 此时节点 B 会回复给节点 A 一个非常重要的信息,R1 存储在节点 C。
  4. 节点 A 得到该信息后,向节点 C 再次发起查询请求,这次运气很好 R1 确实在节点 C。
  5. 节点 A 收到节点 C 返回的 R1。
  6. 节点 A 向客户端返回 R1 上的记录,同时会更新本地元数据。

 

复制协议的选择和数据副本数量有很大关系:如果副本少,参与节点少,可以采用广播方式,也就是 Paxos、Raft 等协议;如果副本多,节点多,那就更适合采用 Gossip 协议。

 

4.2、复制效率

 

Paxos 和 Raft比较的话,在复制效率上 Raft 会差一些,主要原因就是 Raft 必须“顺序投票”,不允许日志中出现空洞。

 

完整的 Raft 日志复制过程:

 

  1. Leader 收到客户端的请求。
  2. Leader 将请求内容(即 Log Entry)追加(Append)到本地的 Log。
  3. Leader 将 Log Entry 发送给其他的 Follower。
  4. Leader 等待 Follower 的结果,如果大多数节点提交了这个 Log,那么这个 Log Entry 就是 Committed Entry,Leader 就可以将它应用(Apply)到本地的状态机。
  5. Leader 返回客户端提交成功。
  6. Leader 继续处理下一次请求。

 

上面描述的是单个事务的运行情况,多事务并行操作情况如下:

 

 

定这个 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 读操作也更加容易。

 

日志比对过程如下:

 

  • 首先,领导者通过日志复制 RPC 的一致性检查,找到跟随者节点上,与自己相同日志项的最大索引值。也就是说,这个索引值之前的日志,领导者和跟随者是一致的,之后的日志是不一致的了。
  • 然后,领导者强制跟随者更新覆盖的不一致日志项,实现日志的一致。
  • 领导者从来不会覆盖或者删除自己的日志。

 

4.2.1、Raft 的性能优化方法(TiDB)

 

  • 批操作(Batch。Leader 缓存多个客户端请求,然后将这一批日志批量发送给 Follower。Batch 的好处是减少的通讯成本。
  • 流水线(Pipeline。Leader 本地增加一个变量(称为 NextIndex),每次发送一个 Batch 后,更新 NextIndex 记录下一个 Batch 的位置,然后不等待 Follower 返回,马上发送下一个 Batch。如果网络出现问题,Leader 重新调整 NextIndex,再次发送 Batch。当然,这个优化策略的前提是网络基本稳定。
  • 并行追加日志(Append Log Parallelly。Leader 将 Batch 发送给 Follower 的同时,并发执行本地的 Append 操作。因为 Append 是磁盘操作,开销相对较大,而标准流程中 Follower 与 Leader 的 Append 是先后执行的,当然耗时更长。改为并行就可以减少部分开销。当然,这时 Committed Entry 的判断规则也要调整。在并行操作下,即使 Leader 没有 Append 成功,只要有半数以上的 Follower 节点 Append 成功,那就依然可以视为一个 Committed Entry,Entry 可以被 Apply。
  • 异步应用日志(Asynchronous Apply。Apply 并不是提交成功的必要条件,任何处于 Committed 状态的 Log Entry 都确保是不会丢失的。Apply 仅仅是为了保证状态能够在下次被正确地读取到,但多数情况下,提交的数据不会马上就被读取。因此,Apply 是可以转为异步执行的,同时读操作配合改造。

 

Etcd

 

它是最早的、生产级的 Raft 协议开源实现,但etcd 是单 Raft 组,写入性能受限。所以,TiDB 和 CockroachDB 都改造成多个 Raft 组,这个设计被称为 Multi Raft,所有采用 Raft 协议的分布式数据库都是 Multi Raft。这种设计,可以让多组并行,一定程度上规避了 Raft 的性能缺陷。

 

Raft 组的大小,也就是分片的大小也很重要,越小的分片,事务阻塞的概率就越低。

 

4.3、小结

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>