DDIA 第二部分-7. 分片

0 阅读10分钟

如果我们不想让每个节点都存储所有数据,我们可以将大量数据分割成更小的 分片(shards) 或 分区(partitions),并将不同的分片存储在不同的节点上。

分片的利与弊

对数据库进行分片的主要原因是 可伸缩性:如果数据量或写吞吐量已经超出单个节点的处理能力,这是一个解决方案,它允许你将数据和写入分散到多个节点上。

分片是我们实现 水平扩展(横向扩展 架构)的主要工具之一,允许系统通过添加更多(较小的)机器而不是转移到更大的机器来增长其容量。

分片是一个重量级解决方案,主要在大规模场景下才有意义。如果你的数据量和写吞吐量可以在单台机器上处理(而单台机器现在可以做很多事情!),通常最好避免分片并坚持使用单分片数据库。

面向多租户的分片

使用分片实现多租户有几个优点:

资源隔离如果某个租户执行计算密集型操作,而它与其他租户运行在不同分片上,那么其他租户性能受影响的可能性更小。

权限隔离如果访问控制逻辑有漏洞,而租户数据集又是彼此物理隔离存储的,那么误将一个租户的数据暴露给另一个租户的概率会更低。

基于单元的架构在 基于单元的架构 中,特定租户集的服务和存储被分组到一个自包含的 单元 中,不同的单元被设置为可以在很大程度上彼此独立运行。这种方法提供了 故障隔离:即,一个单元中的故障仅限于该单元,其他单元中的租户不受影响。

按租户备份和恢复单独备份每个租户的分片使得可以从备份中恢复租户的状态而不影响其他租户

法规合规性数据隐私法规(如 GDPR)赋予个人访问和删除存储的所有关于他们的数据的权利。

数据驻留如果特定租户的数据需要存储在特定司法管辖区以符合数据驻留法律,具有区域感知的数据库可以允许你将该租户的分片分配给特定区域。

渐进式模式推出模式迁移可以逐步推出,一次一个租户。这降低了风险。

主要挑战是:

  • 它假设每个单独的租户都足够小,可以适应单个节点。
  • 如果你有许多小租户,那么为每个租户创建单独的分片可能会产生太多开销。
  • 如果你需要支持跨多个租户关联数据的功能,那么在必须跨多个分片做连接时,实现难度会显著增加。

键值数据的分片

如果分片不公平,使得某些分片比其他分片承载更多数据或查询,我们称之为 偏斜。偏斜会显著削弱分片效果。在极端情况下,所有负载都可能集中在一个分片上,导致 10 个节点中有 9 个处于空闲状态,而瓶颈落在那一个繁忙节点上。负载明显高于其他分片的分片称为 热分片 或 热点。如果某个键的负载特别高,我们称之为 热键。

按键的范围分片

一种分片方法是为每个分片分配一个连续的分区键范围(从某个最小值到某个最大值)分片边界可能由管理员手动选择,或者数据库可以自动选择它们。在每个分片内,键以排序顺序存储这样做的优点是范围扫描很容易,你可以将键视为连接索引,以便在一个查询中获取多个相关记录。

重新平衡键范围分片数据

当你首次设置数据库时,没有键范围可以分割成分片。一些数据库,如 HBase 和 MongoDB,允许你在空数据库上配置一组初始分片,这称为 预分割。这要求你已经对键分布将会是什么样子有所了解,以便你可以选择适当的键范围边界

按键的哈希分片

一个好的哈希函数可以把偏斜的数据变得更均匀。

哈希取模节点数

mod N 函数易于计算,但它导致非常低效的再平衡,因为存在大量不必要的记录从一个节点移动到另一个节点。我们需要一种不会移动超过必要数据的方法。

固定数量的分片

一个简单但广泛使用的解决方案是创建比节点多得多的分片,并为每个节点分配多个分片。

Legend:.png 在这个模型中,只有整个分片在节点之间移动,这比分割分片更便宜。分片的数量不会改变,也不会改变键到分片的分配。唯一改变的是分片到节点的分配。这种分配的变化不是立即的——通过网络传输大量数据需要一些时间——因此在传输进行时,旧的分片分配用于任何发生的读写。

选择分片数量为可被许多因子整除的数字是很常见的,这样数据集可以在各种不同数量的节点之间均匀分割,要你对首次创建数据库时需要多少分片有很好的估计,它就很有效。然后你可以轻松添加或删除节点,但受限于你不能拥有比分片更多的节点。

