数据存储:分区、索引与平衡

分区数据库在20世纪80年代提出。分区是将大型数据库分解成小型数据库的方式。分区主要为了可扩展性,不同分区可以放在不共享集群中的不同节点上。因此,大数据集可以分布在多个磁盘上,并且查询可以负载在多个处理器上。

对于在单个分区上运行的查询,每个节点可以独立执行对自己的查询,因此可以通过添加更多节点来扩大查询吞吐量。大型,复杂的查询可能会跨越多个节点并行处理,但是这也带来了新的困难。

1 分区与复制

分区通常与复制结合使用,使得每个分区的副本存储在多个节点上。

一个节点可能存储多个分区,若使用主从复制模型,则每个节点都有一个领导者和几个追随者。

大多数情况下选择和复制方案是独立的,本章中忽略复制。

2 键值数据的分区

假设有大量数据并且想要分区,如何决定在哪些节点上存储哪些记录呢?

分区目标是将数据和查询负载均匀分布在各个节点上。如果每个节点公平分享数据和负载,那么理论上10个节点应该能够处理10倍的数据量和10倍的单个节点的读写吞吐量(暂时忽略复制)。

偏斜(skew) :分区不公平,一些分区数据更多。数据偏斜的存在使得分区效率下降。

热点(hot spot) :偏斜导致的不均衡的高负载,极端情况下所有的负载可能压在一个分区上。

避免热点:将记录随机分配给节点,在所有节点上上平均分配数据。缺点:当你读取一个特定的值时,不知道在哪个节点上,所以需要并行查询所有节点。

为了解决问题,我们可以使用简单的键值模型记录主键以访问记录。

2.1 根据键的范围分区

为每个分区指定一块连续的键范围,当我们知道范围之间的边界时,就能确定哪个分区包含哪个值(类似于B+Tree)。如果您还知道分区所在的节点,那么可以直接向相应的节点发出请求

分区边界可以由管理员手动选择或数据库自动选择。

键的范围不一定均匀分布,因为数据也很可能不均匀分布。每个分区中,可以按照一定的顺序保存键,这会使得范围扫描变简单。也可以将键作为联合索引来处理,以便一次查询多个相关记录。

缺点:某些特定的访问模式会导致热点。例如主键是时间戳时,所有写入都会放到今天的分区。

2.2 根据键的散列分区

许多分布式数据存储使用散列函数来确定给定键的分区,以应对偏斜和热点 ------ 一个好的散列函数可以将将偏斜的数据均匀分布。

散列函数不需要很强的加密算法,但是许多编程语言的内置hash算法不适合分区,例如Java的Object.hashCode()会导致同一个键在不同进程中有不同的哈希值。

这种技术擅长在分区之间分配键。分区边界可以是均匀间隔的,当分区边界是伪随机的时 此技术称为一致性哈希。

缺点:无法高效执行范围查询。由于顺序丢失,任何范围查询都需要发送到所有分区中(例如MongoDB)。Cassandra采取了折衷的做法,它的键中只有第一列会作为散列的依据。

2.3 负载倾斜与消除热点

哈希分区可以减少但不能完全避免热点。例如一个大v下面评论区突然开战,会导致大量写入同一个键(大v的ID等)。哈希策略不起作用,因为两个相同ID的哈希值是相同的。

大多数数据系统无法自动补偿偏斜,因此可以用应用程序减少偏斜。例如,对于少量的热点,可以在一个主键后增加随机数以取得不同的哈希值,以分散出不同的主键。

3 分片与次级索引

若只通过主键访问时,只需要确定分区并路由。但若设计次级索引,情况会变得复杂。

次级索引是关系型数据库的基础,文档数据库中也很普遍,许多键值存储为了减少实现的复杂度而放弃了次级索引,但是由于他们对于数据模型很有用,一部分键值存储的数据库开始加入这个功能。

