PolarDB是阿里云基于MySQL推出的云原生数据库(Cloud Native Database)产品,经过将数据库中计算和存储分离,多个计算节点访问同一份存储数据的方式来解决目前MySQL数据库存在的运维和扩展性问题;经过引入RDMA和SPDK等新硬件来改造传统的网络和IO协议栈来极大提高数据库性能。表明了将来数据库发展的一个方向。本系列共2篇文章,主要分析为何会出现PolarDB以及其技术实现。html
因为PolarDB并不开源,所以只能基于阿里云公开的技术资料进行解读。这些资料包括从去年下半年开始陆续在阿里云栖社区、云栖大会等场合发布的PolarDB相关资料,以及今年以来公开的PolarDB后端共享存储PolarFS相关文章。node
PolarDB出现背景mysql
MySQL云服务遇到的问题算法
首先来了解下为何会出现PolarDB。阿里云数据库团队具有国内领先的技术能力,为MySQL等数据库在国内的推广起到了很大的做用。在阿里云上也维护了很是庞大的MySQL云服务(RDS)集群,但也遇到了不少棘手的问题。举例以下:sql
实例数据量太大,单实例几个TB的数据,这样即便使用xtrabackup物理备份,也须要很长的备份时间,且备份期间写入量大的话可能致使redo日志被覆盖引发备份失败;数据库
大实例故障恢复须要重建时,耗时太长,影响服务可用性(此时存活节点也挂了,那么完蛋了)。时间长有2个缘由,一是备份须要很长时间,二是恢复的时候回放redo也须要较长时间;后端
大实例作只读扩展麻烦,由于只读实例的数据是单独一份的,因此也须要经过备份来重建;缓存
RDS实例集群很大,包括成千上万个实例,可能同时有不少实例同时在备份,会占用云服务巨大的网络和IO带宽,致使云服务不稳定;服务器
云服务通常使用云硬盘,致使数据库的性能没有物理机实例好,好比IO延时太高;网络
主库写入量大的时候,会致使主从复制延迟过大,semi-sync/半同步复制也无法完全解决,这是因为mysql基于binlog复制,须要走完整的mysql事务处理流程。
对于须要读写分离,且要求部署多个只读节点的用户,最明显的感受就是每增长一个只读实例,成本是线性增加的。
其实不只仅是阿里云RDS,网易云上的RDS服务也有数千个实例,一样遇到了相似的问题,咱们是亲身经历而非感同身受。应该说就目前的MySQL技术实现方案,要解决上述任何一个问题都不是件容易的事情,甚至有几个问题是没法避免的。
现有解决方案及不足
那么,跳出MySQL,是否有解决方案呢,分析目前业界的数据库和存储领域技术,能够发现基于共享存储是个可选的方案,所谓数据库共享存储方案指的是RDS实例(通常指一主一从的高可用实例)和只读实例共享同一份数据,这样在实例故障或只读扩展时就无需拷贝数据了,只需简单得把故障节点从新拉起来,或者新建个只读计算节点便可,省时省力更省钱。共享存储可经过快照技术(snapshot/checkpoint)和写时拷贝(copy-on-write,COW)来解决数据备份和误操做恢复问题,将所需备份的数据量分摊到较长的一段时间内,而不须要瞬时完成,这样就不会致使多实例同时备份致使网络和IO数据风暴。下图就是一个典型的数据库共享存储方案,Primary节点即数据库主节点,对外提供读写服务,Read Only节点能够是Primary的灾备节点,也能够是对外提供只读服务的节点,他们共享一份底层数据。
理想很丰满,但现实却很骨感,目前可用的共享存储方案寥寥无几,好比在Hadoop生态圈占统治地位的HDFS,以及在通用存储领域风生水起的Ceph,只是若是将其做为在线数据处理(OLTP)服务的共享存储,最终对用户呈现的性能是不可接受的。除此以外,还存在大量与现有数据库实现整合适配等问题。
PolarDB实现方案
云原生数据库
说道云原生数据库,就不得不提Aurora。其在2014年下半年发布后,轰动了整个数据库领域。Aurora对MySQL存储层进行了大刀阔斧的改造,将其拆为独立的存储节点(主要作数据块存储,数据库快照的服务器)。上层的MySQL计算节点(主要作SQL解析以及存储引擎计算的服务器)共享同一个存储节点,可在同一个共享存储上快速部署新的计算节点,高效解决服务能力扩展和服务高可用问题。基于日志即数据的思想,大大减小了计算节点和存储节点间的网络IO,进一步提高了数据库的性能。再利用存储领域成熟的快照技术,解决数据库数据备份问题。被公认为关系型数据库的将来发展方向之一。截止2018年上半年,Aurora已经实现了多个计算节点同时提供写服务的能力,继续在云原生数据库上保持领先的地位。
不难推断,在Aurora发布3年后推出的PolarDB,确定对Aurora进行了深刻的研究,并借鉴了不少技术实现方法。关于Aurora的分析,国内外,包括公司内都已进行了深刻分析,本文再也不展开描述。下面着重介绍PolarDB实现。咱们采用先存储后计算的方式,先讲清楚PolarFS共享存储的实现,再分析PolarDB计算层如何适配PolarFS。
PolarDB架构
上图为PolarFS视角看到的PolarDB实现架构。一套PolarDB至少包括3个部分,分别为最底层的共享存储,与用户交互的MySQL节点,还有用户进行系统管理的PolarCtrl。而其中PolarFS又可进一步拆分为libpfs、PolarSwitch和ChunkServer。下面进行简单说明:
MySQL节点,即图中的POLARDB,负责用户SQL解析、事务处理等数据库相关操做,扮演计算节点角色;
libpfs是一个用户空间文件系统库,提供POSIX兼容的文件操做API接口,嵌入到PolarDB负责数据库IO(File IO)接入;
PolarSwitch运行在计算节点主机(Host)上,每一个Host部署一个PolarSwitch的守护进程,其将数据库文件IO变换为块设备IO,并发送到具体的后端节点(即ChunkServer);
ChunkServer部署在存储节点上,用于处理块设备IO(Block IO)请求和节点内的存储资源分布;
PolarCtrl是系统的控制平面,PolarFS集群的控制核心,全部的计算和存储节点均部署有PolarCtrl的Agent。
PolarFS的存储组织
与大多数存储系统同样,PolarFS对存储资源也进行了多层封装和管理,PolarFS的存储层次包括:Volume、Chunk和Block,分别对应存储领域中的数据卷,数据区和数据块,在有些系统中Chunk又被成为Extent,均表示一段连续的块组成的更大的区域,做为分配的基本单位。一张图能够大体表现各层的关系:
Volume
当用户申请建立PolarDB数据库实例时,系统就会为该实例建立一个Volume(卷,本文后续将这两种表达混用),每一个卷都有多个Chunk组成,其大小就是用户指定的数据库实例大小,PolarDB支持用户建立的实例大小范围是10GB至100TB,知足绝大部分云数据库实例的容量要求。
跟其余传统的块设备同样,卷上的读写IO以512B大小对齐,对卷上同个Chunk的修改操做是原子的。固然,卷仍是块设备层面的概念,在提供给数据库实例使用前,需在卷上格式化一个PolarFS文件系统(PFS)实例,跟ext四、btrfs同样,PFS上也会在卷上存放文件系统元数据。这些元数据包括inode、directory entry和空闲块等对象。同时,PFS也是一个日志文件系统,为了实现文件系统的元数据一致性,元数据的更新会首先记录在卷上的Journal(日志)文件中,而后才更新指定的元数据。
跟传统文件系统不同的是PolarFS是个共享文件系统即一个卷会被挂载到多个计算节点上,也就是说可能存在有多个客户端(挂载点)对文件系统进行读写和更新操做,因此PolarFS在卷上额外维护了一个Paxos文件。每一个客户端在更新Journal文件前,都须要使用Paxos文件执行Disk Paxos算法实现对Journal文件的互斥访问。更详细的PolarFS元数据更新实现,后续单独做为一个小节。
Chunk
前面提到,每一个卷内部会被划分为多个Chunk(区),区是数据分布的最小粒度,每一个区都位于单块SSD盘上,其目的是利于数据高可靠和高可用的管理,详见后续章节。每一个Chunk大小设置为10GB,远大于其余相似的存储系统,例如GFS为64MB,Linux LVM的物理区(PE)为4MB。这样作的目的是减小卷到区映射的元数据量大小(例如,100TB的卷只包含10K个映射项)。一方面,全局元数据的存放和管理会更容易;另外一方面,元数据能够全都缓存在内存中,避免关键IO路径上的额外元数据访问开销。
固然,Chunk设置为10GB也有不足。当上层数据库应用出现区域级热点访问时,Chunk内热点没法进一步打散,可是因为每一个存储节点提供的Chunk数量每每远大于节点数量(节点:Chunk在1:1000量级),PolarFS支持Chunk的在线迁移,其上服务着大量数据库实例,所以能够将热点Chunk分布到不一样节点上以得到总体的负载均衡。
在PolarFS上,卷上的每一个Chunk都有3个副本,分布在不一样的ChunkServer上,3个副本基于ParallelRaft分布式一致性协议来保证数据高可靠和高可用。
Block
在ChunkServer内,Chunk会被进一步划分为163,840个Block(块),每一个块大小为64KB。Chunk至Block的映射信息由ChunkServer自行管理和保存。每一个Chunk除了用于存放数据库数据的Block外,还包含一些额外Block用来实现预写日志(Write Ahead Log,WAL)。
须要注意的是,虽然Chunk被进一步划分为块,但Chunk内的各个Block在SSD盘是物理连续的。PolarFS的VLDB文章里提到“Blocks are allocated and mapped to a chunk on demand to achieve thin provisioning”。thin provisioning就是精简配置,是存储上经常使用的技术,就是用户建立一个100GB大小的卷,但其实在卷建立时并无实际分配100GB存储空间给它,仅仅是逻辑上为其建立10个Chunk,随着用户数据不断写入,PolarFS不断分配物理存储空间供其使用,这样可以实现存储系统按需扩容,大大节省存储成本。
那么为什么PolarFS要引入Block这个概念呢,其中一个是跟卷上的具体文件相关,咱们知道一个文件系统会有多个文件,好比InnoDB数据文件*.ibd。每一个文件大小会动态增加,文件系统采用预分配(fallocate())为文件提早分配更多的空间,这样在真正写数据的时无需进行文件系统元数据操做,进而优化了性能。显然,每次给文件分配一个Chunk,即10GB空间是不合理的,64KB或其倍数才是合适的值。上面提到了精简配置和预分配,看起来是冲突的方法,但实际上是统一的,精简配置的粒度比预分配的粒度大,好比精简配置了10GB,预分配了64KB。这样对用户使用没有任何影响,同时还节省了存储成本。
PolarFS组件解析
首先展现一张可以更加清晰描述与数据流相关的各个组件做用的示意图,并逐一对其进行解释。
libpfs
libpfs是一个用户空间文件系统(即上图User Space File System)库,负责数据库IO(File IO)接入。更直观点,libpfs提供了供计算节点/PolarDB访问底层存储的API接口,进行文件读写和元数据更新等操做,以下图所示:
pfs_mount()用于将指定卷上文件系统挂载到对应的数据库计算节点上,该操做会获取卷上的文件系统元数据信息,将其缓存在计算节点上,这些元数据信息包括目录树(the directory tree),文件映射表(the file mapping table)和块映射表(the block mapping table)等,其中目录树描述了文件目录层级结构信息,每一个文件名对应的inode节点信息(目录项)。inode节点信息就是文件系统中惟一标识一个文件的FileID。文件映射表描述了该文件都有哪些Block组成。经过上图咱们还发现了pfs_mount_growfs(),该API可让用户方便得进行数据库扩容,在对卷进行扩容后,经过调用该API将增长的空间映射到文件系统层。
上图右侧的表描述了目录树中的某个文件的前3个块分别对应的是卷的第348,1500和201这几个块。假如数据库操做须要回刷一个脏页,该页在该表所属文件的偏移位置128KB处,也就是说要写该文件偏移128KB开始的16KB数据,经过文件映射表知道该写操做其实写的是卷的第201个块。这就是lipfs发送给PolarSwitch的请求包含的内容:volumeid,offset和len。其中offset就是201*64KB,len就是16KB。
PolarSwitch
PolarSwitch是部署在计算节点的Daemon,即上图的Data Router&Cache模块,它负责接收libpfs发送而来的文件IO请求,PolarSwitch将其划分为对应的一到多个Chunk,并将请求发往Chunk所属的ChunkServer完成访问。具体来讲PolarSwitch根据本身缓存的volumeid到Chunk的映射表,知道该文件请求属于那个Chunk。请求若是跨Chunk的话,会将其进一步拆分为多个块IO请求。PolarSwitch还缓存了该Chunk的三个副本分别属于那几个ChunkServer以及哪一个ChunkServer是当前的Leader节点。PolarSwitch只将请求发送给Leader节点。
ChunkServer
ChunkServer部署在存储节点上,即上图的Data Chunk Server,用于处理块IO(Block IO)请求和节点内的存储资源分布。一个存储节点能够有多个ChunkServer,每一个ChunkServer绑定到一个CPU核,并管理一块独立的NVMe SSD盘,所以ChunkServer之间没有资源竞争。
ChunkServer负责存储Chunk和提供Chunk上的IO随机访问。每一个Chunk都包括一个WAL,对Chunk的修改会先写Log再执行修改操做,保证数据的原子性和持久性。ChunkServer使用了3D XPoint SSD和普通NVMe SSD混合型WAL buffer,Log会优先存放到更快的3DXPoint SSD中。
前面提到Chunk有3副本,这三个副本基于ParallelRaft协议,做为该Chunk Leader的ChunkServer会将块IO请求发送给Follow节点其余ChunkServer)上,经过ParallelRaft一致性协议来保证已提交的Chunk数据不丢失。
PolarCtrl
PolarCtrl是系统的控制平面,相应地Agent代理被部署到全部的计算和存储节点上,PolarCtrl与各个节点的交互经过Agent进行。PolarCtrl是PolarFS集群的控制核心,后端使用一个关系数据库云服务来管理PolarDB的元数据。其主要职责包括:
监控ChunkServer的健康情况,包括剔除出现故障的ChunkServer,维护Chunk多个副本的关系,迁移负载太高的ChunkServer上的部分Chunk等;
Volume建立及Chunk的布局管理,好比Volume上的Chunk应该分配到哪些ChunkServer上;
Volume至Chunk的元数据信息维护;
向PolarSwitch推送元信息缓存更新,好比由于计算节点执行DDL致使卷上文件系统元数据更新,这些更新可经过PolarCtrl推送给PolarSwitch;
监控Volume和Chunk的IO性能,根据必定的规则进行迁移操做;
周期性地发起副本内和副本间的CRC数据校验。
本篇主要是介绍了PolarDB数据库及其后端共享存储PolarFS系统的基本架构和组成模块,是最基础的部分。下一篇重点分析PolarFS的数据IO流程,元数据更新流程,以及PolarDB数据库节点如何适配PolarFS这样的共享存储系统。
本文来自网易云社区 ,经做者温正湖受权发布。
网易云免费体验馆,0成本体验20+款云产品!
更多网易研发、产品、运营经验分享请访问网易云社区。
相关文章:
【推荐】 3分钟掌握一个有数小技能:收入贡献分析
【推荐】 手把手带你打造一个 Android 热修复框架
【推荐】 kudu 存储引擎简析