目录

7. 分区

数据分区

1. 分区

分区的存在为了存储海量数据集或者分摊非常大的查询压力。分区通常是这样定义的,每一条数据(记录)只属于某个特定分区。每个分区都可以视为一个完整的小型数据库。采用数据分区通过将负载分摊到更多的机器上来提高系统的可扩展性。分区和复制通常结合使用,即每个分区在多个节点上都存有副本。

分区的主要目的主要是将数据和查询负载均匀分布在所有节点上。这包括以下几个层面的含义:

  1. 数据的分区是均衡的,注意是每个分区包含的读写负载是均衡而不是物理上的数据量
  2. 每个节点包含的分区数是均衡的

如果分配不均,某些分区承担过多的负载,称之为倾斜。倾斜会导致分区效率严重下降。负载严重不成比例的分区即成为系统热点

避免热点最简单的方法是将记录随机分配到所有节点,但是当试图读取特定数据时,因为不知道数据保存的节点,不得不并行查询所有节点。而分区的难点就是如何在分区均匀和查询效率上进行权衡。

有关分区,记下来我们将介绍一下内容:

  1. 数据分区的方法
  2. 数据索引对分区的影响
  3. 分区的再均衡,即如何将分区对应到节点上
  4. 如何将请求路由到正确的分区

2. 键值数据的分区

首先我们来讨论键值数据模型,它有如下几种分区方法:

  1. 基于关键字区间分区:
  2. 基于关键字哈希值分区

2.1 基于关键字区间分区

基于关键字区间分区:

  • 方法: 为每个分区分配一段连续的关键字区间
  • 注意: 数据本身可能不均匀,分区边界需要适配数据本身的分布特征
  • 实践: 采用这种分区方式的数据有 Bigtable,HBase
  • 优点: 分区内可以按照关键字排序保存,可以轻松支持关键字的区间查询
  • 缺点: 应用程序经常访问与排序一致的某段关键字,就会出现热点,比如以天作为关键字的区间分区,会导致查询特定天的读负载集中在一个分区上

2.2 基于关键字哈希值分区

基于关键字哈希值分区,可以有效均衡负载,但是缺点是失去了良好的区间查询特性,区间查询不得不发送到所有分区上。

但是需要注意的基于哈希的分区不能完全避免倾斜,比如微博里的大V带来的消息洪峰。大多数系统都无法自动消除这种高度倾斜的负载,只能通过应用层来减少倾斜程度。

一个简单的方法是在主键的开始或结尾添加一个随机数。只要一个两位数的十进制随机数就可以将主键分散为100种不同的主键,从而存储在不同的分区中。但随之而来的问题就是,任何读取都要从所有 100 个分区中读取并合并。

3. 分区与二级索引

二级索引带来的挑战是它们不能一对一的映射到分区。有两种方法来支持对二级索引进行分区:

  1. 基于文档分区的二级索引
    • 方法: 每个分区独立维护自己的二级索引,而不关心其他分区中的数据
    • 优点: 数据的增删改查带来的索引更新也仅限于本分区内
    • 缺点: 基于二级索引的查询需要并行访问所有分区,然后合并所有结果
  2. 基于词条的二级索引分区
    • 方法: 构建全局索引,然后对索引页进行分区存储,索引分区的方式可以是基于区间或者哈希的
    • 优点: 读取更加高效,无须访问所有分区,因为可以定位索引所在分区,通过索引可以知道数据所在分区
    • 缺点:
      • 写入慢且非常复杂,首先单文档更新会涉及多个二级索引,多个二级索引可能在不同的分区甚至不同节点上,必然引入显著的写放大
      • 理想情况下,索引应该与数据同步更新,这需要一个跨多个分区的分布式事务支持写入速度非常慢,所以现有的数据库都不支持同步更新二级索引

4. 分区再均衡

查询压力增加、数据规模增加、节点故障和添加都会导致数据和请求从一节点转移到另一个节点。此时我们就需要动态调整节点的负载称为分区再均衡。分区再均衡涉及到分区数据的迁移是一个成本很高的操作。

将分区对应到节点上有多种分配方法,包括:

  1. 固定数量分区: 有确定的分区数
  2. 动态分区: 每个分区有固定范围的的大小
  3. 按节点比例分区: 每个节点有固定数量的分区

分区在均衡是一个复杂的操作,特别是自动化再均衡和自动故障检测结合时可能会导致级联式失效扩散。因此让管理员介入到再均衡可能是个更好的选择。

5. 请求路由

分区的请求路由属于经典的服务发现问题,任何通过网络访问的系统都有这样的问题。即后台的服务,端口,数据等发生变化时,如何通知客户端做出对应的更改。这个问题有以下几个不同的处理策略:

  1. 允许客户端链接任意的节点,由节点负责寻找合适的节点
  2. 将所有客户端请求发送到一个路由层,路由层仅是一个分区感知的负载均衡器
  3. 客户端感知分区和节点的分配关系

无论哪种方法,问题的核心是:做出路由决策的组件,如何知道分区与节点的对应关系,以及变化情况。需要在所有节点间达成共识。

许多分布式数据系统依赖独立的协调服务,比如 Zookeeper 跟踪集群范围内的元数据,Zookeeper 维护了分区到节点的最终映射关系。其他参与者可以向 Zookeeper 订阅此消息。一旦分区发生变化,Zookeeper 主动通知路由层。

Cassandra 和 Riak 则在节点之间使用 gossip 协议同步集群状态的变化。请求可以发送到任意节点,有该节点转发到目标分区节点。

当使用路由层或向随机节点发送请求时,客户端仍然需要找到要连接的IP地址。这些地址并不像分区的节点分布变化的那么快,所以使用DNS通常就足够了。

理论上每个分区基本保持独立运行,这也是为什么我们试图将分区数据库分布扩展到多台机器上。但是如果写入需要跨多个分区,情况会格外复杂。比如一个分区写入成功,另一个发生错误,这时我们就需要事务为我们提供更加强的一致性保证。