分布式存储系统的数据分布算法

前言

分布式存储系统 面临着的首要问题,就是如何将 大量的数据 分布在 不一样的存储节点 上。不管上层接口是 KV 存储对象存储块存储、亦或是 列存储,在这个问题上大致是一致的。本文将介绍如何 分布式存储系统作数据分布目标 及可选的 方案,并试着总结和权衡他们之间的关系及。算法

正文

(一) 指标

这里假设 目标数据 是以 key 标识的 数据块对象。在一个包含 多个存储节点 的集群中,数据分布算法 须要为每个给定的 key 指定 一个多个 对应的 存储节点 负责,数据分布算法 有两个基本目标:负载均衡

  • 均匀性(Uniformity):不一样存储节点的 负载 应该 均衡
  • 稳定性(Consistency):每次一个 key 经过 数据分布算法 获得的 分布结果 应该保持基本稳定,即便再有存储节点发生变化的状况下。

能够看出,这两个目标在必定程度上是 相互矛盾 的。当有 存储节点增长或删除 时,为了保持稳定应该 尽可能少 的进行 数据的移动从新分配,而这样又势必会带来 负载不均衡。一样追求 极致均匀 也会致使较多的 数据迁移分布式

因此咱们但愿在这两个极端之间,找到一个点以得到合适的均匀性和稳定性。除了上述两个基本目标外,工程中还须要从如下几个方面考虑数据分布算法的优劣:性能

  1. 性能可扩展性:这个主要考虑的是算法相对于 存储节点规模时间复杂度。为了整个系统的可扩展性,数据分布算法不该该在集群规模扩大后显著的增长运行时间。
  2. 考虑节点异构:实际工程中,不一样 存储节点 之间可能会有很大的 性能容量差别,好的数据分布算法应该能很好的应对这种 异构,提供 加权的数据均匀
  3. 隔离故障域:为了 数据的高可用,数据分布算法应该为每一个 key 找到 一组存储节点,这些节点可能提供的是 数据的镜像副本,也多是相似 擦除码 的副本方式。数据分布算法应该尽可能 隔离 这些副本的故障域,如 不一样机房不一样机架不一样交换机不一样机器

(二) 演进

看完算法的评价指标后,接下来介绍一些可能的方案演进,并分析他们的优劣。这里假设 key 的值足够分散。优化

1. Hash

一个简单直观的想法是直接用 Hash 来计算,简单的以 Key 作 哈希对节点数取模。能够看出,在 key 足够分散的状况下,均匀性 能够得到,但一旦有 节点加入退出 时,全部的原有节点都会受到影响。稳定性 无从谈起。3d

2. 一致性Hash


一致性 Hash 能够很好的解决 稳定性问题,能够将全部的 存储节点 排列在收尾相接的 Hash 环上,每一个 key 在计算 Hash 后会 顺时针 找到先遇到的 存储节点 存放。而当有节点 加入退出 时,仅影响该节点在 Hash 环上 顺时针相邻后续节点。但这有带来 均匀性 的问题,即便能够将存储节点等距排列,也会在 存储节点个数 变化时带来 数据的不均匀。而这种可能成倍数的不均匀 在实际工程中是不可接受的。orm

3. 带负载上限的一致性Hash

一致性 Hash 有 节点变化时不均匀的问题。Google 在 2017 年提出了 Consistent Hashing with Bounded Loads 来控制这种 不均匀的程度。简单的说,该算法给 Hash 环上的每一个节点一个 负载上限 为 1 + e 倍的 平均负载,这个 e能够自定义。当 key 在 Hash 环上 顺时针找到合适的节点后,会判断这个节点的 负载 是否已经 到达上限,若是 已达上限,则须要继续找 以后的节点 进行分配。cdn


如上图所示,假设每一个桶 当前上限 是 2,红色的小球按序号访问,当编号为 6 的红色小球到达时,发现顺时针首先遇到的 B(3,4),C(1,5)都已经 达到上限,所以最终放置在桶 A 里。对象

这个算法最吸引人的地方在于 当有节点变化 时,须要迁移的数据量是 1/e^2 相关,而与 节点数数据数量 均无关。blog

