天天看点

数据库内核那些事|分布式数据库,挂掉两台机器会发生什么?

作者:阿里云瑶池数据库

挂一部分机器,不会丢数据、不会不可服务,是对现代数据库的一个比较基本的要求。

对于早期的单机数据库,一般使用主备架构。主备架构有很多的缺陷,并且这些缺陷是无解的。穿过主备架构里各种“优化”的名词,最后也无非是选择一碗毒药而已,这几个毒药包括:

  1. 脑裂,两个节点同时写入的冲突数据无法合并,只能丢掉一部分。想要不脑裂?那只能牺牲可用性。
  2. 同步复制,备机不可用的情况下,算不算写入成功?算,可能丢数据;不算,备机不可用==集群不可用,牺牲可用性。
  3. 异步复制,这完全躺平了,不考虑一致性。
  4. 所谓semi-sync等方案,也属于主备架构的一种。
  5. 业务自己去容错,做针对自己业务场景的对账、补偿等方案。

其实可以看出,主备架构是CAP理论做取舍的重灾区,一致性和可用性之间的关系特别矛盾。所谓一致性和可用性“兼顾”的主备方案,实际上是“兼不顾”。

最佳实践:在这个时代,但凡数据有一定的重要性,都不应该选择主备架构的产品。

分布式数据库,除了扩展性之外,解决传统数据库主备结构的容灾问题也是其主要任务。Paxos\Raft(包括其他变种协议)成为了主流选择。其共通点是,每份数据会存在三个副本,并且能够保证在一个副本挂掉的情况下,不影响可用性,并且不会出现任何一致性问题(脑裂、丢数据、丢更新等)。

本文无意去解析Paxos\Raft协议,此类文章已多如牛毛。但有个疑问是,是否一个数据库只要使用了Paxos\Raft协议,那就一定是安全稳定可靠的呢?

我们将探讨几个问题:

  1. 除了协议本身,还有什么因素影响分布式数据库的可用性?
  2. 如何计算不同架构的分布式数据库的可用性?
  3. KV层的可用性和关系型数据库的可用性是否等价?
  4. 数据库的可用性和应用的可用性是否是等价的?

从1+1=2说起

我们先从一个最简单的例子说起。假如有以下的数据库结构:

数据库内核那些事|分布式数据库,挂掉两台机器会发生什么?

请问,当挂一个节点和两个节点的情况下,可用性分别是?

显而易见,挂一台机器的情况下,可用性是100%;挂两台机器的情况下,可用性是0%,所有数据都不可用,如果这两台机器被彻底的摧毁,那代表所有数据都丢失了。

调度就是一个填图游戏

假如我们有6台机器,我们想将数据库部署在这6台机器上,进一步提升扩展性和容灾性:

  1. 为了提升扩展性,我们会对数据进行分片
  2. 每个分片需要存在三个副本
  3. 为了保证容灾,每个分片的三个副本需要分配在不同的机器上

假如我们把数据切成了12个分片,共有12*3=36份副本:

数据库内核那些事|分布式数据库,挂掉两台机器会发生什么?

下面我们来做一个填图游戏,将上面的副本填充到下面的6台机器中(需满足上面的约束,并且每台机器分配到6个副本),你能想到多少不同的填充方式:

数据库内核那些事|分布式数据库,挂掉两台机器会发生什么?

模型1,完全的随机调度

数据库内核那些事|分布式数据库,挂掉两台机器会发生什么?

我们均匀的将12个leader副本分散在集群中,每个节点分配到两个leader副本,除了这两个限定条件,分片与分片之间的调度是无关系的。

目前市面上常见的使用分布式KV+SQL的分布式数据库,一般使用的都是此类模型。

这种情况下,如果挂两台机器,我们分析下会是什么样的情况。

例如,节点1和节点2同时宕机,由于p1、p6均有两个副本在这两个节点上,因此p1和p6处于不可用的状态。

我们枚举一下可能出现的情况:

数据库内核那些事|分布式数据库,挂掉两台机器会发生什么?

一个比较直观的事实:在此模型下,任意挂两台机器,均会导致部分数据不可用。

设分片数不是12而是M,机器数不是6而是N,我们做几个简单的计算:

1. 任意挂两台机器,不可用的分片数的期望值是:

简单的推导过程:

对于特定的一个分片,挂一个副本的概率是 , 再挂一个副本的概率是。因此,特定的一个分片挂掉的概率是

M个分片中挂掉的数量乘以M便是

将M=12,N=6带入,不可用的分片数的期望值是,和我们上面列的具体表格是相符的。

2. 任意挂两台机器,完全可用或者不丢数据(不存在不可用或者丢数据的分片)的概率为:

简单的推导过程:

对于一个特定分片,其可用的概率是,M个分片均可用的概率是其M次幂。

将M=12,N=6带入,完全可用的概率是%。