按哈希范围分片

如果无法提前预测所需的分片数量,最好使用一种方案,其中分片数量可以轻松适应工作负载。前面提到的键范围分片方案具有这个属性,但当有大量对相邻键的写入时,它有热点的风险。一种解决方案是将键范围分片与哈希函数结合,使每个分片包含 哈希值 的范围而不是 键 的范围。

一致性哈希

一致性哈希 算法是一种哈希函数,它以满足两个属性的方式将键映射到指定数量的分片:

  1. 映射到每个分片的键数大致相等,并且
  2. 当分片数量变化时,尽可能少的键从一个分片移动到另一个分片。

偏斜的工作负载与缓解热点

一致性哈希保证键在节点间大致均匀分布,但这并不等于实际负载也均匀分布。如果工作负载高度偏斜,即某些分区键下的数据量远大于其他键,或某些键的请求速率远高于其他键,那么你仍可能出现部分服务器过载、其他服务器几乎空闲的情况。

一些系统(特别是为大规模设计的云服务)有自动处理热分片的方法例如,Amazon 称之为 热管理 或 自适应容量 。

运维:自动/手动再平衡

自动分片管理也可能是不可预测的。再平衡是一项昂贵的操作,因为它需要重新路由请求并将大量数据从一个节点移动到另一个节点。如果操作不当,这个过程可能会使网络或节点过载,并可能损害其他请求的性能。系统必须在再平衡进行时继续处理写入;如果系统接近其最大写入吞吐量,分片分割过程甚至可能无法跟上传入写入的速率

请求路由

它与 服务发现 非常相似,两者之间最大的区别是,对于运行应用程序代码的服务,每个实例通常是无状态的,负载均衡器可以将请求发送到任何实例。对于分片数据库,对键的请求只能由包含该键的分片的副本节点处理。

请求路由必须知道键到分片的分配,以及分片到节点的分配。在高层次上,这个问题有几种不同的方法

  1. 允许客户端连接任何节点(例如,通过循环负载均衡器)。如果该节点恰好拥有请求适用的分片,它可以直接处理请求;否则,它将请求转发到适当的节点,接收回复,并将回复传递给客户端。
  2. 首先将客户端的所有请求发送到路由层,该层确定应该处理每个请求的节点并相应地转发它。这个路由层本身不处理任何请求;它只充当分片感知的负载均衡器。
  3. 要求客户端知道分片和分片到节点的分配。在这种情况下,客户端可以直接连接到适当的节点,而无需任何中介。

90 1o0,.png

在所有情况下,都有一些关键问题:

  • 谁决定哪个分片应该存在于哪个节点上?最简单的是有一个单一的协调器做出该决定,但在这种情况下,如果运行协调器的节点出现故障,如何使其容错?如果协调器角色可以故障转移到另一个节点,如何防止脑裂情况,其中两个不同的协调器做出相互矛盾的分片分配?
  • 执行路由的组件(可能是节点之一、路由层或客户端)如何了解分片到节点分配的变化?
  • 当分片从一个节点移动到另一个节点时,有一个切换期,在此期间新节点已接管,但对旧节点的请求可能仍在传输中。如何处理这些?

许多分布式数据系统依赖于单独的协调服务(如 ZooKeeper 或 etcd)来跟踪分片分配

分片与二级索引

二级索引的难点在于,它们不能整齐地映射到分片。带二级索引的分片数据库主要有两种做法:本地索引与全局索引。

本地二级索引

在这种索引方法中,每个分片是完全独立的:每个分片维护自己的二级索引,仅覆盖该分片中的文档。它不关心存储在其他分片中的数据。每当你需要写入数据库——添加、删除或更新记录——你只需要处理包含你正在写入的文档 ID 的分片。出于这个原因,这种类型的二级索引被称为 本地索引。在信息检索上下文中,它也被称为 文档分区索引

ddia_0709.png

全局二级索引

ddia_0710.png

这种索引也称为 基于词项分区:词项 是你可以搜索的文本中的关键字。

全局索引的优点是,只有一个查询条件时(如 color = red),只需从一个分片读取即可获得倒排列表。但如果你不仅要 ID,还要取回完整记录,仍然必须去负责这些 ID 的各个分片读取。

尽管如此,在读吞吐量高于写吞吐量且倒排列表不太长的场景下,全局索引仍然很有价值。