数据分区

分区/分片(动词)就是将数据拆分为多个子集,一个子集被称为一个分区[1](名词)。使用数据分区的目的是提高可扩展性,不同分区可以存储在不同节点上,查询负载也随之分散到多个节点。在面对海量数据集或者非常高的查询压力,使用数据复制还不够,这时就需要使用数据分区。当分区和复制结合使用时(假设为主从复制),每个分区都会有自己的主节点和从节点,这种情况下单个节点也会存储多个分区的数据(作为某个分区的主节点和其他分区的从节点)。

分区方式

最好的情况是将数据和查询负载均匀分布到所有节点。如果分区不均匀,就会出现负载的倾斜,这会导致分区的效率下降。极端情况下,所有负载都集中在单个节点上,其他节点都处于空闲状态,此时这个节点被称为系统的热点。避免热点的最简单的方法是将记录随机分配给所有节点,但是由于不知道数据的分布情况,在查询时需要访问所有节点。下面将会介绍对键值数据和二级索引进行分区的方式。

这节的内容看得有点懵。按我的理解,键(key)也被称为关键字(keyword),它是一列或多列属性的集合,可以是唯一的或者不唯一的。但是按照书中的描述以及我在网上看到的定义来说,在“键值数据”这样的词中,似乎要求键必须是唯一的。书中在介绍完键值数据之后,又紧接着介绍二级索引,然后说二级索引在分区时的复杂性在于索引键的不唯一。但是书中接下来的讨论让我感觉,二级索引在分区时的复杂性在于如何创建二级索引,即是创建本地索引还是全局索引。

键值数据

基于区间的分区

解释:可以为每个分区分配一个关键字区间,区间可以由管理员手动选择,或者由数据库自动选择。

优点:分区内的数据可以按照关键字排序存储,从而支持区间查询。

缺点:如果查询集中访问某个范围内的数据,则会导致热点问题,解决方案是使用额外的内容作为关键字的第一列。

基于哈希的分区

解释:可以为每个分区分配一个哈希值区间,关键字根据哈希值进行分区,用于分区的哈希函数不需要很强的加密性。

分析:可以将关键字均匀的分配到多个分区,但是不能很好地支持关键字上的区间查询(查询时需要将请求发送给所有分区)。如果使用的是联合关键字,则可以只将关键字的第一列用于哈希分区,然后将其他列用作联合索引来对分区内的数据排序,从而可以在关键字的其他列上实现区间查询。

问答:为什么要将哈希值拆分为区间,而不直接使用取模操作?因为如果添加/删除节点,取模会导致大量的数据迁移。

负载倾斜与热点

虽然哈希分区可以减轻热点,但是无法完全避免。极端情况下,所有读/写操作都是针对一个关键字,则最终所有请求都会被路由到同一个分区。例如,发生热点事件时,会产生大量对相同关键字的读/写操作,此时哈希分区起不到作用。大多数系统至今仍无法自动消除这种高度倾斜的负载,而只能通过应用层来减轻倾斜程度。例如,如果某个关键字被认为是热点,则可以通过在关键字的开头或结尾添加随机数(有点像密码学中的盐值),从而将请求路由到不同分区。但是,此时读操作必须将多个分区中的数据合并,开销较大。

二级索引

基于文档的分区

解释:每个分区独自创建和维护二级索引,创建的是本地索引,而非全局索引。

缺点:如果要使用索引查询满足某个条件的数据,则需要将查询请求发送给所有分区,然后合并返回的结果。

基于词条的分区

解释:对所有数据创建全局索引,然后对索引进行分区,可以使用区间或哈希分区。

优点:进行单关键字查询时,只需要读取单个分区,因为相同的索引键都会被分配到相同的节点。

缺点:即使更新的是单个节点上的数据,可能也需要更新多个节点上的索引。如果选择同步更新,那么需要分布式事务的支持,写请求会被阻塞;如果选择异步更新,就意味着更新的滞后。

分区再平衡

在某些情况下,可能需要为数据库添加/删除节点,我们希望在添加/删除节点的过程中平衡所有节点的负载,这个迁移负载的过程被称为再平衡或者动态平衡。

再平衡的策略

固定数量的分区

解释:创建远超实际节点数的固定数量的分区,然后为每个节点分配多个分区。如果添加节点,则从每个现有节点中移动几个分区到新节点;如果删除节点,则将其中的分区均匀分配给剩余节点。也可以将硬件配置考虑进来,为性能更强的节点分配更多的分区。

分析:如果数据的规模不确定,就很难确定合适的分区数量。每个分区包含的数据量的上限是固定的,实际大小应该和集群中的数据总量成正比。如果分区数量太大,则每个分区包含的数据量太小,徒增管理开销;如果分区数量太小,则每个分区包含的数据量太大,再平衡和故障恢复的开销就更大(不是很懂为什么)。

动态分区

解释:为每个分区设置阈值,如果分区中的数据量太大或太小,就会进行分裂或合并(类似 B+ 树)。每个节点可以包含多个分区,当某个分区分裂时,可以将其中一半的数据转移到其他节点,以平衡负载。

分析:优点是分区的数量可以通过分裂和合并自动适配数据总量。对于空的数据库来说,需要进行预分裂,从而避免开始时只存在一个分区,导致负载不均衡的情况。

按节点比例分区

解释:为每个节点分配固定数量的分区。如果添加节点,则随机选择固定数量的现有分区进行分裂。

分析:随机选择可能会带来不公平的分裂,但是当每个节点包含的分区数量较大时,可以减少不公平的概率。

疑问:为什么书上说随机选择分区边界的前提是使用哈希分区,以及为什么说该方法符合一致性哈希。

请求路由

我们已经知道如何将数据分区,以及如何平衡节点上的分区,现在还有一个问题是,如何将请求路由到对应分区所在的节点。如果发生分区再平衡,分区和节点的对应关系还会随之变化,我们需要能够跟踪这些变化。有如下三种处理策略:

  • 客户端将请求发送给任意节点,如果当前节点没有对应的分区,则将请求转发给其他节点,直到找到对应节点。
  • 客户端将请求发送给路由层(负载均衡器),路由层负责将请求转发给对应节点。
  • 客户端跟踪分区和节点之间的关系,直接将请求发送给对应节点。

不管使用哪种方法,核心问题是:作出路由决策的组件(节点、路由器、客户端)如何跟踪分区和节点的对应关系。有的分布式系统依靠独立的协调服务(例如 ZooKeeper)跟踪对应关系,有的使用 gossip 协议在节点之间同步对应关系,等等。

PS:数据分区这章看得有点痛苦,感觉书上的表述很乱,包括多个同义词混用,以及前后表述不一致。一些部分也讲得很模糊,没有一个实际的例子,单是看某句话感觉会有歧义,不知道实际上想表达的是什么。先这样吧,总感觉笔记上有很多问题。


  1. 这里讨论的数据分区和网络分区问题(一种节点间的网络故障)中的分区是不同的概念。 ↩︎

作者

Ligh0x74

发布于

2023-12-29

更新于

2024-02-27

许可协议

评论