次级索引的问题是它们不能整齐地映射到分区。有两种用二级索引对数据库进行分区的方法:基于文档的分区(document-based)和基于关键词(term-based)的分区

3.1 文档分区二级索引

每个分区完全独立,每个分区维护自己的二级索引,仅覆盖该分区中的文档。所以文档分区索引 也被称为本地索引

辅助索引通常并不能唯一地标识记录,而是一种搜索记录中出现特定值的方式:想让用户搜索汽车,允许他们通过颜色和厂商过滤,就需要一个在颜色和厂商上的次级索引(文档数据库中这些是字段(field) ,关系数据库中这些是列(column) )。

而在声明索引后,数据库就应该可以自动执行索引:无论何时将红色汽车添加到数据库,数据库分区都会自动将其添加到索引条目color:red的文档ID列表中。同时需要注意索引与底层数据不一致的问题。

我们一般不能将两个ID放在同个分区中,这导致当我们搜索时,需要将查询发送到所有分区,这种查询分区数据库的方法称为分散/聚集(scatter/gather),即并行查询分区,这会使二级索引查询变得昂贵,尾部延迟放大。

3.2 根据关键词(Term)的二级索引

构建一个覆盖所有分区数据的全局索引,然后将全局索引进行分区(可以采用与主键不同的分区方式),这就是关键词分区索引。

关键词来自全文搜索索引(一种特殊的次级索引),指文档中出现的所有单词。可以对关键词进行哈希分区以增加均衡负载能力。

优点:读取效率比文档分区索引高,不需要分散/收集所有分区,只需要向包含关键词的分区发出请求

缺点:写入慢且复杂,因为写入单个文档可能影响索引的多个分区。因此在实践中,对全局二级索引的更新通常是异步的,在写入后不久读取索引。

理想情况下,索引总是最新的。但是,这需要跨分区的分布式事务,并不是所有数据库都支持。在实践中,对全局二级索引的更新通常是异步的。

4 分区再平衡

随着时间的推移,数据库会有各种变化。

  • 查询吞吐量增加,需要添加更多的CPU来处理负载。
  • 数据集大小增加,需要添加更多的磁盘和RAM来存储。
  • 机器出现故障,其他机器需要接管故障机器的责任。

这些更改需要数据和请求从一个节点移动到另一个节点。负载在集群的节点中移动的过程称为再平衡(reblancing)

再平衡需要满足以下要求:

  • 再平衡之后,负载(数据存储,读取和写入请求)应该在集群中的节点之间公平地共享。
  • 再平衡发生时,数据库应该继续接受读取和写入。
  • 节点之间只移动必须的数据,以便快速再平衡,并减少网络和磁盘I/O负载。

4.1 平衡策略

固定数量的分区

创建比节点更多的分区,并为每个节点分配多个分区。当一个节点加入集群时,新节点从每个节点中拿一些分区直到分区平衡。(删除则相反)

分区的数量通常在数据库第一次建立时确定,虽然原则上可以分割和合并分区,但固定数量的分区操作更简单,因此,一开始配置的分区数就是你的最大节点数量。所以您需要选择合适的分区数量以适应未来的增长。

选择正确的分区数是困难的,如果分区数量固定,但数据量变动很大,则难以达到最佳性能。

动态分区

对于使用键范围分区的数据库,固定边界固定数量的分区,如果出现边界错误会很麻烦------分区数据可能会清空,但是手动重新配置分区边界会非常繁琐。所以,按键的范围进行分区的数据库会动态创建分区。

当分区增长到超过配置大小时,会被分为两个分区,此时可以将一个分区转移给另一个节点。相反,当分区数量缩小到某个阈值则会与相邻分区合并。因此,分区数量能适应总数据量。每个分区分配给一个节点,每个节点可以处理多个分区,大型分区拆分后,可以将其中一半转移给另一个节点以平衡负载。

