DDIA(《数据密集型应用系统设计》)的阅读随笔:第6章 数据分区

DDIA(《数据密集型应用系统设计》)的阅读随笔:第6章 数据分区

文章目录

  • DDIA(《数据密集型应用系统设计》)的阅读随笔:第6章 数据分区
  • 前言
  • 一、分区
    • 1.分区基本方法
      • 1)按键范围分区
      • 2)按散列分区
    • 2. 一致性哈希
    • 3.动态分区
  • 二、分区再平衡
  • 三、请求路由和并行查询
    • 1 请求路由
    • 2 执行并行查询
  • 总结


前言

本章继续讨论分布式系统的可扩展性的设计,上一章讨论的是通过数据复制,在不同的节点通过数据副本达到冗余。本章进一步讨论,节点副本太大,为了提升读写效率就需要将数据进行分区,从而将读写在多个节点上并行执行。分区和复制通常在系统设计中也是组合使用的,如书中插图所示。
DDIA(《数据密集型应用系统设计》)的阅读随笔:第6章 数据分区_第1张图片


一、分区

1.分区基本方法

分区目标是将数据和查询负载均匀分布在各个节点上。如果每个节点公平分享数据和负载,那么理论上 节点和吞吐可以达到基本线性。

如果分区是不公平的,一些分区比其他分区有更多的数据或查询,我们称之为 偏斜(skew)。数据偏斜的存在使分区效率下降很多。在极端的情况下,所有的负载可能压在一个分区上,其余 9 个节点空闲的,瓶颈落在这一个繁忙的节点上。不均衡导致的高负载的分区被称为 热点(hot spot)。

避免热点最简单的方法是将记录随机分配给节点。这将在所有节点上平均分配数据,但是它有一个很大的缺点:当你试图读取一个特定的值时,你无法知道它在哪个节点上,所以你必须并行地查询所有的节点。

所以,目前系统设计上,分区常见的两种方法:

1)按键范围分区

其中键是有序的,并且分区拥有从某个最小值到某个最大值的所有键。排序的优势在于可以进行有效的范围查询,但是如果应用程序经常访问相邻的键,则存在热点的风险。

在这种方法中,当分区变得太大时,通常将分区分成两个子分区,动态地再平衡分区。

2)按散列分区

散列函数应用于每个键,分区拥有一定范围的散列。这种方法破坏了键的排序,使得范围查询效率低下,但可以更均匀地分配负载。

通过散列进行分区时,通常先提前创建固定数量的分区,为每个节点分配多个分区,并在添加或删除节点时将整个分区从一个节点移动到另一个节点。也可以使用动态分区。

两种方法搭配使用也是可行的,例如使用复合主键:使用键的一部分来标识分区,而使用另一部分作为排序顺序。

2. 一致性哈希

前面提到按散列进行分区,一个比较简单的实现是对于数据的键值通过mod节点数,进行分区。但这样一来,带来的一个问题是,节点的动态扩缩容,都会导致分区的全局变化,意味着所有的数据分片都会重新分配到不同的节点,带来大量的数据迁移成本。这个问题的解决,就是前面说的,采用散列范围区间,而不是直接取摸。一致性哈希这个技术,其实对于我来说比较熟系,毕竟搞网络出生,无论是cdn还是后来的ndn,一致性哈希都是一个基本技术。

书中给出了一个一致性哈希的实现:创建比节点更多的分区,并为每个节点分配多个分区。例如,运行在 10 个节点的集群上的数据库可能会从一开始就被拆分为 1,000 个分区,因此大约有 100 个分区被分配给每个节点。

现在,如果一个节点被添加到集群中,新节点可以从当前每个节点中 窃取 一些分区,直到分区再次公平分配。这个过程如 图 6-6 所示。如果从集群中删除一个节点,则会发生相反的情况。

只有分区在节点之间的移动。分区的数量不会改变,键所指定的分区也不会改变。唯一改变的是分区所在的节点。这种变更并不是即时的 — 在网络上传输大量的数据需要一些时间 — 所以在传输过程中,原有分区仍然会接受读写操作。 通过下图可以比较清晰的展示
DDIA(《数据密集型应用系统设计》)的阅读随笔:第6章 数据分区_第2张图片

3.动态分区

对于使用键范围分区的数据库,具有固定边界的固定数量的分区将非常不便:如果出现边界错误,则可能会导致一个分区中的所有数据或者其他分区中的所有数据为空。手动重新配置分区边界将非常繁琐。

出于这个原因,按键的范围进行分区的数据库(如 HBase 和 RethinkDB)会动态创建分区。当分区增长到超过配置的大小时(在 HBase 上,默认值是 10GB),会被分成两个分区,每个分区约占一半的数据。与之相反,如果大量数据被删除并且分区缩小到某个阈值以下,则可以将其与相邻分区合并。此过程与 B 树顶层发生的过程类似(请参阅 “B 树”)。

