用“分区”来面对超大数据集和超大吞吐量

2023年 7月 17日 61.9k 0

大家好,我是 方圆。本文的内容参考《数据密集型应用系统设计》的第六章,原文收录在我的 Github: enthusiasm 中,欢迎Star和获取原文。

1. 为什么要分区?

分区(partitions) 也被称为 分片(sharding),通常采用对数据进行分区的方式来增加系统的 可伸缩性,以此来面对非常大的数据集或非常高的吞吐量,避免出现热点。

分区通常和复制结合使用,使得每个分区的副本存储在多个节点上,保证数据副本的 高可用。如下图所示,如果数据库被分区,每个分区都有一个主库。不同分区的主库可能在不同的节点上,每个节点可能是某些分区的主库,同时是其他分区的从库。

分区与复制.png

1.1 一致前缀读

分区也会由于复制延迟而产生问题,我们先来看下图中的例子,是Poons先生和Cake小姐的对话:

一致前缀读.png

Poons先生先问: "How far into the future can you see, Mrs.Cake?"

Cake小姐回答说: "About ten seconds usually, Mr.Poons."

正常情况下,这段对话是有因果关系的(先问后答)。但是对于观察者,他看到的顺序却是先得到了答案,再看到了问题,这就是在分区数据库中,因复制延迟而产生的特殊情况。

为了避免这种混乱,我们就需要保证 一致前缀读:如果一系列写入按某个顺序发生,那么任何人读取这些写入时,也会看见它们以同样的顺序出现。一种解决方案是,确保任何因果相关的写入都在相同的分区。

2. 该怎么分区?

分区的目的是将数据和负载均匀的分布到各个节点上,理论上10个节点能够处理10倍的数据量和10倍单节点的读写吞吐量。

但是如果分区不均,那么就会出现一些分区有更多的数据或读写,我们称之为 偏斜,这会使得分区后并没有得到很大的效率提升。在极端情况下,所有的负载如果都落在一个分区,使得该分区负载过高,我们称之为 热点。

所以,为了避免偏斜和热点的产生,以键值数据的分区为例,讨论如何将数据分区做得妥当。

2.1 根据键的范围进行分区

我们可以根据键值的范围进行分区,比如说我们以26个英文字符划分26个分区,之后根据键值首字母对它们进行分区。通常情况下,键值并不是均匀分布的,这会造成按照首字母分区之后,发生数据偏斜。为了均匀分配数据,分区的边界需要根据数据分区的实际情况再进行调整。

2.2 散列分区

一个好的散列函数可以将数据均匀分布,避免发生偏斜。但是这也带来了问题:我们没有办法再进行高效的范围查询。

3. 热点消除

避免热点最简单的方法是将数据记录进行散列分区,记录因此会在所有节点上平均分配。

但是它并不能完全避免热点的产生,因为如果所有的读写操作都是针对同一个键的话,那么所有的请求还是会被路由到同一个分区。比如说有一个百万粉丝的博主发布动态,该动态根据博主ID的键值进行分区,如果此时有大量的粉丝对该动态进行互动,那么哈希策略会把这些请求都路由到同一个分区进行操作,发生热点事件。

其实,我们还可以在该热点键上再进行分区,以避免热点:在主键的最后拼接随机数,两位十进制的随机数就能把一个主键分成100个不同的主键,从而存储在不同的分区中,这就完成了热点消除。但是主键被分割后,任何读取工作都必须在每次读取时将所有的数据拉出去合并到一起再返回结果。

4. 分区再平衡

如果保存某分区数据的服务器故障,需要使用其他服务器接管或想将目前的服务器换成性能更好的服务器,那么就需要进行 分区再平衡。

分区再平衡 是将负载从集群中的一个节点向另一个节点移动的过程。执行再平衡需要满足以下要求:

  • 再平衡期间,数据库应该继续接受读取和写入

  • 节点之间只移动必须的数据,以便快速再平衡,并减少网络和磁盘的IO负载

  • 再平衡之后,负载应该在集群中的节点之间公平地共享

比较简单的再平衡分区策略是选择 固定数量的分区,当节点数量增加时,可以从原节点中 窃取 一些分区(当节点数量减少时,则发生相反的情况),如下图所示:

固定数量分区再平衡.png

在这种配置中,分区的数量通常在数据库第一次建立时确定,操作比较简单,之后不会改变,因此你需要选择足够多的分区以适应未来的增长。但是,每个分区也有管理开销,所以选择太大的数字会适得其反。

除此之外也可选择 动态分区,根据配置的分区大小,当超过该阈值时,可以将该大分区分割成两个小分区,能够使 分区数量适应总数据量。在大型分区拆分后,可以将其中的一半转移到另一个节点上,以平衡负载。

还有一种 根据节点数增加来进行分区 的方法:每个节点上有固定的分区数,当节点增加时,分区将变小,新增的节点会从原有节点的分区中随机进行拆分,最终这个新节点获得公平的负载份额。

分区再平衡可以 手动执行 也可以 自动执行。自动再平衡比较方便,因为不需要人工维护,但是它的执行过程是不可预测的:再平衡时将大量数据集从一个节点转移到另一个节点的过程中可能会产生很大的网络开销,这会使得该服务器对请求响应的性能降低,对用户的体验和生产造成负面影响。所以再平衡的过程有人参与是一件好事,这样能防止发生运维问题。

5. 请求路由(服务发现)

当我们已经将数据进行分区后,如何才能知道用户想要的数据在哪个节点上?这可以概括为是一个 服务发现 的问题。为了解决这个问题,可以通过如下图所示的三个方案

服务发现.png

1. 允许访问所有的节点,如果第一个访问的节点有该键值,则处理该请求,否则将该请求转发到适当的节点上,这个方法避免了使用注册中心中间件,但是实现比较复杂
2. 使用分布式的协调服务,用户将所有的请求发送到路由层,由路由层将该请求转发到合适的节点
3. 要求用户(客户端)自己知道分区和节点的分配

但是这其中还隐藏着一个问题:作出决策的组件(节点之一、路由层或客户端)是如何了解数据在节点间的分配变化的?这就需要一个独立的协调服务,比如使用 zookeeper 来跟踪元数据,如下图所示

zookeeper管理路由元数据.jpeg

每个节点都会在 zookeeper 中进行注册,zookeeper 中维护有节点到各个分区的可靠映射,负责决策的组件在 zookeeper 中订阅这个消息。当分区分配发生改变时,zookeeper 就会通知负责决策的组件更新路由信息,使其保持在最新的状态。

除此之外也可以在各个节点间采用 流言协议 来传播集群状态的变化,这样每个节点都维护有最新的数据路由方案,当其中一个节点收到请求时,会将其转发到合适的分区节点上(对应服务发现的方案一)。

巨人的肩膀

  • 《数据密集型应用系统设计》:第六章 分区

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论