《数据密集型应用系统设计》读书笔记整理(6)-- 数据分区


我们必须摆脱串行的限制。明确状态定义,提供优先级与属性描述。我们必须首先定义清楚关系,然后才是执行步骤。
——Grace Murray Hopper,管理方法以及未来的计算机

分区

分区通常这样定义,即每一条数据(记录、行或文档)只属于某个特定分区。采用数据分区的主要目的是提高可扩展性。不同的分区可以放在一个无共享集群的不同节点上。

分区通常与复制结合使用,即每个分区在多个节点都存有副本。同样的数据会保存在不同的节点上以提高系统的容错性。

如果分区不均匀,会出现某些分区比其它分区承担更多的数据量或查询负载,称之为倾斜。这种负载严重不成比例的分区即成为系统热点。

基于关键字区间分区

这种分区方式为每个分区分配一段连续的关键字或者关键字区间范围。同时为了更均匀地分布数据,分区边界理应适配数据本身的分布特征。然而这种分区的缺点是某些访问模式会导致热点,为避免此类问题,可能还要使用其它关键字来辅助。

基于关键字哈希值分区

通过合适的关键字哈希函数,可以为每个分区分配一个哈希范围,关键字根据其哈希值的范围划分到不同的分区中。

这种方法可以很好地将关键字均匀地分配到多个分区中。分区边界可以是均匀间隔,也可以是伪随机选择(此技术有时被称为一致性哈希,可以从哈希函数产生的数字范围里设置边界)。

但是,通过关键字哈希进行分区后,没有了良好的区间查询特性。基于哈希分区的方法可以减轻热点,但无法做到完全避免。目前暂时只能通过应用层来减轻倾斜程度。

分区与二级索引

二级索引通常不能唯一标识一条记录,而是用来加速特定值的查询。二级索引是关系数据库必备特性,在文档数据库也比较普遍,但许多键值存储还不支持。此外,二级索引也是Solr和Elasticsearch等全文索引服务器存在之根本。

有两种方法来支持对二级索引进行分区:基于文档的分区和基于词条的分区。

基于文档分区

每个分区完全独立,各自维护自己的二级索引,且只负责自己分区内的文档而不关心其它分区中数据。 每当需要写数据时,只需要处理包含目标文档ID的那个分区。因此文档分区索引也被称为本地索引。

当需要查询时,需要将查询发送到所有的分区,再合并所有返回的结果。这种查询分区数据库的方法有时也称为分散/聚集,查询代价较高,即使采用了并行查询,也容易导致读延迟显著放大。

MongoDB、Riak、Cassandra、Elasticsearch、SolrCloud、VoltDB都支持基于文档分区的二级索引。

基于词条的二级索引分区

我们可以对所有数据构建全局索引,而不是每个分区维护自己的本地索引,同时对此全局索引进行分区,可以和数据关键字采用不同的分区策略。此方案称为词条分区,以待查找的关键字本身作为索引。

基于词条的二级索引分区相比基于文档分区索引的优点是读取更高效,查询时只需要向包含词条的那个分区发出读请求。但全局索引缺点在于写入较慢且复杂,一个文档写入时,可能有多个二级索引,而二级索引的分区又可能在不同的节点上,由此会引入显著的写放大。对于词条分区来讲,这需要一个跨多个相关分区的分布式事务支持,性能会受到很大影响,所以实践中,对全局二级索引的更新往往是异步的。

分区再平衡

随着时间的推移,数据库可能出现的一些变化:

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

上述的变化都要求数据和请求可以从一个节点转移到另一个节点。这样的迁移负载的过程称为再平衡(或动态平衡)。分区再平衡通常至少需要满足下面几点:

  • 平衡之后,负载、数据存储、读写请求等应该在集群范围更均匀地分布
  • 再平衡执行过程中,数据库可以继续正常提供读写服务
  • 避免不必要的负载迁移,以加快动态再平衡,并尽量减少网络和磁盘I/O影响。

动态再平衡的策略

为什么不用取模?对节点数取模的方法问题是,如果节点数N发生了变化,会导致很多关键字需要从现有的节点迁移到另一个节点。大量的迁移操作大大增加了再平衡的成本。

固定数量的分区

首先创建远超实际节点数的分区数,再为每个节点分配多个分区。接着如果集群中添加了一个新节点,该新节点可以从每个现有的节点上匀走几个分区,直到分区再次达到全局平衡。如果从集群中删除节点,则采取相反的均衡措施。

分区在节点之间迁移,但分区的总数量仍维持不变,也不会改变关键字到分区的映射关系。这时要调整的是分区与节点的对应关系,在此期间旧的分区仍然可以接收读写请求。固定数量的分区使得相关操作非常简单。

原则上,也可以将集群中不同的硬件配置因素考虑进来,性能更强大的节点分配更多的分区,从而分担更多的负载。

目前,Riak、Elasticsearch、Couchbase、Voldemort都支持这种动态平衡方法。

动态分区

一些数据库如HBase和RethinkDB采用了动态创建分区。当分区的数量增长超过一个可配的参数阈值,它就拆分为两个分区。相反,大量数据被删除时,分区缩小到某个阈值以下,则将其与相邻分区进行合并。 动态分区的一个优点是分区数量可以自动适配数据总量。

采用动态分区策略,拆分和合并操作使每个分区的大小维持在设定的最小值和最大值之间,因此分区的数量与数据集的大小成正比关系。

按节点比例分区

Cassandra和Ketama采用了使分区数与集群节点数成正比关系的方案。换句话说,每个节点具有固定数量的分区。节点数不变时,每个分区的大小与数据集大小保持正比增长关系。节点数增加时,分区会调整变得更小。

自动与手动再平衡操作

分区动态再平衡有几种执行方式:自动执行、手动执行、系统生成建议方案再人工确认执行。让管理员介入到再平衡可能是个更好的选择,可以有效防止意外发生。

请求路由

客户端在访问数据时,需要知道连接哪个IP和端口,这属于服务发现问题,有几种处理策略:

  1. 允许客户端连接任意的节点,如果节点恰好有所请求的分区,则直接处理该请求,否则,将请求转发到下一个合适的节点,接收答复,并将答复返回给客户端。
  2. 将所有客户端的请求都发送到一个路由层,由后者负责将请求转发到对应的分区节点上。路由层本身不处理请求,仅充当一个分区感知的负载均衡器。
  3. 客户端感知分区和节点分配关系。此时,客户端可以直接连接到目标节点,而不需要任何中介。

以上几个策略的关键点在于作出路由决策的组件(可能是节点、路由层或客户端)需要知道分区与节点的对应关系和变化情况。

许多分布式数据系统依靠独立的协调服务(如ZooKeeper)跟踪集群范围内的元数据。每个节点向ZooKeeper注册自己,ZooKeeper维护了分区到节点的最终映射关系。为客户端作出路由决策的组件可以向ZooKeeper订阅此信息。当分区发生了改变,ZooKeeper就会主动通知订阅者使路由信息保持最新。

HBase、SolrCloud和Kafka使用ZooKeeper来跟踪分区分配情况,MongoDB依赖自己的配置服务器和mongos守护进程充当路由层。Cassandra和Riak在节点之间使用gossip协议来同步集群状态的变化。请求发送到任何节点,节点负责将其转发到目标分区节点,同上述策略1。


参考文献:
[1] (美)Martin Kleppmann. 数据密集型应用系统设计(赵军平,吕云松,耿煜,李三平 译)[M]. 北京:中国电力出版社,2018.

Published

Author

Levin

Category

Web Arch

Tags

web arch database book report
Disqus loading now...