每个分区分配给一个节点,每个节点可以处理多个分区,就像固定数量的分区一样。大型分区拆分后,可以将其中的一半转移到另一个节点,以平衡负载。在 HBase 中,分区文件的传输通过 HDFS(底层使用的分布式文件系统)来实现。

动态分区的一个优点是分区数量适应总数据量。如果只有少量的数据,少量的分区就足够了,所以开销很小;如果有大量的数据,每个分区的大小被限制在一个可配置的最大值。

需要注意的是,一个空的数据库从一个分区开始,因为没有关于在哪里绘制分区边界的先验信息。数据集开始时很小,直到达到第一个分区的分割点,所有写入操作都必须由单个节点处理,而其他节点则处于空闲状态。为了解决这个问题,HBase 和 MongoDB 允许在一个空的数据库上配置一组初始分区(这被称为 预分割,即 pre-splitting)。在键范围分区的情况中,预分割需要提前知道键是如何进行分配的。

动态分区不仅适用于数据的范围分区,而且也适用于散列分区。从版本 2.4 开始,MongoDB 同时支持范围和散列分区,并且都支持动态分割分区。

按节点比例分区
通过动态分区,分区的数量与数据集的大小成正比,因为拆分和合并过程将每个分区的大小保持在固定的最小值和最大值之间。另一方面,对于固定数量的分区,每个分区的大小与数据集的大小成正比。在这两种情况下,分区的数量都与节点的数量无关。

二、分区再平衡

多主节点复制有意义的语境是在多数据中心下的。假如你有一个数据库,副本分散在好几个不同的数据中心(也许这样可以容忍单个数据中心的故障,或地理上更接近用户)。 使用常规的基于但主节点的复制设置,主库必须位于其中一个数据中心,且所有写入都必须经过该数据中心。 多主节点配置中可以在每个数据中心都有主库。 在每个数据中心内使用常规的主从复制;在数据中心之间,每个数据中心的主库都会将其更改复制到其他数据中心的主库中。

当然这里,作者给出了两个近似的场景,也可以理解为多主节点:一种是离线客户端,即应用程序在断网之后仍然需要继续工作,另一种是多人协作编写文档,如Etherpad和Google Docs。

这个场景下最主要考虑的就是写冲突的处理。 本质上写冲突其实目前并没有特别优的解决方法,书中给出的原则:处理冲突的最简单的策略就是避免它们:如果应用程序可以确保特定记录的所有写入都通过同一个主节点,那么冲突就不会发生。由于多主节点复制处理的许多实现冲突相当不好,避免冲突是一个经常推荐的方法。 对于冲突合并,最实用的方案还是最后写入胜利(LWW, last write wins)。当然判定最终写入,可以依据一个唯一的ID(例如,一个时间戳,一个长的随机数,一个UUID或者一个键和值的哈希),通常挑选最高ID的写入作为胜利者,并丢弃其他写入。

三、请求路由和并行查询

1 请求路由

将数据集分割到多个机器上运行的多个节点上,但是仍然存在一个悬而未决的问题:当客户想要发出请求时,如何知道要连接哪个节点?随着分区重新平衡,分区对节点的分配也发生变化。

这个问题可以概括为 服务发现(service discovery) ,它不仅限于数据库。任何可通过网络访问的软件都有这个问题,特别是如果它的目标是高可用性(在多台机器上运行冗余配置)。许多公司已经编写了自己的内部服务发现工具,其中许多已经作为开源发布。

书中给出了几种不同的方案,贴一下书中插图辅助理解

  1. 允许客户联系任何节点(例如,通过 循环策略的负载均衡,即 Round-Robin Load
    Balancer)。如果该节点恰巧拥有请求的分区,则它可以直接处理该请求;否则,它将请求转发到适当的节点,接收回复并传递给客户端。

  2. 首先将所有来自客户端的请求发送到路由层,它决定了应该处理请求的节点,并相应地转发。此路由层本身不处理任何请求;它仅负责分区的负载均衡。

  3. 要求客户端知道分区和节点的分配。在这种情况下,客户端可以直接连接到适当的节点,而不需要任何中介。
    DDIA(《数据密集型应用系统设计》)的阅读随笔:第6章 数据分区_第3张图片
    目前的工程实践中,采用中间件作为服务发现,是比较常用的方案,即上述的方案2

2 执行并行查询

对于用于分析的 MPP数据库产品在其支持的查询类型方面要复杂得多。一个典型的数据仓库查询包含多个连接,过滤,分组和聚合操作。 MPP 查询优化器将这个复杂的查询分解成许多执行阶段和分区,其中许多可以在数据库集群的不同节点上并行执行。涉及扫描大规模数据集的查询特别受益于这种并行执行。

总结

(后面补充一个脑图)

你可能感兴趣的:(系统设计,分布式系统,数据库,分布式)