Database Partitioning_系统设计笔记10

写在前面

为了提升数据库的处理能力,我们把单库扩展成多库,并通过更新同步机制(即Replication)来保证多份数据的一致性。如此这般,数据库的扩展难题似乎已经顺利解决了

然而,在 Replication 方案下,每个数据库都持有一份完整数据,基于全量数据提供增删改查服务, 单库的性能瓶颈仍然存在 ,并将成为限制系统扩展性的关键因素

一.单库的性能瓶颈

单机的硬件资源是有限的,因此单库的处理能力也是有限的:

  • 容量有限:数据量可能大到单库无法容纳

  • 性能有限:单库的读写性能同样受数据量影响,查询/更新越来越慢

单靠加机器/加库显然无法直接解决单机/单库的性能问题, 除非进一步打破库的边界,把单库拆分成多库 (而不只是复制多份)

P.S.理论上,Web 应用层也面临同样的问题,却不曾听说过一个 Web 服务庞大到单机无法部署,这是因为 Web 服务在设计之初就会考虑职责划分与解耦 ,以便各部分能够独立部署、独立扩展,从 20 年前的 SOA(即面向服务架构,包括 微服务架构(Microservices) 等变体)起便是如此

二.分区(Partitioning)

为了避免单库性能成为系统可扩展性的瓶颈,通常把逻辑数据库(或其组成元素,例如数据表)拆分成各个独立部分,这种做法称为 分区(Partitioning)

A partition is a division of a logical database or its constituent elements into distinct independent parts.

(摘自 Partition (database)

就像微服务架构中把单体应用(Monolithic application)拆分成一组小型服务一样,我们通过分区把单库拆分成一组(数据规模)更小的库,各自处理一部分数据,共同分担流量,主要优势体现在:

  • 可扩展性:把单库数据拆分到多库后,系统的可扩展性不再受限于单库性能,数据库层“无限”扩展成为了可能

  • 性能:单库数据量减少,数据操作更快,甚至允许多库并行操作

  • 安全性:可以针对(拆出去的)敏感数据,采取更强的安全控制

  • 灵活性:可以对不同的库(比如按数据重要性)采用不同的监控、备份策略,以缩减成本,提升管理效率。或者对不同类型的数据选用不同的存储服务,比如大型二进制内容放到 blob 存储中,更复杂的数据可以存放在文档数据库中

  • 可用性:把数据分散放到多个篮子里,能够避免单点故障,并且单库故障仅影响一部分数据

具体的,有 3 种拆分策略:

  • 水平分区(Horizontal partitioning,也叫 Sharding):按行拆分,把不同的行放入不同的表中

  • 垂直分区(Vertical partitioning):按列拆分,把一些列放到其它表中

  • 按功能分区(Functional partitioning,有时也叫 Federation):按业务功能拆分,把业务领域中属于相同界限上下文(Bounded Context)的数据放在一起

当然,这 3 种策略并不冲突,可以结合使用

P.S.关于领域驱动设计(Domain-Driven Design),以及界限上下文的更多信息,见 去中心化数据管理(Decentralized Data Management)

三.水平分区

水平分区,即 分片(Sharding) 。每片(shard)都是原数据的一个子集,共同构成完整的数据集:

A database shard is a horizontal partition of data in a database or search engine. Each individual partition (or server) acts as the single source for this subset of data.

(摘自 Shard (database architecture)

与垂直分区相比,水平分区最大的特点是 schema 保持不变

Each partition is a separate data store, but all partitions have the same schema.

就像把一张表横向切几刀,分成几段小表,它们的表结构(字段等)完全一致

这种横向切分减少了单库所需存储的数据量,以及所需承载的流量/操作,另一方面,还减少了资源争用(contention),有助于提升性能

shard key 的选取

具体操作上, 关键在于如何选取 shard key (按哪个字段的什么特征来分片),尽可能保证负载被均匀地分散到每一片上

注意,均匀并不意味着要求每一片的数据量均等,重点是均分流量(有些片可能数据量很大,但访问量却很低)

同时还要避免产生“热点”,比如按姓氏首字母对用户信息进行分片实际上是不均匀的,因为有些字母更常见,此时按用户 ID 哈希值来分片可能更均匀些

四.垂直分区

另一种拆分方式是垂直分区,将一些列(字段)拆分到其它表中:

多用于减少 I/O、降低性能成本,比如,按使用频率把常用字段和不常用的字段分开

比起水平分区,垂直分区的关键优势在于 把信息拆的更细,进而允许一些针对性的优化 ,比如把不经常变化的数据拆分出来,丢到缓存中,把照片等大型二进制内容拆出去单独存放,或者对部分敏感数据进行针对性的安全控制,另一方面,细粒度的数据划分也能够消除一些并发访问,降低并发访问量

五.按功能分区

此外,还可以结合具体应用场景,按业务功能拆分:

把不相干的数据剔除出去(把紧密相关的数据放到一起),有助于加强数据隔离,提升数据访问性能,比如把客户信息和商品库存信息分开

六.分区的代价

把单库拆成多库,虽然能够解决数据库的扩展性难题,但也引发了一些新问题:

  • 连表查询慢:尽量避免跨分区 join、或者考虑并行查询

  • 全表查询慢:对于需要扫描全量数据的查询操作,即便有并行优化也慢,可以通过垂直分区、按功能分区来定位目标分区,避免全表查询,至于水平分区,可以在应用层维护一张映射表,加快分区定位

  • 不支持事务操作:将事务操作交由应用层来处理

  • 负载不匀导致分区效果大打折扣:考虑增加监控,并根据分析预测定期调整

诚然,其中有些问题没有非常漂亮的解决方案,实际应用中更多的是面向特定场景的权衡取舍

参考资料