系统设计之分区策略

本文涉及的产品
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
简介: 对大数据集或非常高吞吐量,仅复制还不够,还需将数据拆分成为分区(partitions),也称分片(sharding)1。

对大数据集或非常高吞吐量,仅复制还不够,还需将数据拆分成为分区(partitions),也称分片(sharding)1。


术语澄清

分区 (partition),对应MongoDB、ES中的shard,HBase 的Region,Bigtable的tablet,Cassandra的vnode,Couchbase的vBucket。但分区 (partitioning)是最普遍的。


定义


每条数据(或每条记录,每行或每个文档)属于且仅属于某特定分区。每个分区都能视为一个完整小型数据库,虽然数据库可能存在跨分区操作。


目的


提高可扩展性。不同分区可放在一个无共享集群的不同节点。这样的一个大数据集可分散在更多磁盘,查询负载也随之分布到更多处理器。


单分区查询时,每个节点对自己所在分区查询可独立执行查询操作,添加更多节点就能提高查询吞吐量。大型复杂查询尽管比较困难,但也可能做到跨节点的并行处理。


分区数据库在 20 世纪 80 年代由 Teradata 和 NonStop SQL等产品率先推出,最近因NoSQL和基于Hadoop的数据仓库重新被关注。有些系统是为事务处理而设计,有些系统则用于分析:这种差异会影响系统的运作方式,但是分区的基本原理均适用于这两种工作方式。


在本章中,我们将首先介绍分割大型数据集的不同方法,并观察索引如何与分区配合。然后讨论rebalancing,若想添加、删除集群中的节点,则必须进行再rebalancing。最后,概述DB如何将请求路由到正确的分区并执行查询。


1 分区与复制


分区一般和复制搭配使用,即每个分区的多个节点都有副本。这意味着,某条记录属于特定的分区,而同样内容会存储在不同的节点上,以提高系统容错性。


一个节点可能存储多个分区。如图-1所示,主从复制模型和分区组合时数据的分布情况。每个分区都有自己的主副本,如被分配给某节点,而从库副本被分配给其他节点。一个节点可能是某些分区的主副本,同时也是其他分区的从副本。


上一个文章讨论的复制相关所有内容同样适用于分区数据的复制。考虑到分区方案的选择通常独立于复制,为简单起见,本文忽略复制相关内容。

8.png



2 KV数据的分区


海量数据想切分,如何决定在哪些节点上存储哪些记录?


分区的主要目标:将数据和查询负载均匀分布在各节点。若每个节点平均分担数据和负载,则理论上10个节点能处理10 倍的数据量和10 倍于单节点的读写吞吐量(暂忽略复制)。


但若分区不均,则会导致某些分区节点比其他分区有更多数据量或查询负载,即倾斜,这会导致分区效率下降很多。极端情况下,所有负载可能压在一个分区节点,其余9个节点空闲,系统瓶颈落在这最忙的节点。这时的高负载分区即是系统热点。


2.1 避免热点


最简单的,将记录随机分配给所有节点。这能在所有节点比较均匀分布数据,但缺点是:试图读取特定数据时,不知道保存在哪个节点,必须并行查询所有节点。


可以优化该方案。假设数据是简单的KV数据模型,即总能通过K访问记录。如在一本百科全书,可通过标题查找一个条目;而所有条目按字母序排序,因此能快速找到目标条目。


2.2 根据K的范围分区(Key Range分区策略)


一种分区方案,为每个分区指定一块连续的K范围(以min和max 指示),如纸质百科全书的卷(图-2)。若知道K区间的边界,就能轻松确定哪个分区包含这些K。若你还知道分区所在的节点,则可直接请求相应节点(就像从书架上选取正确书籍)。

7.png



K的区间不一定要均匀分布,因为数据本身可能就不均匀。如图-2中,1卷包含A、B开头的单词,但12卷则包含T、U、V、X、Y和Z开头单词。若只是简单规定每个卷包含两个字母,可能导致一些卷比其他卷大。为更均匀分布数据,分区的边界应适配数据本身的分布特征。


分区边界可由管理员手动确定或由DB自动选择。Bigtable及其开源版本HBase和2.4版本之前的MongoDB都采用该分区策略。