预分割:数据集一开始很小的时候只有一个节点在处理操作,为了解决此问题,预分割允许在一个空数据库上配置一组初始分区。在键范围分区的情况下,预分割需要提前指定键是如何进行分配的。

动态分区也适用于散列分区,例如MongoDB同时支持动态分割的范围和哈希分区。

按节点比例分区

使分区数和节点数成正比,每个节点有固定数量的分区,那么每个分区的大小都于数据集大小成比例的增长,而节点数量保持不变。而当增加节点数时,分区变小。

当一个新节点加入集群时,随机选择固定数量的现有分区进行拆分,然后拿走每个选中的分区的一半。随机化会产生不公平的分割,但是平均在更大数量的分区上时,新节点会获得公平的负载份额。

4.2 运维:手动还是自动?

再平衡要手动还是自动进行呢?

全自动重新平衡很方便但不可预测。再平衡是个昂贵的操作,会使网络节点负载增加,降低其他请求的性能。

自动化再平衡和自动故障检测的结合十分危险,可能会造成级联失效(节点连锁的死亡):当发现节点过载响应慢时自动故障检测可能会认为节点已经死亡,并自动重新平衡集群,而再平衡使得其他节点负载增加并被判定为死亡。

因此,再平衡需要有人参与,虽然比自动化慢,但是可以防止运维出现意外。

5 请求路由

当客户发出请求时,如何知道连接哪个节点?这可以概括为服务发现问题。

方案:

  • 允许客户连接任何节点,如果该节点恰巧有请求的分区,则可以直接处理该请求。否则则将请求转发到适当的节点,接收回复并传递给客户端。
  • 将所有请求发送到路由层进行转发。路由层只负责分区的均衡负载。
  • 直接在客户端中配置分区和节点的路由。

关键问题是,做出路由决策的组件如何了解分区-节点之间的分配关系的变化?

分布式数据系统可以依赖于一个独立的协调服务,比如ZooKeeper。每个节点向ZooKeepper注册自己,路由层或客户端在ZooKeeper中订阅此信息,当分区或节点发生改变时,ZooKepper通知路由层使路由信息保持最新状态。

Cassandra和Riak在节点中使用流言协议来传播集群状态的变化。请求可以发送到任意节点,该节点会转发到目标分区的节点。这避免了对外部协调服务的依赖。

6 小结

本章讨论了大数据集怎么划分成小的子集。分区的目标是在多台机器上均匀分布数据和查询负载,避免出现热点。

分区主要有两种方法:键范围分区、散列分区。

次级索引页需要分区:按文档分区(本地索引)、按关键词分区(全局索引)。

相关推荐
青云交4 分钟前
大数据新视界 -- Hive 数据分区:提升查询效率的关键步骤(下)(8/ 30)
大数据·数据库·精细化管理·hive 数据分区·分区修剪·分区合并·缓存协同
重生之我是数学王子7 分钟前
QT简易项目 数据库可视化界面 数据库编程SQLITE QT5.12.3环境 C++实现
数据库·c++·qt
sevevty-seven13 分钟前
详细讲解MySQL中的默认索引(B+树)
数据库·b树·mysql
Object~15 分钟前
【第十一课】Rust并发编程(二)
开发语言·后端·rust
蓝天扶光19 分钟前
MySQL事务知识点梳理
数据库·mysql
.生产的驴25 分钟前
SpringBoot 接口加密SM2非对称加密算法 国密算法 公钥加密 私钥解密
java·spring boot·后端·spring·spring cloud·tomcat·gateway
Kika写代码32 分钟前
【大数据技术基础】 课程 第5章 HBase的安装和基础编程 大数据基础编程、实验和案例教程(第2版)
大数据·数据库·hbase
程序猿老罗39 分钟前
Flask 创建API接口服务
后端·python·flask
向阳121839 分钟前
Dubbo HTTP接入架构
http·架构·dubbo
Adolf_199339 分钟前
Flask 自定义路由转换器
后端·python·flask