也就是说当 集群规模扩大 时,数据迁移量 并不会随着显著增长。另外,使用者能够经过调整 e 的值来控制 均匀性稳定性 之间的权衡,就是一种 以时间换空间 的算法。整体来讲,不管是 一致性 Hash 仍是 带负载限制一致性 Hash,都没法解决 节点异构 的问题。

4. 带虚拟节点的一致性Hash

为了解决 负载不均匀异构 的问题,能够在 一致性 Hash 的基础上引入 虚拟节点。即 hash 环上的 每一个节点 并非 实际存储节点,而是一个 虚拟节点。实际的 存储节点 根据其 不一样的权重,对应 一个多个虚拟节点,全部落到相应虚拟节点上的 key 都由该 存储节点负责

以下图所示,存储节点 A 负责 (1,3],(4,8],(10, 14],存储节点 B 负责 (14,1],(8,10]。


这个算法的问题在于,一个 实际存储节点加入退出,会影响 多个虚拟节点的从新分配,进而引发 不少节点 参与到 数据迁移 中来。

另外,实践中将一个 虚拟节点 从新分配给 新的实际节点 时,须要将这部分数据 遍历 出来 发送给新节点。咱们须要一个更合适的 虚拟节点切分分配方式,那就是 分片

5. 分片

分片哈希环 切割为 相同大小的分片,而后将这些 分片 交给 不一样的节点 负责。

注意这里跟上面提到的 虚拟节点 有着很 本质的区别分片的划分和分片的分配被解耦

一个 节点退出 时,其所负责的 分片 并不须要 顺时针合并 给以后节点,而是能够更灵活的 将整个分片 做为一个 总体 交给 任意节点。在实践中,一个 分片 多做为 最小的数据迁移备份单位


而也正是因为上面提到的 解耦,至关于将原先的 key 到 节点映射 拆成了两层。须要一个新的机制 来进行 分片存储节点映射。因为 分片数 相对 key 空间已经很小而且 数量肯定,能够更精确地初始设置,并引入 中心目录服务 来根据 节点存活 修改 分片的映射关系。同时将这个 映射信息 通知给全部的 存储节点客户端


上图是 分布式KV存储 Zeppelin中的 分片方式,Key Space 经过 Hash 到 分片分片及其副本 又经过一层映射到 最终的存储节点 Node Server。

6. CRUSH算法

CRUSH 算法本质上也是一种 基于分片 的数据分布方式,其试图在如下几个方面进行优化:

  • 分片映射信息量:避免 中心目录服务存储节点客户端之间 交互大量的 分片映射信息,而改由 存储节点客户端 本身根据 少许稳定 的集群节点拓扑和肯定的规则本身计算分片映射。
  • 完善的故障域划分:支持 层级故障域控制,将 同一分片不一样副本 按照配置划分到 不一样层级故障域中

客户端存储节点 利用 key、存储节点拓扑结构分配算法,独立的进行 分片位置 的计算,获得一组负责对应 分片副本存储位置

如图所示是 一次定位 的过程,最终选择了一个 row 下的 cab21,cab23,cab24 三个机柜下的三个存储节点。


节点变化 时,因为 节点拓扑 的变化,会影响 少许分片 数据进行迁移,以下图是加入 新节点 引发的 数据迁移。经过良好的 分配算法,能够获得很好的 负载均衡稳定性,CRUSH 提供了 Uniform、List、Tree、Straw 四种分配算法。

(三) 应用案例

常见的 分布式存储系统 大多采用相似于 分片数据分布和定位方式

  1. Cassandra/Dynamo:采用 分片 的方式并经过 Gossip 协议在对等节点间通讯;
  2. Redis Cluster:将 key Space 划分为 slots,一样利用 Gossip 协议通讯;
  3. Zeppelin:将数据分片为 Partition,经过 Meta 集群提供 中心目录服务
  4. Bigtable:将数据切割为 Tablet,相似于可变的分片,Tablet Server 能够进行分片的切割,最终分片信息记录在 Chubby 中;
  5. Ceph:采用 CRUSH 方式,由 中心集群 Monitor 提供并维护 集群拓扑 的变化。
相关文章
相关标签/搜索