每个分区中,可按K排序保存。范围扫描就很简单,将K作为联合索引来处理,从而在一次查询中获取多个相关记录。假设有个程序存储网络传感器的数据,K是测量的时间戳(年月日-时分秒)。范围扫描此时很有用,可快速获取某月内的所有数据。


缺点

某些访问模式会导致热点。 若K是时间戳,则分区对应于一个时间范围,如每天一个分区。 测量数据从传感器写入DB时,所有写入操作都集中在同一分区(即当天的分区),导致该分区在写入时处于高负载,而其他分区始终空闲。


为避免该问题,需要使用时间戳之外的内容作为K的第一项。 可考虑每个时间戳前添加传感器名称,这样首先按传感器名称,再按时间进行分区。假设多个传感器同时运行,则写入负载最终会均匀分布在多个节点。 当想要获取一个时间范围内、多个传感器的数据,可根据传感器名称,各自执行单独的范围查询。


2.3 根据键的Hash分区


由于数据倾斜和热点问题,许多分布式系统采用基于K散列函数来分区。


好的散列函数可处理倾斜数据并使其均匀分布。


数据分区目的的hash函数无需健壮的加密能力,如Cassandra 和 MongoDB 使用 MD5。许多编程语言也有内置的简单哈希函数(主要用于哈希表),但可能不适合分区:如Java 的 Object.hashCode(),同一K可能在不同进程中有不同哈希值。


确定合适的hash函数后,就能为每个分区分配一个hash范围(而不是直接就是K的范围),每个K通过hash散列落在不同分区,如图-3:

6.png


这种方案擅长在分区之间均匀分配K。分区边界可以是均匀间隔,也可以是伪随机选择(也称为一致性哈希)。


一致性哈希

一种平均分配自己负载的方法,最初用于内容分发网络(CDN)等互联网缓存系统。 采用随机选择的分区边界来规避中央控制或分布式共识。此处的一致性与副本一致性或ACID一致性无任何关联 ,它只描述了数据动态平衡的一种方法。


正如“分区再平衡” 中所见,这种特殊分区方法对于DB实际上效果并非很好,所以目前很少使用(虽然某些DB的文档仍会使用一致性哈希说法,但其实不准确)。 因为有可能产生混淆,所以最好避免使用一致性哈希这个术语,而只是把它称为 散列分区(hash partitioning)。


但通过hash分区,失去高效的执行范围查询的能力:即使相邻的K,经过hash后也会分散在不同分区。MongoDB中,若使用hash分区,则范围查询都必须发送到所有分区。而Couchbase或Voldemort干脆直接不支持K的范围查询。


Cassandra在两种分区策略之间采取折中。 Cassandra的表可使用由多个列组成的复合主键。键中只有第一部分可用于 hash 分区,而其他列则被用作 Casssandra 的 SSTables 中排序数据的联合索引。尽管不支持复合主键的第一列的范围查询,但若第一列已指定固定值,则可对其他列执行高效的范围查询。


联合索引为一对多关系提供一个优雅的数据模型。如社交网站,一个用户可能发布很多消息更新。若更新的K被设置为 (user_id,update_timestamp),则能高效检索某用户在某时间段内,按时间戳排序的所有更新。不同用户可存储在不同分区,但对某一用户,消息会按时间戳顺序存储在同一分区。


2.4 负载偏斜与热点消除


hash分区可减少热点,但无法完全避免:极端情况下,所有读/写操作都是针对同一K,则所有请求都会被路由到同一分区。


这种负载也许不常见,但也并非不可能:如社交网站,一个坐拥百万粉丝的大V用户,发布一些热点事件时,可能引发一场访问风暴。导致同一个K的大量写操作(K可能是大V的用户ID或人们正在评论的事件ID)。此时,hash策略不起任何作用,因为两个相同ID的hash值仍相同。


如今,大多数据系统仍无法自动消除这种高度偏斜的负载,只能通过应用层来减少倾斜。如某K被确认为热点,简单方法是在K的开始或结尾添加一个随机数。只要一个两位数的十进制随机数就能将主键分散为100种不同的K,从而存储在不同分区。


