数据系统分区设计 - 分区再平衡(rebalancing)

2022-07-25 14:32:49 浏览数 (1)

随业务井喷,DB出现变化:

  • 查询负载增加,需更多CPU处理负载
  • 数据规模增加,需更多磁盘和内存来存储
  • 节点可能故障,需要其他节点接管失效节点

所有这些更改都要求数据、请求可以从一个节点转移到另一个节点。 将负载从集群中的一个节点向另一个节点移动的过程称为 再平衡(rebalancing)。无论哪种分区策略,分区rebalancing通常至少要满足:

  • rebalancing后,负载、数据存储、读写请求应在集群内更均匀分布
  • rebalancing执行时,DB应能继续正常读写
  • 避免不必要的负载迁移,以加速rebalancing效率,并尽量减少网络和磁盘 I/O 影响

4.1 再平衡策略

4.1.1 反面教材:hash mod N

图-3提过,最好将hash值分成不同区间范围,然后每个区间分配给一个分区。

那为何不使用mod(Java中的%运算符)。如hash(key) mod 10 返回介于 0 和 9 之间的数字。若有 10 个节点,编号为 0~9,这似乎是将每个K分配给一个节点的最简单方法。

但问题是,若节点数量 N 变化,大多数K将需从一个节点移动到另一个。假设 hash(key)=123456 。最初10个节点,则该K一开始在节点6(因为123456 mod 10 = 6):

  • 当增长到 11 个节点,K需移动到节点 3(
123456 mod 11 = 3

  • 当增长到 12 个节点,K需要移动到节点 0(
123456 mod 12 = 0

这频繁的迁移大大增加rebalancing的成本。

所以重点是减少迁移的数据。

4.1.2 固定数量的分区

还好有个很简单的解决方案:创建比节点更多的分区,并为每个节点分配多个分区。如10 个节点的集群,DB可能会从一开始就逻辑划分为 1,000 个分区,因此大约有 100 个分区分配给每个节点。

若一个新节点加入集群,新节点可以从当前每个节点中窃取一些分区,直到分区再次达到全局平衡。过程如图-6。若从集群中删除一个节点,则会发生相反情况。

选中的整个分区会在节点之间迁移,但分区的总数不变,K到分区的映射关系也不变。唯一变的是分区所在节点。这种变更并非即时,毕竟在网络上传输数据总需要时间,所以在传输过程中,旧分区仍可接收读写操作。

原则上,也可以将集群中的不同的硬件配置因素考虑进来:性能更强大的节点分配更多分区,从而能分担更多负载。在ES 、Couchbase中使用了这种动态平衡方法。

使用该策略时,分区数量通常在DB第一次建立时确定,之后不会改变。虽然原则上可拆分、合并分区,但固定数量的分区使得操作都更简单,因此许多固定分区策略的 DB 决定不支持分区拆分。因此,初始化时的分区数就是你能拥有的最大节点数量,所以你要充分考虑将来业务需求,设置足够大的分区数。但每个分区也有额外管理开销,选择过高数字也有副作用。

若数据集的总规模难预估(如可能开始很小,但随时间推移会变异常得大),此时,选择合适的分区数就很难。由于每个分区包含的数据量上限是固定的,因此每个分区的实际大小与集群中的数据总量成正比:

  • 若分区里的数据量很大,则再平衡和从节点故障恢复的代价就很大
  • 若分区太小,则会产生太多开销

分区大小应“恰到好处”,若分区数量固定了,但总数据量却变动很大,则难以达到最佳性能。

4.1.3 动态分区

对于使用K范围分区的DB,若边界设置有问题,可能导致所有数据都挤在一个分区而其他分区基本为空,则设定固定边界、固定数量的分区将很不便:而手动去重新配置分区边界又很繁琐。

对此,K范围分区的DB,如HBase采用动态创建分区:

  • 当分区的数据增长超过配置的阈值(HBase默认10GB),就会拆分成两个分区,每个承担一半数据量
  • 相反,若大量数据被删除,并且分区缩小到某阈值以下,则将其与相邻分区合并

这有些类似B树的分裂过程。

每个分区分配给一个节点,而每个节点可承载多个分区,和固定数量的分区一样。大分区拆分后,可将其中一半转移到另一个节点,以平衡负载。HBase中,分区文件的传输通过 HDFS实现。

动态分区的一个优点,分区数量可自动适配数据总量:

  • 若只有少量数据,少量分区就够,开销也很小
  • 若有大量数据,每个分区的大小则被限制在一个可配的最大值

但一个空DB,因为没有确定分区边界的先验信息,所以会从一个分区开始。数据集开始时可能很小,直到达到第一个分区的分裂点前,所有写操作都必须由单节点处理,而其他节点则处于空闲状态。为解决该问题,HBase、MongoDB允许在一个空DB配置一组初始分区(预分割,pre-splitting)。在K范围分区策略下,预分割需要提前知道K的分布情况。

动态分区不仅适于K的范围分区,也适用于hash分区。MongoDB 2.4 开始同时支持范围和hash分区,且都支持动态分割分区。

4.1.4 按节点比例分区
  • 动态分区策略,分区数与数据集大小成正比,因为拆分、合并过程使每个分区的大小维持在固定的min和max之间
  • 固定数量的分区方式,每个分区的大小与数据集大小成正比

两种情况下,分区数都和节点数无关。

Cassandra则采用第三种方案,使分区数与集群节点数成正比。即每个节点具有固定数量的分区。此时,每个分区的大小和数据集大小成正比,而节点数不变,但是当增加节点数时,分区将再次变小。由于较大数据量通常需大量节点来存储,因此这种方法也使每个分区的大小保持稳定。

当一个新节点加入集群时,它随机选择固定数量的现有分区进行拆分,然后拿走这些分区的一半数据量,将另一半数据留在原节点。随机选择可能产生不公平的分区分割,但平均分区数较大时(Cassandra默认每个节点有256个分区),新节点最终会从现有节点获得相当数量的负载。 Cassandra 3.0引入优化算法,可避免不公平的分割。

随机选择分区边界要求使用hash分区策略(可从hash函数产生的数字范围中设置边界)。这种方法也最符合一致性哈希的定义。

4.2 运维:手动 or 自动再平衡

动态是自动还是手动执行?

全自动的再平衡(即由系统自动决定,何时将分区从一个节点迁移到另一个节点,无须人工干预)和完全手动(即分区到节点的映射由管理员显式配置)之间有个权衡。如Couchbase会自动生成一个推荐的分区分配,但需管理员确认生效。

全自动再平衡更方便,正常维护之外操作工作很少,但可能不可预测。再平衡是个昂贵操作,因其需重新路由请求,并将大量数据从一个节点迁移到另一个节点。若出现异常,可能会使网络或节点的负载过重,并降低其他请求的性能。

自动平衡和自动故障检测相结合也可能存在风险。假设某节点过载,且对请求的响应暂时很慢,而其他节点得出结论:过载节点已失效,并自动平衡集群,转移其负载。客观上,这会加重该节点、其他节点和网络的负载,从而使情况更糟,甚至级联失效。

对此,再平衡过程中有人参与是更推荐做法。这比全自动响应慢一点,但可有效防止意外。

0 人点赞