3. 正常情况下,机器数越多,数据量越大,分片数越多,M和N会成一个比例,单节点上的副本数量记为m,则:。任意挂两台机器,完全可用的概率为:

以一个N=50节点的集群为例,我们看一下m的取值与可用性的概率关系:

数据库内核那些事|分布式数据库,挂掉两台机器会发生什么?

可以直观发现,可用性会随着m的增长,迅速下降。当m=1(每个机器只存一个副本的情况下),可用性最高,达到96%。

什么情况下m会变大?

如果设计数据库时,将单分片的大小限定的比较小,例如单个分片96M,那么m就容易非常大(50个96M大约只有5个G的数据,每个节点只存100G的数据,也有1000个分片了)。

模式1下,各分片(或者说各Paxos协议组)的副本分布缺乏协调,如果单节点分片数略多,只要同时挂两台或者更多机器,几乎一定会有一部分数据不可用或者丢失。

数据可用性!=业务可用性

回到上面的例子中来,12个分片中,有2.4个不可用,好像也可以嘛,毕竟挂了33%的机器嘛,是不是只影响了20%的业务?

我们需要明确的是,20%的不可用,仅仅的指数据层面的。例如我们把它想象成一个KV数据库,那么确实,对于这个KV数据库来说,“只有”20%的数据不可用。

但!数据的可用性和业务的可用性很多时候是不等价的!

第一个点显而易见,不可用的背后,一种情况是数据丢失,对于有的业务来说(例如存银行的账户余额),无论丢失的比例有多么的低,都是无法接受的。对于此类业务,丢失的概率必须无限趋近于0。

第二个点则更为隐晦。

我们知道,绝大多数业务,不会直接访问KV数据库的。一般情况下,我们会在KV之上构建一个关系型数据库。关系型数据库比KV数据库多了两层概念,表和索引。从业务角度来看,一次业务请求,会操作多个表的多个索引。

例如,业务接到某个HTTP请求后,会执行这样一段操作:

begin;
## 根据user_id上的索引查询一下用户的余额,并且这个查询还需要对主键进行一个回表(涉及到2个key)
select * from user_info where user_id=xxx; 
## 按照item_id对库存进行扣减(涉及到1个key)
update items set count=count-1 where item_id=xxx
## 向订单表orders中写入一条记录(注意,orders表还有5个二级索引,所以等于要写6个key)
insert into orders xxx;
commit;           

这次HTTP请求的处理中,一共涉及了三张表的9个索引(主键或二级索引)的9个key,并且这9个key之间没有任何的关联关系,也就是说,他们是均匀的分布在集群中的。

虽然每个key(也即key所在的分片)的可用性“高达“80%,但指数是一个可怕的东西,它会放大你所有的坏运气,9个key运气都很好,全部可用的概率只有%。

也即,从业务角度看,HTTP请求的成功率只有13%,失败率87%。87% vs 20%,这就是业务最终看到的不可用性与KV层看到的不可用性。

两个结论:

  1. 关系模型(表、索引)的引入,会指数放大KV层的不可用性
  2. 一般业务会在一次请求内操作多个表、索引、Key,会继续指数放大KV层的不可用性

模型2,分片之间存在一定的绑定关系的调度

在模型1中,m的值上升后,由于指数关系,可用性会迅速下降。如果M仅与节点数N相关,而与单节点副本数m无关,则能将可用性维持在一个比较好的数字上。

数据库内核那些事|分布式数据库,挂掉两台机器会发生什么?

在模型2中,我们将节点每三个组成一个组,每个组的每个节点,所包含的副本来自相同的分片,如上图所示。p1-p6、p7-p12各属于一个组。我们枚举一下可能出现的情况:

数据库内核那些事|分布式数据库,挂掉两台机器会发生什么?

计算下,任意挂两台机器,完全可用或者不丢数据(不存在不可用或者丢数据的分片)的概率为:

简单推导过程:

对于一个特定的组,其可用的概率是,N/3个组均可用的概率是其N/3次幂。

数据库内核那些事|分布式数据库,挂掉两台机器会发生什么?

我们可以看到,随着机器数的增加,可用性会迅速增加,20个节点的时候便能超过90%。

模型2相对于模型1,不同分片(或者说不同Paxos协议组)之间的调度有了一定的绑定关系,使其变成了仅与节点数成线性关系的量,显著的提升了可用性。

数据库内核那些事|分布式数据库,挂掉两台机器会发生什么?

注意,这里说的“组”,不是平时说的Paxos组、Raft组之类的概念,它是一种调度上的概念。一个直观的描述,节点、“组”、Paxos\Raft组、分片之间的关系:

  • 节点与“组”:3对N,N>=1,也即1个“组”包含3个节点,一个节点可能属于多个“组”
  • “组”与Paxos\Raft组:1对N,N>=1
  • Paxos\Raft组与分片:通常情况下是1对N,很多数据库实现是1对1