但之后的任何读取都要做额外工作,必须从所有100个K分布中读取数据然后合并。因此通常只对少量热点K附加随机数才有意义;而对写吞吐量低的大多数K,这些都是不必要开销。此外,还需额外元数据来标记哪些K进行了特殊处理。


也许将来某天,数据系统将能自动检测和处理负载倾斜情况;但当下,仍需你自己来综合权衡策略。


分区是一种有意将大型数据库分解成小型数据库的方式。它与 网络分区(network partitions, netsplits) 无关,这是节点之间网络故障的一种。后文再讨论这些错误。 ↩︎

目录
相关文章
|
容器 微服务 Kubernetes
带你读《Istio入门与实战》之一:服务网格与Istio
本书系统化介绍Istio技术要点与应用技巧,可帮助读者快速搭建微服务架构并进行管理。主要内容包括:service mesh基本概念与使用,Istio架构设计与主要功能,快速搭建一个微服务实验,介绍如何让服务流量控制更简单,让服务更具弹性,让服务故障测试更容易,让服务通信更安全可控,让服务更易观测与监控,以及istio维护方案。本书内容丰富、案例讲解,实用性强,非常适合入门级读者快速掌握Istio技术。
|
11月前
|
消息中间件 监控 中间件
常用的消息队列中间件都有什么?优缺点是什么?如何选择?
常用的消息队列中间件都有什么?优缺点是什么?如何选择?
390 5
|
8月前
|
前端开发 Java 程序员
2025年了,PHP 还是“世界上最好的语言”吗?
“PHP是全世界最好的语言”源自2001年PHP官方文档,本为积极评价,后因PHP性能、安全等问题成为技术圈知名梗。Ruby调侃自己是程序员最好的朋友,其他语言如Go、Java、Python则低调介绍优势。前端CSS预处理语言Sass高调自称最成熟强大,Less则低调表示仅比CSS多一点。2025年TIOBE指数显示,PHP已跌至13名,Python位居第一。尽管PHP难回巅峰,但其早期辉煌仍值得怀念。
354 3
|
存储 安全 Java
Spring Security 6.x OAuth2登录认证源码分析
上一篇介绍了Spring Security框架中身份认证的架构设计,本篇就OAuth2客户端登录认证的实现源码做一些分析。
840 2
Spring Security 6.x OAuth2登录认证源码分析
|
11月前
|
监控 关系型数据库 MySQL
数据治理平台Datavines
【10月更文挑战第20天】随着数据量的增长和数字化转型的推进,数据治理成为关键议题。Datavines是一个开源的数据治理平台,提供数据目录、概览及质量检查等功能,帮助用户全面了解和管理数据,确保数据的准确性和有效性。通过简单的部署和配置,即可快速启动使用,支持数据源配置、质量监控及作业管理等核心功能。
2006 10
|
11月前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
403 5
|
12月前
|
存储 NoSQL 关系型数据库
什么是 CAP 理论和 BASE 理论,看这一篇就够了
什么是 CAP 理论和 BASE 理论,看这一篇就够了
554 12
|
存储 关系型数据库 MySQL
MySQL删除数据 文件大小不变的原因以及处理空洞问题
总之,MySQL中删除数据后文件大小不变的现象是由于InnoDB存储引擎的设计决策,旨在优化性能和空间的重用。处理这一问题需要综合考量数据库的使用场景以及可能的性能影响,选择合适的策略
1175 6
|
Java Linux Docker
【zookeeper 第二篇章】windows、linux、docker-compose 安装 zookeeper
本文介绍Zookeeper在不同环境下的安装方法。Linux安装需备好JDK,下载并解压Zookeeper后,复制`zoo_sample.cfg`为`zoo.cfg`,最后运行`zkServer.sh start`启动服务。Windows安装类似,通过`zkServer.bat`启动。使用Docker-Compose则需编写配置文件,并通过`docker-compose up -d`后台启动容器。
251 0
|
Kubernetes Perl 容器
在K8S中,flannel能固定节点IP和Pod的IP地址吗?
在K8S中,flannel能固定节点IP和Pod的IP地址吗?