对于大数据集或高吞吐量情况,仅复制数据不够,需进行分区(也叫分片)
分区主要为了可扩展性,可将不同分区放于不共享集群的不同节点上,实现大数据集分布在多磁盘、查询负载分布在多处理器
分区与复制
每个节点可能是某些分区的领导者,同时是其他分区的追随者
键值数据的分区
将数据和查询负载均匀分布在各个节点上,若能实现公平分配,理论上多个节点可按比例提升处理数据量和读写吞吐量(暂不考虑复制)。
分区不均问题
- 偏斜:若分区不公平,部分分区数据或查询量多于其他分区,即出现数据偏斜,这会大幅降低分区效率。
- 热点:极端情况下,所有负载集中于一个分区,形成热点,其余节点空闲,导致性能瓶颈出现在该繁忙节点上。
避免热点的方法及弊端
- 随机分配记录:将记录随机分配给节点可平均分配数据,但存在明显缺点,即读取特定值时无法确定其所在节点,需并行查询所有节点。
- 基于主键访问记录的设想:假设采用简单键值数据模型,通过主键访问记录,就像在按字母顺序排序的纸质百科全书中通过标题查找条目那样
根据键的范围分区
- 为每个分区指定一块连续的键范围,但可能分布不均匀
- 优点:范围扫描简单,可以将键作为联合索引来处理,以此在一次查询中获取多个相关记录
- 缺点:某些特定访问模式会导致热点,可以通过在除此键之外的其他作为键的第一部分,避免数据打到同一分区
根据键的散列分区
- 使用散列函数来分区,保证同一个键在不同的进程中有相同的哈希值
- 优点:均匀分区
- 缺点:不能范围查询
- 组合索引:表可使用由多个列组成的复合主键声明,仅第一列作为散列依据,其他列作排序数据的连接索引。虽无法在复合主键第一列按范围扫表,但指定第一列固定值后,可对其他列执行有效范围扫描
负载偏斜和热点消除
- 热带消除:若主键可能引发高热度读写,可在主键开头或结尾添加随机数,如两位数的十进制随机数能将主键分散为 100 种不同形式,使其存储在不同分区。
- 存在问题:对主键进行分割后,读取操作需做额外工作,要从所有分散的主键分布中读取数据并合并。而且此技术会增加额外记录,仅对少量热点主键添加随机数较为合理,对绝大多数写入吞吐量低的主键而言是不必要开销,还需有方法跟踪哪些键需被分割。
分区与次级索引
二级索引:不能唯一的标识记录,而是一种搜索记录中出现特定值的方式
存在问题
- 二级索引的问题是他们不能整齐的映射到分区
- 两种用二级索引对数据库进行分区的方法:基于文档的分区和基于关键词的分区
基于文档的二级索引进行分区
特点
- 分区完全独立:每个分区维护自己的二级索引,仅覆盖该分区中的文档,只需处理包含该文档ID的分区即可
- 文档分区索引也称本地索引(local index)
- 查询时需要查询所有分区,合并所有的结果返回
缺点
- 查询分区数据库的方法有时称为分散或聚焦
- 二级索引查询代价较高,即使并行查询分区,分散/聚焦容易导致尾部延迟放大
MongoDB、Riak、Elasticsearch、VoltDB等都使用文档分区二级索引
根据关键词Term的二级索引
- 构建一个覆盖所有分区数据的全局索引,对全局索引也进行分区,称为关键词分区,通过关键词本身(范围扫描有优势)或者它的散列进行索引分区(负载均衡有优势)
- 优点:读取更有效率:不需要手机/分散所有分区,客户端只需要想包含关键词的分区发出请求
- 缺点
- 写入速度减慢且较为复杂,因为写入单个文档会影响索引的所个分区(文档中的每个关键词可能位于不同的分区或者不同的节点上)
- 需要跨分区的分布式事务
- 实践中,对全局二级索引的更新通常是异步的,即写入后不久读取索引,所做更改可能尚未反映在索引中
分区再平衡
再平衡:将负载从集群中的一个节点向另一个节点移动的过程
要求:
- 再平衡后,负载(数据存储、读取和写入请求)应该在集群中的节点之间公平的共享
- 再平衡发生时,数据库应该继续接受读取和写入
- 节点之间之移动必须的数据,以便快速再平衡,并减少网络和磁盘I/O负载
平衡策略
hash mod N
节点数目变化时,需对所有进行mod运算,再平衡过于昂贵
固定数量的分区
- 创建比节点更多的分区,并为每个节点分配多个分区
- 新节点加入,可以从当前每个节点中窃取一些分区,直到分区再次公平分配
- 只有分区在节点之间移动,分区数量不改变,key所指定的分区也不改变,只改变分区所在节点
- 为更强大的节点分配更多的分区,强制这些节点承载更多的负载。在Riak、Elasticsearch、Couchbase等中使用这种再平衡方法
- 分区数量在数据库第一次建立时确定,。但是每个分区有管理开销,选择太大的数字也会适得其反
- 分区的大小和集群中的数据总量成比例增长,分区非常大再平衡和从节点故障恢复很昂贵;分区非常小,会产生太多的开销。
- 问题:如果出现边界错误,可能导致一个分区中所有数据或其他分区中的所有数据为空
动态分区
- 动态分区:按键的范围进行分区的数据库,当分区增长到超过配置的大小,就会分为两个分区,每个分区约占一般的数据库;如果大量数据被删除并且分区缩小到某个阈值下,可以将其与相邻分区合并
- 一个节点可以处理多个分区,大型分区拆分后,可以将其中一半转移到另一分区来平衡负载。【HBase中,分区文件的传输通过HDFS实现】
- 优点:分区数量适应总数据量
- 问题:空数据库从一个分区开始,所有写入操作都由单个节点处理,其他节点处于空闲状态,通过预分割解决
- 预分割:在一个空的数据库上配置一组初始分区,但在键范围分区情况中,需提前知道键是如何进行分配的
- 动态分区适用场景:范围分区、散列分区
按节点比例分区
- 动态分区和固定数量分区,分区数量均与节点数量无关
- 按节点比例分区:每个节点具有固定数量的分区
- 节点数不变,分区大小与数据集大小成比例增长
- 节点数改变,分区大小将变小
- 新节点加入集群,随即选择固定数量的现有分区进行拆分,占有这些分区中每个分区的一般,另一半留在原地
- 虽然随机化可能导致不公平分割,但在较大数量分区的情况下(如 Cassandra 中默认每个节点有 256 个分区),新节点最终能从现有节点获得相对公平的负载份额。并且 Cassandra 3.0 还引入了另一种再分配算法来避免不公平分割。
- 随机选择分区边界需要基于散列的分区,即从散列函数产生的数字范围中挑选边界
运维:手动还是自动再平衡
- 完全自动再平衡缺点
- 如果一个节点过载,并且对请求的响应暂时很慢,这时自动再平衡,会对已经超负荷的节点,其他节点和网络造成额外的负载,从而使得结果更糟糕,可能导致级联失败
- 结果不可预测
请求路由
- 问题:分区重平衡后,分区对节点的分配发生变化,客户想要发出请求时,如何知道要连接哪个节点?
- 服务发现
- 允许客户连接任何节点(例如,通过循环策略的负载均衡Round-Robin Load Balancer),如果该节点有该分区,直接处理,否则将请求转发,并接受回复传递给客户端
- 将所有来自客户端的请求发送至路由曾,路由层进行相应转发,路由层仅负责分区的负载均衡
- 要求客户端知道分区和结点的分配,然后客户端直连
- 关键问题:路由层/客户端/节点如何知道分区-节点之间的分配关系变化
- 解决方案
- 利用Zookeeper等。Zookeeper维护分区到节点的可靠映射,参与者在Zookeeper订阅消息,分区分配发生改变,Zookeeper通知路由层,HBase、Kafak使用
** - gossip protocol:传播集群状态的变化。请求可以发送到任意节点,该节点会转发到包含所请求的分区的适当节点。增加了数据库节点之间的复杂性,但避免对外部依赖,Cassandra和Riak使用
- moxi路由层:从集群节点了解路有变化,Couchbase使用
- 利用Zookeeper等。Zookeeper维护分区到节点的可靠映射,参与者在Zookeeper订阅消息,分区分配发生改变,Zookeeper通知路由层,HBase、Kafak使用
执行并行查询
通常用于分析的大规模并行处理(MPP, Massively parallel processing) 关系型数据库产品在其支持的查询类型方面要复杂得多。一个典型的数据仓库查询包含多个连接,过滤,分组和聚合操作。
MPP查询优化器将这个复杂的查询分解成许多执行阶段和分区,其中许多可以在数据库集群的不同节点上并行执行。涉及扫描大规模数据集的查询特别受益于这种并行执行。