笔者实在是不想用“云原生”这个风口浪尖上的词来形容美丽的云上数据库们,它们就像 TCP/IP,简洁但有用。市场从来不会说谎,它们一定是有过人之处的。
2014 年 10 月,亚马逊发布了 Aurora 云上数据库,开创性地在云环境中将计算节点和存储节点分离:基于云上资源的特点,将计算节点 scale up(增配),将存储节点 scale out(增加节点),实现了极佳的性能/成本平衡。Aurora 将云上关系型数据库产品推向了一个新的高度。
Aurora 提出的计算与存储分离可以说是目前数据库领域最火的方向,但是它火的原因笔者相信大多数人都认识的不对:不是因为性能强,而是因为便宜。
十年前笔者在 SAE 实习的时候,中午大家一起吃饭,组长说云计算就是云安全,这句话当然说的很对。从这句话推开,我们很容易就能找到云计算真正的商业价值在哪里:传统托管式部署,哪些资源浪费的最多,哪里就是云计算的商业价值所在。
为了满足业务波动而多采购的 CPU 和内存,可能浪费了 50%;网络安全设备,可以说 95% 以上的资源都是浪费;高端存储,这个已经不能用资源浪费来形容了,而是被云计算颠覆了:云厂商用海量的多地域多机房内廉价的 x86 服务器里面的廉价磁盘,基于软件,构建出了超级便宜、多副本、高可用的存储,唯一的问题是性能不是太好。亚马逊 S3 和阿里云 OSS 就是最佳代表,可以说这类对象存储服务,其单价已经低于本地机房的 2.5 寸 SAS 机械磁盘了,更不要说本地机房还需要另外采购昂贵的存储控制器和 SAN 交换机了。
云数据库可以算是云服务厂商最重要的产品:受众足够广,成本足够低,性能足够高。这一点很像特斯拉汽车,时至今日,特斯拉依然在疯狂地想各种办法压低生产成本,虽然在降价,但是单车毛利依然维持在 30% 以上,是 BBA 的 2-3 倍。
Aurora 和 PolarDB 的核心价值是用一种低成本的方式,制造了一个 Oracle 要高成本才能做出来的软件和服务,这才是真的“创造价值”。
计算与存储分离并不是什么“高性能”技术,而是一种“低成本”技术:关系型数据的存储引擎 InnoDB 本身就是面向低性能的磁盘而设计的,而 CPU 和内存却是越快越好、越大越好,如果还把磁盘和 MySQL 进程部署在同一台物理机内,一定会造成磁盘性能的浪费。计算与存储分离的真正价值在于大幅降低了存储的成本。
虽然说这个架构的主要价值在于便宜,但是在技术上,它也是有优势的:
它显著降低了传统 MySQL 主从同步的延迟。传统架构下,无论是语句同步还是行同步,都要等到事务提交后,才能开始同步,这就必然带来很长时间的延迟,影响应用代码的编写。而计算和存储分离之后,基于 redo log 传递的主从同步就要快得多了,从 1-2s 降低到了 100ms 以下。由于主从延迟降低,集群中从节点的个数可以提升,总体性能可以达到更高。
上一章中我们说过,在更新数据时,主节点在完成了 redo log 写入,并对内存缓存 Buffer Pool 中相应的数据页进行修改之后,就会返回成功。这个内存缓存给 Aurora 从节点的数据更新造成了不小的影响:
Aurora 的出现确实独具慧眼,但是也会被时代所局限。
在 Aurora 论文 中,开篇就提到Instead, the bottleneck moves to the network between the database tier requesting I/Os and the storage tier that performs these I/Os
。Aurora 认为网络速度会成为云数据库的瓶颈,而在它研发的 2012-2013 年也确实如此,当时万兆网卡刚刚普及,CPU 单核性能也不足,软件也没有跟上,可以说速度比千兆网卡也快不了多少,所以亚马逊又搞了神奇的技术:存储节点具有自己将 redo log 写入 ibd 文件的能力。
由于这个神奇能力的存在,Aurora 的多机之间采用传输 redo log 的方式来同步数据,并用一种今天看起来不太靠谱的协议来保证最终一致性:consul 使用的那个 gossip 协议。由于 Aurora 采用六副本技术,所以每次写入都需要发起六次不怎么快的网络 I/O,并且在其中 4 个成功以后才给客户端返回成功。Aurora 确实便宜,但是单节点的性能也确实捉鸡,这代表的就是写入性能差,进而限制了整个集群的性能上限。而且,经过比较长的时间(100ms)才能保证从从节点
上读到的数据是最新的,这会让主节点压力增大影响集群性能上限,或者让应用代码做长时间的等待,严重的会引起应用代码的执行逻辑变更,引入持久的技术债务。
那该怎么提升计算存储分离情况下的集群性能呢?我们看阿里云是怎么做的。
阿里云 RDS 集群的成本已经够低了,不需要再用计算存储分离技术降低成本了,而中国市场的用户,普遍需要高性能的 MySQL 数据库:ECS 价格太低了,如果不是运维方便和性能压力过大,谁愿意用你昂贵的数据库服务啊。
2015 年,PolarDB 开始研发,当时 25Gb RDMA 网络已经逐渐普及,所以阿里云将视角放在了网络速度之外:在 I/O 速度没有瓶颈以后,基于内核提供的 syscall 所编写的旧代码将会成为新的性能瓶颈。
站在 2023 年初回头看,阿里云的判断是非常准确的。
由于所有节点都使用了同一块“逻辑磁盘”,所以双主可写
想都不要想,一个计算存储分离的数据库集群的性能上限就是主节点的写入性能上限
。(Aurora 有多主可写数据库,对 ID 进行自动切分,使用时有一堆限制;PolarDB 也有多主可写数据库,但是更绝:每个库/表只支持绑定到一个可写节点,感情就是对多个数据库做了个逻辑聚合,还不如中间件呢。)
在主节点不接受读的情况下,主节点只承接写入操作,以及和写入操作在同一个会话 session 中的后续的读请求。
那 PolarDB 是如何提升主节点性能的呢?
主从之间并不是依靠纯属 redo log 来同步数据的,而是直接共享同一个 ibd 文件,即真正的共享磁盘。而且,基于块设备的 Raft 算法也比基于文件的 gossip 协议要快很多。
虽然对 redo log 的解析这一步在 Aurora 那边是存储做的,PolarDB 这边是主节点做的,看似增加了 CPU 消耗,但是这并不是真正的性能瓶颈所在,真正的瓶颈是网络栈和 UNIX 进程模型。看过笔者《性能之殇》系列文章的人应该都比较熟悉了,这是老生常谈了。那 PolarDB 是怎么优化的呢?
ParallelRaft 协议让 Aurora 那边需要执行六次的网络 I/O 变成了一次:只需要向 leader 节点写入成功,剩下的数据同步由 Raft 算法来执行,这和 Google Spanner 的两阶段提交优化是一个思路。
原始的 Raft 协议确实逻辑完备,实现简单,就是一个一个地协商太慢了。ParallelRaft 让收敛协商能够并行起来,加速 redo log 落入 ibd 文件的过程。
基于共享存储的低延迟优势,PolarDB 主从之间使用共享存储来同步 redo log 以刷新缓存,这一点逻辑上和 Aurora 一致,但是实际表现比较好,笔者实测主从同步时间在 20~70ms 范围内。
RDMA 存储比本地存储更快,因为减少了计算和存储争抢中断的问题:I/O 这个 CPU 不擅长的东西完全卸载给了 RDMA 网卡。同配置下 PolarDB 比标准 MySQL 的性能要高几倍。
在各种实测里面,PolarDB 在相同规格下对其他的云上数据库都拥有 2 倍的性能优势,但是它基于 RDMA 存储的特点也让它付出了两个代价:1. 硬件成本高昂 2. 扩展性有上限。
是不是感觉很熟悉?Shared-Disk 的代表 Oracle RAC 也有这两个缺点。不知道大家有没有发现,PolarDB 就是云时代的 RAC 数据库:看起来是 Shared-Disk,其实是共享缓存让它们的性能变的超强。
如图 9-7 所示是各代分布式数据库的兼容性和扩展性的对比,我们可以直观地看出 PolarDB、Aurora、Google Spanner 的区别。
一句话概括 PolarDB:利用了高性能云存储并且做了性能优化的一主多从 MySQL 集群。
一个分布式系统中,不可能完全满足①一致性、②可用性、③分区容错性。我们以一个两地三中心的数据库为例:
在一个分布式数据库系统中,到底什么是可以放弃的呢?笔者觉得可以从分布式系统带来了什么优势这个问题开始思考。
相比于单体系统,一个分布式的数据库,在一致性上进步了吗?完全没有。在可用性上进步了吗?进步了很多。在分区容错性上呢?单体系统没有分区,不需要容错。所以,结论已经一目了然了:
①和③都是分布式系统带来的新问题,只有②是进步,那就取长补短,选择牺牲可用性来解决自己引发的一致性和分区容错性两个新问题。这也正是目前常见分布式数据库系统的标准做法。
📙 高并发的哲学原理 《Philosophical Principles of High Concurrency》
Copyright © 2023 吕文翰