在有“组”概念的数据库里,它有不同的名字。例如对于PolarDB分布式版来说,他对应的是“DN”这个名字,每个DN拥有三个节点(三个进程),每个DN有一个Paxos组,每个DN(Paxos组)有若干分片。

表组&本地索引

模型2很大程度上降低了单分片故障的风险,但是,上文中我们说过,业务请求中的多个事务、多个表、多个索引,都会放大这个不可用性。对于稳定性要求特别高的业务,分布式数据库需要提供更多能力来进一步降低此类风险。

PolarDB分布式版使用表组技术来解决此类问题。其核心思路是,将同一次业务请求中涉及到的数据,尽量绑定在一起,调度到相似的位置上,这样其可用性不会受涉及到的数据量指数影响。

例如,某业务有用户这个概念,那么在PolarDB分布式版中,可以使用分区表、表组、本地索引等工具,将一个用户涉及的多个表(例如用户详情、操作日志、地址列表等)、多个记录、多个索引绑定在同一个DN上。

如我们在《PolarDB-X 数据分布解读(四)》[1] 中所说,这实际上是数据库手动模式的一个应用。数据库需要提供更多的工具来达成核心业务对稳定性和数据安全性的要求。

自建 vs 云服务

在云上,自建的分布式数据库会存在一些额外的问题。

云上自建,通常情况下,我们会购买一批的ECS,在ECS上部署分布式数据库。

这里一个很大的风险点是,ECS都是虚拟化的(除非购买独享物理机的那种超大规格),一台物理机上会虚拟化出多台ECS,而作为普通的云用户,是无法感知到ECS的调度策略的。

例如,多个节点,甚至同一份数据的多个副本所在的ECS会在一台物理机上:

数据库内核那些事|分布式数据库,挂掉两台机器会发生什么?

这种节点的物理分布,自建数据库是无法感知,也无法控制的。

此外,自建还容易出现如下问题:

  1. 同一个物理机上,多个数据库节点的IO、网络带宽争抢等问题
  2. 买到的同CPU内存规格的ECS,可能对应不同的CPU型号,性能之间存在差异

以上问题,会极大的增加云上自建分布式数据库的故障风险和概率。

通常情况下,有以下几种解决方式:

1. 三可用区部署。由于不同可用区的ECS肯定在不同的物理机上,通过数据库内部的locality等能力,可以确保对于同一个分片的三个副本不在同一台物理机上。缺点是:

a. 依然无法控制同可用区内的物理分布情况

b. 多可用区带来的响应时间的变化

2. 购买独立的物理机规格的ECS。完全自己运维物理机内部的虚拟化、隔离、节点分布等。缺点是:

a. 贵

b. 运维要求很高

c. 对于机架、交换机等依然无法控制

云厂商提供的分布式数据库服务则能很好的解决此类问题。例如云上的PolarDB分布式版服务会做到以下几点:

  1. 同一个集群的所有节点一定都在不同的物理机上、不同的机架上;
  2. 同一个集群的所有节点使用的物理机的CPU等型号都是一致的。

云上自建数据库,会面临更多的稳定性风险。云上自建分布式数据库,此类风险会指数上升。

最后

模型1由于看起来非常的干净、很容易做到分层清晰(每个分片可以自由调度,也不用考虑索引之间的位置关系),特别适合做一些数据库领域的玩具级项目。但用在关系型数据库(因为大量的表、二级索引会急剧放大这种效应)中就是徒有其表,在面临多机故障的情况下,其不出问题的概率会随着数据量和节点数的增长迅速降到0。

模型2则更适合生产级的数据库使用。如果读者要使用分布式数据库,建议仔细甄别其是否有类似模型2的调度策略。

表组、本地索引等能力也是生产级数据库不可或缺的。这些能力的合理使用, 会避免指数级放大不可用性。对于稳定性要求更高的应用,需要选择有这些能力的数据库。

现在我们可以回答文章开头的几个问题了。

1. Paxos\Raft协议是根本,协议上有问题基本跟数据安全就没啥关系了;

2. 只有协议也是万万不行的,如果缺少有绑定关系的调度策略、表组、本地索引等能力,都会极大的影响最终业务层看到的可用性;

3. 关系模型、业务对数据库的使用逻辑等,都会成指数的放大KV层的不可用性。

欢迎大家持续关注,我们会在后续的文章里,带来更多生产级数据库在设计与方案选择上的一些思考。

参考文章

[1] PolarDB-X 数据分布解读(四):透明 vs 手动:https://zhuanlan.zhihu.com/p/556788150

推荐阅读

PolarDB-X内核新版本:更精细的数据管理

三款典型国产分布式数据库的对比评测

PolarDB-X开源分布式数据库在韵达科技的应用实践

继续阅读