面向大规模商业系统的数据库设计和实践
导读:目前关系型数据库从上世纪 70 年代诞生以来得到了广泛应用,各种数字化的信息系统都能见到关系型数据库的身影。在真实的场景里面,业务系统对关系型数据库这种基础软件的要求非常简单,那就是高可靠和高性能,同时希望尽可能借助复杂的 SQL 语义来简化业务层功能的实现。传统数据库产品例如 Oracle、SQLServer、MySQL、PostgreSQL 等都发展趋于成熟,新一代的云原生数据库产品例如 Aurora、PolarDB、TiDB、OceanBase 等又开始引发更广泛的关注,那么什么样的数据库产品才能更好地适应业务发展?数据库这种比较古老的软件产品的未来又是什么?本文主要从商业产品系统的需求出发探讨数据库技术的实践和思考。
全文 11241 字,预计阅读时间 18 分钟。
一、商业产品系统对数据存储设施需求的特点
百度商业产品矩阵主要包括效果广告(搜索广告、信息流广告)和展示广告(品牌广告、开屏聚屏广告)两大类广告产品,以及基木鱼和观星盘等营销工具,商业产品系统是连接百度客户和广告检索系统的桥梁,帮助客户表达营销诉求,达成营销目标。
商业产品系统本质就是一个复杂、庞大的广告信息管理系统,有 toB、toC 的多种场景,需求多样丰富且迭代频繁。
这些业务需求聚焦到数据存储层面,主要有:
投放,交易场景的事务型需求(OLTP,On-Line Transaction Processing);
广告效果分析场景的分析型需求(OLAP,Online analytical processing)
特定场景的高 QPS 查询,例如账户结构,权限关系等;
字面场景的正反 KV 查询,例如关键词字面和 id 互查等;
物料列表场景的模糊查询;
为了应对商业场景下如此多样且迥异的数据存储需求,如果使用传统的存储技术,至少需要使用关系型数据库(例如 MySQL)、KV 存储(例如 Redis)、OLAP 数仓(例如 Palo)、全文检索(例如 ElasticSearch)以及自定义内存结构的存储等。
那么业务系统对数据存储设施的要求是什么呢?
首先是稳定可靠,不可用就意味着客户体验受损乃至直接的经济损失;
其次数据尽可能一致,如果客户在不同环节看的数据有差异则会产生误解甚至引发错误的广告投放操作;
再次尽可能低成本的应对数据规模持续增长,不需要预先购置大量硬件,后期扩展时也尽可能简单;
最后综合读写性能好,尽量毫秒级响应,不影响客户的操作体验。
对于业务研发的同学来说,他们希望用到的数据存储产品是什么样呢?
接口的使用方式单一,学习和迁移成本低,不同的数据存储也尽量采用相同的接口形式;
数据变更行为可理解,不出现数据丢失或者覆盖,不因并发引入异常数据;
扩展性高,能够适应数据规模和流量从 1 到 N 的变化,业务最好无感知;
高可用,内建高度容错能力,业务对数据库异常最好无感知;
Schema 变更成本低廉;
对各种读写模式都能提供很好的性能。
总结起来最好什么都可以干,什么负载都可以扛,什么运维都不用管!
二、BaikalDB 的发展历程
商业系统最核心的存储需求就是广告库,广告库存储了所有的广告物料信息,用于完成整个广告生命周期的管理,帮助客户完成全部广告投放功能,获取转化。伴随百度凤巢系统的发展,广告库的存储设施经历了两个重要的阶段:
1. 单库到分库分表的 MySQL 集群
2. MySQL 主存储集群+镜像辅助存储构成的异构复合存储集群
2.1 分库分表的 MySQL 集群
最早的凤巢广告库采用单机 MySQL,部署在独立的盘柜(高性能磁盘阵列)上,这种架构受限于当时的硬件条件现在看来比较古老,但这个跟现在流行的存储计算分离的云原生架构从思想上是完全一致的,AWS 的 Aurora 或者阿里云的 PolarDB 就是把 MySQL、PostgreSQL 等单机数据库部署到一个由 EBS 磁盘或者 RDMA 高速网络连接的分布式文件系统上,实现 100%的 SQL 兼容。
随着业务发展,单机部署的 MySQL 无法支撑数据量和读写量的膨胀,分库分表就成了当时乃至现在最优的选择,通过分库分表,MySQL 可以实现容量和性能的高扩展性。
从 2010 年开始,凤巢广告库就依次经历了 1 拆 4、4 拆 8、8 拆 16、16 拆 32 的分库过程,从一套单机集群发展成了有 33 分库(多拆出来的一个分库是为了解决个别大客户购买巨量关键词的场景),每分库 1 主 11 从的多分库集群,存储了数十 TB 的广告物料信息,读写 PV 达到每日数十亿。拆库的服务停顿时间从一天到 6 个小时,再到分钟级别。
2.2 异构复合存储集群
凤巢广告库的业务场景是读多写少,查询场景多样,多分库 MySQL 集群在满足一些查询场景较为吃力,比如在账户-计划-单元-关键词层级结构里,获取账户下关键词数,计划下的关键词数等涉及全表扫的 count,关键词字面高 qps 查询,创意模糊搜索,物料列表分筛排等,这些需求使用 MySQL 都难以满足。
为解决这个问题,我们通过数据流,把 MySQL 的数据实时同步到一个镜像的内存存储,这些镜像存储采用针对特定查询场景的内存结构,来满足业务性能。同时为了业务应用的开发便利,还专门开发了 SQL 代理层,按照一定规则在 SQL 不改变的情况路由到镜像索引,并转化为镜像存储所需要的请求参数,这样虽然我们使用了不同的数据源,但是业务应用仍然认为是一个 MySQL 协议的数据库在提供服务,且无需要关注应该查询哪种数据源,由此形成一个异构的复合存储形态。架构如下图所示:
这是一种常见的架构设计,在另一些业务场景中会把 OLTP 数据库的数据同步到 OLAP 数据仓库,隔离离线分析场景,它的优势在于多套同种数据不同存储引擎的系统通过分而治之来解决复杂的查询场景,并具有一定业务隔离性。
依靠 SQL 代理层能够有效提升业务应用的使用体验,并且可以把应用层分库分表逻辑也下沉到这个代理层,拆库时业务应用也无需感知。对于业务应用来说,看到的是一个单机的 MySQL 系统,不再需要考虑任何性能和容量的问题。
但是这种架构也有明显的缺点:
运维更为复杂:除了关注 MySQL 本身,还需要运维数据实时同步流,SQL 代理层,镜像索引这些系统。
数据实时同步容易出现故障或者延迟:客户可能感知到明显的不一致,从镜像索引查询到的数据跟从 MySQL 查询有差异。为了降低这种差异的影响,SQL 代理层还需要设计一定的降级能力(发现延迟时尽可能切换到 MySQL 查询)。还需要有快速修正镜像索引数据的设施。
资源冗余浪费:镜像索引实际是数据的复制, MySQL 为扛住读性能和同步需求需要大量的从库。
2.3 2017 年的选择
时间来到 2017 年,凤巢广告库已经有 33 分库,磁盘也用上了 NVME SSD,对于限定场景的读写性能可以满足业务需求,但是如果再进行一次拆库,无论是资源消耗还是运维成本都更为巨大。
到这个阶段,我们开始思考是否存在一种成本更低的解决方案。新的信息流广告业务也在快速发展,如果再形成一套凤巢广告类似的存储架构,实际成本会非常可观。虽然 4 年后的今天,凤巢广告库依靠硬件升级,包括 CPU 和内存升级、NVME SSD 升级到单盘 3T,依然维持在 33 分库的部署架构,但性能瓶颈已经开始突显,如果广告物料继续高速增长,预计 2022 年底就需要进行新的拆库。
当时广告系统的业界标杆 Google AdWords 的核心存储是 F1/Spanner,采用全球部署可以跨远距离的数据中心多活,配备原子钟用于实现分布式强一致事务,具备极高的可用性和自动增容的扩展性。参考 Google 存储系统的设计理念,广告存储系统设计也有可见的两种路线:
2.3.1 基于 MySQL 深度定制
MySQL 是一种单机的架构,代码规模达到百万行级别,掌控和修改的难度都特别高。如果要把 MySQL 从内部改造成一种类似 F1/Spanner 能力的系统基本不大可能。
这时一般有两种解决思路,都是从外部来寻求突破:
类似 Aurora 和 PolarDB,在文件系统上进行突破,使用 EBS 或者构建一种 RDMA 高速连接的分布式文件系统,这并不是研发新的数据库系统。但是为获取更好的性能,依然需要深入 MySQL 的存储引擎和主从同步机制进行一些定制和深度优化。即便如此,总容量和性能也不能无限扩展,例如 Aurora 最高可达 128TB,性能是 MySQL 的 5 倍,PolarDB 最高可达 100TB,性能是 MySQL 的 6 倍。
类似凤巢广告存储的设计思路,通过数据同步并借助扩展的镜像索引提升查询性能,但冗余成本高,数据一致性差。
2.3.2 使用满足分布式+云原生+多样化索引架构+强一致等条件的新数据库系统
2017 年的时候,无论是 Google 的 F1/Spanner 还是 OceanBase 都是闭源系统,跟内部设施耦合很大。开源系统主要有两个流派,一类是支持 SQL 的 OLAP 系统,例如百度的 Palo(现开源名为 Doris)、Impala(无存储引擎)、ClickHouse 等,一类是参考 F1/Spanner 思想的 CockroachDB 和 TiDB。OLAP 系统肯定不太满足我们 TP(在线事务)场景的主需求,当时 CockroachDB 和 TiDB 也处于起步阶段,生产场景的使用基本没有。
这时放眼望去,实际并没有特别成熟的解决方案,基于 MySQL 的方案也走到了一个瓶颈,那么我们能否自研一个新的分布式数据库系统?当时的决策依据是看团队是否具备能力从零研发出一个高可用、高性能、低成本的 OLTP 为主兼顾 OLAP 的数据库(也就是 HTAP,Hybrid Transaction and Analytical Process,混合事务和分析处理)。
团队的条件:已有的存储方向团队(4 人)是 C++技术栈,研发过 SQL 代理层和定制化存储,熟悉 MySQL 协议,有实战的工程经验。
技术的条件:
1、分布式系统需要有效的通信框架,百度的 brpc 框架当时已经非常成熟,是工业级的 RPC 实现,有超大规模的应用。
2、保障数据一致性当时主流的方案就是 Paxos 和 Raft,百度 braft 框架是基于 brpc 的 Raft 协议实现,发展也很迅速,有内部支持。
3、单机存储节点需要一个可靠的 KV 存储,Facebook&Google 联合出品的 RocksDB 是基于 LSM-Tree 的高性能 KV 引擎,CockroachDB 和 TiDB 都选择了 RocksDB。
后来经过 8 个月的设计研发,我们的 1.0 版本数据库就完成上线,结果也证明了我们的决策是可行的。
2.4 面向商业产品系统的新一代存储系统 BaikalDB
BaikalDB 是面向商业产品系统的需求而设计的分布式数据库系统,核心的目标有三个:
1、灵活的云上部署模式:面向容器化环境设计,能够与业务应用混部,灵活迁移,容量和性能支持线性扩展,成本低廉,不需要特殊硬件。
2、一站式存储计算能力:面向业务复杂需求具备综合的适应性,主要满足 OLTP 需求,兼顾 OLAP 需求、全文索引需求、高性能 KV 需求等。
3、兼容 MySQL 协议:易于业务使用,学习成本低。
BaikalDB 命名来自于 Lake Baikal(世界上容量最大的淡水湖-贝加尔湖),贝加尔湖是世界上容量最大的淡水湖,相当于北美洲五大湖水量的总和,超过整个波罗的海水量,淡水储量占全球 20%以上。西伯利亚总共有 336 条河流注入贝加尔湖。冬天的贝加尔湖畔,淡蓝色的冰柱犹如分布式数据库一列列的数据密密麻麻但是有井然有序,令人惊艳。
BaikalDB 是一个兼容 MySQL 协议的分布式可扩展存储系统,支持 PB 级结构化数据的随机实时读写,整体系统架构如下:
BaikalDB 基于 RocksDB 实现单机存储,基于 Multi Raft 协议(braft 库)保障副本数据一致性,基于 brpc 实现节点通讯交互,其中
BaikalStore 负责数据存储,用 Region 组织,三个 Store 的三个 Region 形成一个 Raft group 实现三副本,多实例部署。Store 实例宕机可以自动迁移 Region 数据。
BaikalMeta 负责元信息管理,包括分区、容量、权限、均衡等, Raft 保障的 3 副本部署,Meta 宕机只影响数据无法扩容迁移,不影响数据读写。
Baikaldb 负责前端 SQL 解析,查询计划生成执行,无状态全同构多实例部署,宕机实例数不超过 QPS 承载极限即可。
BaikalDB 的核心特性有:
全自主化的容量管理:可以自动扩容和自动数据均衡,应用无感知,很容易实现云化,目前运行在 Opera PaaS 平台之上
高可用,无单点:支持自动故障恢复和迁移
面向查询优化:支持各种二级索引,包括全文索引,支持多表 join,支持常见的 OLAP 需求
兼容 MySQL 协议,支持分布式事务:对应用方提供 SQL 界面,支持高性能的 Schema 和索引变更
支持多租户:meta 信息共享,数据存储完全隔离
在系统研发过程中,BaikalDB 以业务需求为导向规划快速迭代,在业务使用中深度打磨优化,随业务成长而成长,关键功能的时间节点如下:
从 2018 年上线以来,BaikalDB 已部署 1.5K+数据表,数据规模达到 600+TB,存储节点达到 1.7K+。
三、BaikalDB 关键设计的思考和实践
分布式数据存储系统一般有三种架构模式,分别是 Shared Everthing、Shared Disk 和 Shared Nothing。
1、Shared Everthing:一般是针对单个主机,完全透明共享 CPU、内存和磁盘,传统 RDMS 产品都是这种架构。
2、Shared Disk:各个处理单元使用自己的私有 CPU 和内存,但共享磁盘系统,这可以实现存储和计算分离,典型的代表是 Oracle Rac(使用 SAN 共享数据)、Aurora(使用 EBS)、PolarDB(使用 RDMA)。
3、Shared Nothing:各个处理单元都有自己私有的 CPU、内存和磁盘等,不存在资源共享,类似于 MPP(大规模并行处理)模式,各处理单元之间可以互相通信,并行处理和扩展能力更好。典型代表是 hadoop,各 node 相互独立,分别处理自己的数据,处理后可能向上层汇总或在节点间流转。
Shared Disk 是很多云厂商倡导的架构,云厂商希望在云上提供一个完全兼容传统 RDMS 系统的云产品,希望广大的数据库使用者基本没有迁移成本,但是各家云厂商的实现有比较多差异,主要比拼性能、容量和可靠性,这些也是各家云厂商吸引客户的卖点。但是该架构的 Scale Out(横向扩容)能力比较有限,所以云厂商的存储广告宣传语一般是百 TB 级别的数据容量。
Shared Nothing 是一种分布式的依靠多节点来工作的架构,大部分 NoSQL 都是这样的架构,Sharding MySQL 集群也是一种 Shared Nothing 的架构,每个分片独立工作。这类架构最大的局限是难以同时保障一致性和可用性,也就是受限于著名的 CAP 理论。对于 NoSQL 系统大部分不支持事务,所以优先保障可用性。但对于 OLTP 场景,数据一致性非常重要,事务是不可或缺的环节。
因为 BaikalDB 的目标是一个面向业务需求的具备融合型能力的分布式数据存储,并且大规模数据场景更看重 Scale Out 能力(仅仅 100TB 容量远远不够),所以采用的是 Shared Nothing 的架构。
对于分布式数据系统而言,设计最需要关注存储、计算、调度三个方面的内容。
3.1 存储层的设计
存储层的设计主要是关注用什么样的数据结构来描述数据存放。对于分布式数据系统,还需要额外关注怎么利用多节点来协同存储同一份数据。
对于大规模数据场景的存储优先需要考虑使用磁盘,而不是成本更高数据易失的内存。面向磁盘的存储引擎,RocksDB 是比较突出的代表,其核心模型是 Key-Value 结构。如果使用 RocksDB 就需要考虑如何数据表的结构映射到 Key-Value 结构。
为了把数据分散到多台机器,BaikalDB 还引入了 Region 的概念,用于描述最小的数据管理单元,每个数据表是有若干个 Region 构成,分布到多台机器上。这样的架构就需要考虑如何对数据进行拆分,一般有 Hash(根据 Key 的 Hash 值选择对应的机器)和 Range(某一段连续的 Key 都保存在一个机器上)两种。
Hash 的问题在于如果 Region 增大到需要分裂时如何动态修改 Hash 规则,而 Hash 规则的改动会涉及大量数据的重新分布,每个 Region 的大小都很难均衡,即使引入一致性 Hash 也只能有限改善该问题。Range 虽然容易实现数据的分裂拆分,但是容易有热点,不过相对来说好克服。所以 BaikalDB 采用了 Range 拆分。
Key-Value 不等同于数据库的 Table,需要把数据表的主键索引(也叫聚簇索引,存储主键值和全部数据),和面向查询优化的二级索引(也叫非主键索引、非聚簇索引,存储索引值和主键值)以及全文索引都要映射到 Key-Value 模型里面。
主键索引,为区分 Region 和索引类型,除了包含主键值还需要包含 region_id 和 index_id,由于 region_id 可以在同一集群全局唯一分配,也不需要 table_id,同时考虑到多字段构成的联合主键需要按联合主键的大小顺序存放,还需要引入对 Key 的 Memcomparable 编码来提升 Scan 性能;整行的数据可以用 protobuf 进行编码后再存储到 Value 里,这样可有一定压缩也能更方便的实现加列操作。
二级索引,主要需要考虑采用本地(局部)二级索引(Local Secondary Index)还是全局二级索引(Global Secondary Index)。
本地二级索引:只索引本机 Region 的数据,优点是索引和数据都在同一个节点,回表速度快,不需要实现分布式事务。但是查询总需要附带上主键条件,否则就只能广播给全部分区。
全局二级索引:能够索引整个表的全部分区数据,优点是没有主键条件时也不需要广播,但由于全局二级索引是一张独立的表,跟所有主键数据没法在一个存储节点上,需要引入分布式事务才能工作。
二级索引在 Key-Value 模型里面,不管本地的还是全局的,Key 都是由 region_id、index_id、索引键值、主键值(如果是二级唯一索引就无需包含),Value 是主键值,可以看出使用二级索引拿到整行数据还需要从主键索引再获取一次(也就是回表操作),如果相关数据都在索引键值里就不需要回表。
BaikalDB 在早期没有引入分布式事务(实在太复杂),所以先实现了本地二级索引,在实现分布式事务后再实现了全局二级索引。对于业务应用而言,可以按使用场景优先选择本地二级索引。
全文索引,主要涉及索引构建及检索:
构建:将正排字段切词为一或多个 term。构建 term 的有序倒排拉链,并按照格式进行存储。
检索:根据检索词,对多个倒排拉链进行布尔查询。
在 Key-Value 模型里面,Key 为 region_id、index_id、分词后的 term,Value 为排序好的主键键值。
所以在存储层面,包括以上主要核心逻辑结构,以及列存、HLL、TDdigest 等都是 KV 的物理结构。关于更多的索引细节设计请参考 BaikalDB 的索引实现(https://my.oschina.net/BaikalDB/blog/4514979)。
数据基于 Range 方式按照主键切分成多个分片(Region)后,同时为提升在分布式场景下的整体可用性,需要多个副本(Replica)来存储同样的 Region,这时就需要考虑多个副本和多个分片的数据一致性:
多副本(Replica)的数据一致性:多个副本需要数据可靠地复制,在出现故障时,能产生新的副本并且不会发生数据错乱,这主要依靠 Raft 一致性协议来实现。Raft 提供几个重要的功能:Leader 选举、成员变更、日志复制,每个数据变更都会落地为一条 Raft 日志,通过 Raft 的日志复制功能,将数据安全可靠地同步到副本 Group 的多数节点中。在强一致性要求的情况下,读写都发生在 Leader 节点。
多分片(Region)的数据一致性:涉及多个 Region 的操作需要保障同时成功或者失败,避免一个修改中部分 Region 失败导致的不一致问题,这主要依靠结合 RocksDB 单机事务的两阶段提交(Two-phase Commit,即 2PC)实现悲观事务,同时结合 Percolator 的思想,采用 Primary Region 来充当事务协调者的角色,避免协调者的单点。分布式事务的细节比较复杂,在很多数据库研发公司中都是一个专门的团队在投入,更多细节可以参考 BaikalDB 的分布式事务实现(https://my.oschina.net/BaikalDB/blog/4319429)。
3.2 计算层的设计
计算层需要关注如何把 SQL 解析成具体的查询计划,或者叫分布式的计算过程,还需要考虑如何基于代价进行优化。
BaikalDB 的 SQL 层面是一种分布式的分层设计,整体架构如下:
目前 BaikalDB 还不是完全的 MPP 架构,跟 OLAP 系统的设计有较大差异,数据最后的计算汇总只发生在一个 Baikaldb 节点上,同时各种 Filter 条件会尽量下推到 BaikalStore 模块,减少最后需要汇总的数据,考虑到 OLTP 场景为主的情况下返回数据规模有限,所以也足够使用。因此 BaikalDB 存储节点具备一定的计算能力,可以分布式执行,降低传输压力,所以也不是严格的存储和计算分离。
在 SQL 引擎的实现层面,我们采用了经典的火山模型,一切皆算子,包括 Baikaldb 和 BaikalStore 的交互也是算子,每个算子提供 open、next、close 操作,算子之间可以灵活拼接,具备很好的扩展性。
在查询条件执行过程中,如果数据表有多种索引,为了让查询更优,还需要具备自动选择最合适索引的能力,这种查询优化器的主流设计有两种:
基于规则的优化器(RBO,Rule-Based Optimization):该方式按照硬编码在数据库中的一系列规则来决定 SQL 的执行计划。实际过程中,数据的量级差异会影响相同 SQL 的性能,这也是 RBO 的缺陷所在,规则是不变的,数据是变化的,最后规则决定的不一定最优。
基于代价的优化器(CBO,Cost-Based Optimization):该方向通过根据优化规则会生成多个执行计划,然后 CBO 会通过根据统计信息(Statistics)和代价模型(Cost Model)计算各种执行计划的代价,即 COST,从中选用 COST 最低的执行计划作为实际执行计划。统计信息的准确与否会影响 CBO 做出最优的选择。
查询优化器也是非常复杂的话题,现在还有基于 AI 技术的查询优化器,也是学术研究的热门,在很多数据库研发公司,这一般也是一个专门的大方向。BaikalDB 采用了 RBO 和 CBO 结合的方式做查询优化,关于 CBO 的细节可以参考 BaikalDB 的代价模型实现(https://my.oschina.net/BaikalDB/blog/4715063)。
3.3 调度层的设计
分布式数据系统涉及到多个工作节点,每个节点可能有不用的硬件环境和软件负载,为尽可能发挥集群的性能,肯定希望每个工作节点存储的数据大小基本一致,数据处理的负载基本一致,但同时还需要考虑故障节点的避让和恢复,保持集群的性能平稳。
调度系统基本都会有一个 Master 角色来综合评估集群所有节点的信息做出调度决策,BaikalDB 的 Master 角色是 BaikalMeta 模块,BaikalStore 定时通过心跳包收集信息上报给 BaikalMeta,BaikalMeta 获得整个集群的详细数据后根据这些信息以及调度策略来生成决策,这些决策会通过心跳包的回复发送给 BaikalStore,BaikalStore 会根据实际情况来灵活执行,这里并不需要保证操作的执行成功,后续还会通过心跳告知 BaikalMeta 执行情况。
BaikalMeta 每次决策只需要根据本轮收集的所有节点的心跳包的结果来处理,不需要依赖之前的心跳包信息,这样决策逻辑就比较容易实现。如果 BaikalMeta 故障,BaikalStore 的心跳包没有回应,就会停止全部的调度操作,整个集群处于不调整的状态,同时 Baikaldb 模块会缓存 BaikalMeta 返回的集群信息,能准确知道每个数据表的全部 Region 信息,并能对失败的节点做剔除或者重试,这样即使 BaikalMeta 故障,也不会影响读写。
另一方面 BaikalMeta 的决策并不需要很高的时效性,所有 BaikalStore 可以间隔较长时间发送心跳,有效控制对 BaikalMeta 请求压力,这样一组 BaikalMeta 就可以管理成千上万的 BaikalStore 节点。
在存储节点的调度中主要需要关注 Leader 的均衡和 Peer 的均衡:
Leader 均衡 :每个 BaikalStore 节点的 Region Leader 数量应尽量一致,Leader 是主要的读写压力承担者,均衡可以让每个 BaikalStore 节点的 CPU 内存负载接近。在 BaikalStore 负载较高时(通常容器化环境下会显著变高),如果同机器的其他容器消耗大量的 CPU 和内存,同一个 BaikalStore 的其他 Leader 也可能消耗大量资源,就需要把它上面的 Leader 切换到其他节点,避免热点导致的处理超时。
Peer 均衡:指每个 Raft Group 的副本尽量分散到每个 BaikalStore 节点,使得每个 BaikalStore 节点的副本数量尽可能一致,每个数据表的所有 Region 大小基本是一致的,因此使得每个 BaikalStore 的存储容量也比较接近,避免数据倾斜,这样能够重复利用集群的磁盘资源。此外还希望每个副本在不同的机器,甚至不同的网段上,避免机器故障和网络故障导致一个 Region 的大部分副本不可用进而导致 Leader 无法选出不能读写。在 Peer 有节点故障或者主动迁移时,还需要创建新的 Peer 同步数据达成可用,并删除不可用 Peer,这样来保障 Peer 数量稳定。
Region 作为一个调度单元,它的可分裂性也是调度机制的一个基础,BaikalDB 会在 Region 大小超过设定阈值时,采用基准+增量的方式来拆分 Range 产生新的 Region 及其副本,通过汇报信息进行调度均衡,这样使得在数据增长的时候可以自动化拆分。调度也是一个比较复杂的话题,通过引入很多调度策略能够提升资源的利用率、容灾、避免热点,保障性能,这块工作也是 BaikalDB 迭代的重点方向。
四、总结
本文从大规模商业系统的需求出发,总结了商业场景对数据存储设施的期望。通过回顾整个凤巢广告库依赖的数据库系统的发展过程,来展示了商业平台研发部自研更低成本、更为可靠、更为强大的数据存储系统-BaikalDB 的迭代历程。
经过 4 年的工作,BaikalDB 已经整合了商业产品系统历史存在的全部存储系统,实现了大一统。在结合业务需求研发过程中,BaikalDB 也尽可能依靠很少的人力投入,快速构建核心功能集,根据业务需求的紧迫程度逐渐迭代,不仅仅满足了广告场景的需求,还满足新的包括落地页和电商的新商业场景的需求,而且仍在不停的丰富功能、优化性能和降低成本,打磨整个系统。
最后针对如何研发一个数据库,从存储、计算、调度三个角度总结了 BaikalDB 的一些关键设计思路。数据库和操作系统、编译器并称三大系统软件,可以说是整个计算机软件的基础设施,数据库技术同样是博大精深,本文只是以业务视角管中窥豹,难免有疏漏,希望大家探讨指正。
最后希望大家多多关注 BaikalDB 的开源项目 github.com/baidu/BaikalDB 。
---------- END ----------
百度 Geek 说
百度官方技术公众号上线啦!
技术干货 · 行业资讯 · 线上沙龙 · 行业大会
招聘信息 · 内推信息 · 技术书籍 · 百度周边
欢迎各位同学关注
版权声明: 本文为 InfoQ 作者【百度Geek说】的原创文章。
原文链接:【http://xie.infoq.cn/article/94ea7f41100fadf5ddf0f0edb】。文章转载请联系作者。
评论