MongoDB分片集群架构详解

分片简介

分片(shard)是指在将数据进行水平切分之后,将其存储到多个不同的服务器节点上的一种扩展方式。分片在概念上非常类似于应用开发中的“水平分表”。不同的点在于,MongoDB 本身就自带了分片管理的能力,对于开发者来说可以做到开箱即用。

为什么要使用分片?

MongoDB 复制集实现了数据的多副本复制及高可用,但是一个复制集能承载的容量和负载是有限的。在你遇到下面的场景时,就需要考虑使用分片了:

  • 存储容量需求超出单机的磁盘容量。
  • 活跃的数据集超出单机内存容量,导致很多请求都要从磁盘读取数据,影响性能。
  • 写 IOPS 超出单个 MongoDB 节点的写服务能力。

垂直扩容(Scale Up) VS 水平扩容(Scale Out):
垂直扩容:用更好的服务器,提高 CPU 处理核数、内存数、带宽等;
水平扩容:将任务分配到多台计算机上;

分片集群架构

MongoDB 分片集群(Sharded Cluster)是对数据进行水平扩展的一种方式。MongoDB 使用分片集群来支持大数据集和高吞吐量的业务场景。在分片模式下,存储不同的切片数据的节点被称为分片节点,一个分片集群内包含了多个分片节点。当然,除了分片节点,集群中还需要一些配置节点、路由节点,以保证分片机制的正常运作。

核心概念

  • 数据分片

分片用于存储真正的数据,并提供最终的数据读写访问。分片仅仅是一个逻辑的概念,它可以是一个单独的 mongod 实例,也可以是一个复制集。图中的 Shard1、Shard2 都是一个复制集分片。在生产环境中也一般会使用复制集的方式,这是为了防止数据节点出现单点故障。

  • 配置服务器(Config Server)

配置服务器包含多个节点,并组成一个复制集结构,对应于图中的 ConfigReplSet。配置复制集中保存了整个分片集群中的元数据,其中包含各个集合的分片策略,以及分片的路由表等。

  • 查询路由(mongos)

mongos 是分片集群的访问入口,其本身并不持久化数据。mongos 启动后,会从配置服务器中加载元数据。之后 mongos 开始提供访问服务,并将用户的请求正确路由到对应的分片。在分片集群中可以部署多个 mongos 以分担客户端请求的压力。

环境搭建

分片集群搭建

使用 mtools 搭建分片集群。

使用分片集群

为了使集合支持分片,需要先开启 database 的分片功能

sh.enableSharding("shop")

执行 shardCollection 命令,对集合执行分片初始化

sh.shardCollection("shop.product",{productId:"hashed"},false,{numInitialChunks:4})

shop.product 集合将 productId 作为分片键,并采用了哈希分片策略,除此以外,“numInitialChunks:4”表示将初始化 4 个 chunk。 numInitialChunks 必须和哈希分片策略配合使用。而且,这个选项只能用于空的集合,如果已经存在数据则会返回错误。

向分片集合写入数据

向 shop.product 集合写入一批数据

db=db.getSiblingDB("shop");
var count=0;
for(var i=0;i<1000;i++){
  var p=[];
  for(var j=0;j<100;j++){
    p.push({
      "productId":"P-"+i+"-"+j,
      name:"羊毛衫",
      tags:[
        {tagKey:"size",tagValue:["L","XL","XXL"]},
        {tagKey:"color",tagValue:["蓝色","杏色"]},
        {tagKey:"style",tagValue:"韩风"}
      ]
    });
  }
  count+=p.length;
  db.product.insertMany(p);
  print("insert ",count)
}

查询数据的分布

db.product.getShardDistribution()

分片策略

通过分片功能,可以将一个非常大的集合分散存储到不同的分片上。
假设这个集合大小是 1TB,那么拆分到 4 个分片上之后,每个分片存储 256GB 的数据。这个当然是最理想化的场景,实质上很难做到如此绝对的平衡。一个集合在拆分后如何存储、读写,与该集合的分片策略设定是息息相关的。在了解分片策略之前,我们先来介绍一下 chunk。

什么是 chunk

chunk 的意思是数据块,一个 chunk 代表了集合中的“一段数据”,例如,用户集合(db.users)在切分成多个 chunk 之后如图所示:
image.png
chunk 所描述的是范围区间,例如,db.users使用了 userId 作为分片键,那么 chunk 就是 userId 的各个值(或哈希值)的连续区间。集群在操作分片集合时,会根据分片键找到对应的 chunk,并向该 chunk 所在的分片发起操作请求,而 chunk 的分布在一定程度上会影响数据的读写路径,这由以下两点决定:

  • chunk 的切分方式,决定如何找到数据所在的 chunk
  • chunk 的分布状态,决定如何找到 chunk 所在的分片

分片算法

chunk 切分是根据分片策略进行实施的,分片策略的内容包括分片键和分片算法。当前,MongoDB 支持两种分片算法:

(1) 范围分片(range sharding)
假设集合根据 x 字段来分片,x 的完整取值范围为 [minKey, maxKey] (x 为整数,这里的 minKey、maxKey 为整型的最小值和最大值),其将整个取值范围划分为多个 chunk,例如:

  • chunk1 包含 x 的取值在 [minKey,-75) 的所有文档。
  • chunk2 包含 x 取值在 [-75,25) 之间的所有文档,依此类推。

范围分片能很好地满足范围查询的需求,比如想查询 x 的值在 [-30,10] 之间的所有文档,这时 mongos 直接将请求定位到 chunk2 所在的分片服务器,就能查询出所有符合条件的文档。范围分片的缺点在于,如果 Shard Key 有明显递增(或者递减)趋势,则新插入的文档会分布到同一个 chunk,此时写压力会集中到一个节点,从而导致单点的性能瓶颈。一些常见的导致递增的 Key 如下:

  • 时间值。
  • ObjectId,自动生成的 _id 由时间、计数器组成。
  • UUID,包含系统时间、时钟序列。
  • 自增整数序列。

(2)哈希分片(hash sharding)
哈希分片会先事先根据分片键计算出一个新的哈希值(64 位整数),再根据哈希值按照范围分片的策略进行 chunk 的切分。适用于日志,物联网等高并发场景。
哈希分片与范围分片是互补的,由于哈希算法保证了随机性,所以文档可以更加离散地分布到多个 chunk 上,这避免了集中写问题。然而,在执行一些范围查询时,哈希分片并不是高效的。因为所有的范围查询都必然导致对所有 chunk 进行检索,如果集群有 10 个分片,那么 mongos 将需要对 10 个分片分发查询请求。哈希分片与范围分片的另一个区别是,哈希分片只能选择单个字段,而范围分片允许采用组合式的多字段作为分片键。
哈希分片仅支持单个字段的哈希分片:

{ x : "hashed" }
{x : 1 , y : "hashed"} // 4.4 new

4.4 以后的版本,可以将单个字段的哈希分片和一个到多个的范围分片键字段来进行组合,比如指定 x:1,y 是哈希的方式。

分片标签

MongoDB 允许通过为分片添加标签(tag)的方式来控制数据分发。一个标签可以关联到多个分片区间(TagRange)。均衡器会优先考虑 chunk 是否正处于某个分片区间上(被完全包含),如果是则会将 chunk 迁移到分片区间所关联的分片,否则按一般情况处理。
分片标签适用于一些特定的场景。例如,集群中可能同时存在 OLTP 和 OLAP 处理,一些系统日志的重要性相对较低,而且主要以少量的统计分析为主。为了便于单独扩展,我们可能希望将日志与实时类的业务数据分开,此时就可以使用标签。
为了让分片拥有指定的标签,需执行 addShardTag 命令

sh.addShardTag("shard01","oltp")
sh.addShardTag("shard02","oltp")
sh.addShardTag("shard03","olap")

实时计算的集合应该属于 oltp 标签,声明 TagRange

sh.addTagRange("main.devices",{shardKey:MinKey},{shardKey:MaxKey},"oltp")

而离线计算的集合,则属于 olap 标签

sh.addTagRange("other.systemLogs",{shardKey:MinKey},{shardKey:MaxKey},"olap")

main.devices集合将被均衡地分发到 shard01、shard02 分片上,而other.systemLogs集合将被单独
分发到 shard03 分片上。

分片键(ShardKey)的选择

在选择分片键时,需要根据业务的需求及范围分片、哈希分片的不同特点进行权衡。一般来说,在设计分片键时需要考虑的因素包括:

  • 分片键的基数(cardinality),取值基数越大越有利于扩展。

以性别作为分片键 :数据最多被拆分为 2 份;
以月份作为分片键 :数据最多被拆分为 12 份;

  • 分片键的取值分布应该尽可能均匀。
  • 业务读写模式,尽可能分散写压力,而读操作尽可能来自一个或少量的分片。
  • 分片键应该能适应大部分的业务操作。

分片键(ShardKey)的约束

ShardKey 必须是一个索引。非空集合须在 ShardCollection 前创建索引;空集合 ShardCollection 自动创建索引。
4.4 版本之前:

  • ShardKey 大小不能超过 512 Bytes;
  • 仅支持单字段的哈希分片键;
  • Document 中必须包含 ShardKey;
  • ShardKey 包含的 Field 不可以修改。

4.4 版本之后:

  • ShardKey 大小无限制;
  • 支持复合哈希分片键;
  • Document 中可以不包含 ShardKey,插入时被当 做 Null 处理;
  • 为 ShardKey 添加后缀 refineCollectionShardKey 命令,可以修改 ShardKey 包含的 Field;

而在 4.2 版本之前,ShardKey 对应的值不可以修改;4.2 版本之后,如果 ShardKey 为非_id 字段,那么可以修改 ShardKey 对应的值。

数据均衡

均衡的方式

一种理想的情况是,所有加入的分片都发挥了相当的作用,包括提供更大的存储容量,以及读写访问性能。因此,为了保证分片集群的水平扩展能力,业务数据应当尽可能地保持均匀分布。这里的均匀性包含以下两个方面:

  1. 所有的数据应均匀地分布于不同的 chunk 上;
  2. 每个分片上的 chunk 数量尽可能是相近的;

其中,第 1 点由业务场景和分片策略来决定,而关于第 2 点,我们有以下两种选择:

  • 手动均衡

一种做法是,可以在初始化集合时预分配一定数量的 chunk(仅适用于哈希分片),比如给 10 个分片分配 1000 个 chunk,那么每个分片拥有 100 个 chunk。另一种做法则是,可以通过 splitAt、moveChunk 命令进行手动切分、迁移。

  • 自动均衡

开启 MongoDB 集群的自动均衡功能。均衡器会在后台对各分片的 chunk 进行监控,一旦发现了不均衡状态就会自动进行 chunk 的搬迁以达到均衡。其中,chunk 不均衡通常来自于两方面的因素:
一方面,在没有人工干预的情况下,chunk 会持续增长并产生分裂(split),而不断分裂的结果就会出现数量上的不均衡;
另一方面,在动态增加分片服务器时,也会出现不均衡的情况。自动均衡是开箱即用的,可以极大简化集群的管理工作。

chunk 分裂

在默认情况下,一个 chunk 的大小为 64MB(MongoDB6.0 默认是 128M),该参数由配置的 chunksize 参数指定。如果持续地向该 chunk 写入数据,并导致数据量超过了 chunk 大小,则 MongoDB 会自动进行分裂,将该 chunk 切分为两个相同大小的 chunk。
chunk 分裂是基于分片键进行的,如果分片键的基数太小,则可能因为无法分裂而会出现 jumbo chunk(超大块)的问题。例如,对db.users使用 gender (性别)作为分片键,由于同一种性别的用户数可能达到数千万,分裂程序并不知道如何对分片键(gender)的一个单值进行切分,因此最终导致在一个 chunk 上集中存储了大量的 user 记录(总大小超过 64MB)。
jumbo chunk 对水平扩展有负面作用,该情况不利于数据的均衡,业务上应尽可能避免。一些写入压力过大的情况可能会导致 chunk 多次失败(split),最终当 chunk 中的文档数大于1.3×avgObjectSize时会导致无法迁移。此外在一些老版本中,如果 chunk 中的文档数超过 250000 个,也会导致无法迁移。

自动均衡

MongoDB 的数据均衡器运行于 Primary Config Server(配置服务器的主节点)上,而该节点也同时会控制 chunk 数据的搬迁流程。
流程说明:

  • 分片 shard0 在持续的业务写入压力下,产生了 chunk 分裂。
  • 分片服务器通知 Config Server 进行元数据更新。
  • Config Serve r的自动均衡器对 chunk 分布进行检查,发现 shard0 和 shard1 的 chunk 数差异达到了阈值,向 shard0 下发 moveChunk 命令以执行 chunk 迁移。
  • shard0 执行指令,将指定数据块复制到 shard1。该阶段会完成索引、chunk 数据的复制,而且在整个过程中业务侧对数据的操作仍然会指向 shard0;所以,在第一轮复制完毕之后,目标 shard1 会向 shard0 确认是否还存在增量更新的数据,如果存在则继续复制。
  • shard0 完成迁移后发送通知,此时 Config Server 开始更新元数据库,将 chunk 的位置更新为目标 shard1。在更新完元数据库后并确保没有关联 cursor 的情况下,shard0 会删除被迁移的 chunk 副本。
  • Config Server 通知 mongos 服务器更新路由表。此时,新的业务请求将被路由到 shard1。

迁移阈值

(1)mongodb4.4 迁移条件
均衡器对于数据的“不均衡状态”判定是根据两个分片上的 chunk 个数差异来进行的。

chunk个数迁移阈值
少于202
20~794
80及以上8

https://www.mongodb.com/docs/v4.4/core/sharding-balancer-administration/

(2)mongodb6.0 迁移条件
如果碎片之间的数据差异(对于该集合)小于该集合配置范围大小的三倍,则认为该集合是平衡的。对于 128MB 的默认范围大小,对于给定的集合,两个分片必须具有至少 384MB 的数据大小差异,才能进行迁移。

https://www.mongodb.com/docs/v6.0/core/sharding-balancer-administration/

迁移速度

数据均衡的整个过程并不是很快,影响 MongoDB 均衡速度的几个选项如下:

  • _secondaryThrottle

用于调整迁移数据写到目标分片的安全级别。如果没有设定,则会使用w:2选项,即至少一个备节点确认写入迁移数据后才算成功。从 MongoDB 3.4 版本开始, _secondaryThrottle 被默认设定为 false,chunk 迁移不再等待备节点写入确认。

  • _waitForDelete

在 chunk 迁移完成后,源分片会将不再使用的 chunk 删除。如果 _waitForDelete 是 true,那么均衡器需要等待 chunk 同步删除后才进行下一次迁移。该选项默认为 false,这意味着对于旧 chunk 的清理是异步进行的。

  • 并行迁移数量

在早期版本的实现中,均衡器在同一时刻只能有一个 chunk 迁移任务。从 MongoDB 3.4 版本开始,允许 n 个分片的集群同时执行 n/2 个并发任务。

随着版本的迭代,MongoDB 迁移的能力也在逐步提升。从 MongoDB 4.0 版本开始,支持在迁移数据的过程中并发地读取源端和写入目标端,迁移的整体性能提升了约 40%。这样也使得新加入的分片能更快地分担集群的访问读写压力。

数据均衡带来的问题

数据均衡会影响性能,在分片间进行数据块的迁移是一个“繁重”的工作,很容易带来磁盘 I/O 使用率飙升,或业务时延陡增等一些问题。因此,建议尽可能提升磁盘能力,如使用 SSD。除此之外,我们还可以将数据均衡的窗口对齐到业务的低峰期以降低影响。
登录 mongos,在 config 数据库上更新配置,代码如下:

use config
sh.setBalancerState(true)
db.settings.update(
  {_id:"balancer"},
  {$set:{activeWindow:{start:"02:00",stop:"04:00"}}},
  {upsert:true}
)

在上述操作中启用了自动均衡器,同时在每天的凌晨 2 点到 4 点运行数据均衡操作。

对分片集合中执行 count 命令可能会产生不准确的结果,mongos 在处理 count 命令时会分别向各个分片发送请求,并累加最终的结果。如果分片上正在执行数据迁移,则可能导致重复的计算。替代办法是使用db.collection.countDocuments({})方法,该方法会执行聚合操作进行实时扫描,可以避免元数据读取的问题,但需要更长时间。
在执行数据库备份的期间,不能进行数据均衡操作,否则会产生不一致的备份数据。在备份操作之前,可以通过如下命令确认均衡器的状态:

sh.getBalancerState():查看均衡器是否开启。
sh.isBalancerRunning():查看均衡器是否正在运行。
sh.getBalancerWindow():查看当前均衡的窗口设定。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/303382.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Tensorflow2.0笔记 - 基本数据类型,数据类型转换

【TensorFlow2.0】(1) tensor数据类型&#xff0c;类型转换_tensorflow tensor转int-CSDN博客文章浏览阅读1.5w次&#xff0c;点赞8次&#xff0c;收藏28次。各位同学好&#xff0c;今天和大家分享一下TensorFlow2.0中的tensor数据类型&#xff0c;以及各种类型之间的相互转换方…

【OpenVINO 】在 MacOS 上编译 OpenVINO C++ 项目

前言 英特尔公司发行的模型部署工具OpenVINO™模型部署套件&#xff0c;可以实现在不同系统环境下运行&#xff0c;且发布的OpenVINO™ 2023最新版目前已经支持MacOS系统并同时支持在苹果M系列芯片上部署模型。在该项目中&#xff0c;我们将向大家展示如何在MacOS系统、M2芯片的…

③使用Redis缓存,并增强数据一致性。

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ 使用Redis缓存&#xff0c;并增强数据一致性。…

1. 认识SPSS

使用的是IBM SPSS statistics 25&#xff0c;参考教材《统计分析与SPSS的应用》 一、安装和启动 具体安装过程是参考spss下载以及安装详细教程这篇文章&#xff0c;下载安装包然后按他的步骤获取用户许可证即可。 二、主要窗口 数据编辑器窗口data editor 是SPSS的主程序窗…

UV映射技巧和窍门

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 有一个鲜为人知的主题是纹理映射。这始终是 3D 建模师想要处理的最后…

.NET 6中如何使用Redis

1、安装redis Redis在windows平台上不受官方支持&#xff0c;所以想要在window安装Redis就必须去下载windows提供的安装包。安装地址&#xff1a;https://github.com/tporadowski/redis/releases 2、在NueGet安装包 3、在appsettings.json文件里面添加Redis相关配置信息 &quo…

第11章 GUI Page480~486 步骤二十七 “脏数据”与“新文档”状态维护

wxMyPainterFrame类定义中声明新的成员&#xff1a; 增加一个全局变量&#xff0c;初始化新成员&#xff1a; 先实现TrySaveFile() SaveFile()暂时为空实现 增加两个新的私有成员方法&#xff1a; wxMyPainterFrame类中&#xff0c;修改了“_items”的几个地方 ① 鼠标抬起时…

uniapp中使用tmt-calendar字体的颜色如何修改

tmt-calendar这个插件市场下载的组件默认的眼色为深蓝&#xff0c;如下图 然后能够动态修改这些颜色的参数也很限制&#xff0c;就想着从源代码上面去修改&#xff0c; 但是本人在项目的src/components这个目录下找了很久没有找到 又去node_modules目录下寻找也没有找到&#…

嵌入式——循环队列

循环队列 (Circular Queue) 是一种数据结构(或称环形队列、圆形队列)。它类似于普通队列,但是在循环队列中,当队列尾部到达数组的末尾时,它会从数组的开头重新开始。这种数据结构通常用于需要固定大小的队列,例如计算机内存中的缓冲区。循环队列可以通过数组或链表实现,…

【深度学习】优化器介绍

文章目录 前言一、梯度下降法&#xff08;Gradient Descent&#xff09;二、动量优化器&#xff08;Momentum&#xff09;三、自适应学习率优化器 前言 深度学习优化器的主要作用是通过调整模型的参数&#xff0c;使模型在训练数据上能够更好地拟合目标函数&#xff08;损失函…

Fiddler工具 — 9.命令行和状态栏

1、命令行 命令行在Fiddler的左下方的黑色窗口&#xff0c;也叫QuickExec&#xff0c;可以调用 Fiddler的内置命令。 这一系列内置的函数用于筛选和操作会话列表中的session&#xff08;会话&#xff09;。 虽然它不是很显眼&#xff0c;但用好它&#xff0c;会让你的工作效率…

python 函数中字典的修改会影响函数外字典的值

def modify_dict(d):d[key] new valueprint(函数中字典d的位置,id(d))# 创建一个字典 original_dict {key: old value} print(函数外字典的位置,id(original_dict))# 调用函数来修改字典 modify_dict(original_dict)# 输出原始字典的值&#xff0c;可以看到它已经被修改了 pr…

致远OA getAjaxDataServlet XXE漏洞复现(QVD-2023-30027)

0x01 产品简介 致远互联-OA 是数字化构建企业数字化协同运营中台,面向企业各种业务场景提供一站式大数据分析解决方案的协同办公软件。 0x02 漏洞概述 致远互联-OA getAjaxDataServlet 接口处存在XML实体注入漏洞,未经身份认证的攻击者可以利用此漏洞读取系统内部敏感文件…

基于Spark个性化图书推荐系统

介绍 该系统基于Spark&#xff0c;结合了协同过滤算法和个性化推荐技术&#xff0c;实现了一款个性化的书籍推荐系统。 在该系统中&#xff0c;用户可以通过登陆注册后进入系统&#xff0c;查找和筛选自己喜欢的图书信息&#xff0c;同时也能够获得基于用户历史浏览、评分等数…

【PostgreSQL在线创建索引(CIC)功能的锁分析以及使用注意】

前一篇文章提到了普通创建索引会阻塞DML操作 PostgreSQL创建索引的锁分析和使用注意 而PostgreSQL里可以使用create index concurrently 在线创建索引(CIC)功能&#xff0c;降低创建索引在表上申请的锁的级别&#xff0c;ShareUpdateExclusiveLock级别的锁和RowExclusiveLock…

解决Qt Creator中文乱码的问题

方法1 使用QStringLiteral()包裹中文字符串 QString str1"中文测试&#xff01;"; QString str2QStringLiteral("中文测试&#xff01;");方法2 #if _MSC_VER > 1600//MSVC2015>1899,MSVC_VER14.0 #pragma execution_character_set("utf-8&qu…

第二百五十四回

文章目录 1. 概念介绍2. 思路与方法2.1 实现思路2.2 实现方法 3. 代码与效果3.1 示例代码3.2 运行效果 4. 内容总结 我们在上一章回中介绍了"如何给图片添加阴影"相关的内容&#xff0c;本章回中将介绍自定义Radio组件.闲话休提&#xff0c;让我们一起Talk Flutter吧…

7个Pandas绘图函数助力数据可视化

大家好&#xff0c;在使用Pandas分析数据时&#xff0c;会使用Pandas函数来过滤和转换列&#xff0c;连接多个数据帧中的数据等操作。但是&#xff0c;生成图表将数据在数据帧中可视化&#xff0c;通常比仅仅查看数字更有帮助。 Pandas具有几个绘图函数&#xff0c;可以使用它…

Java面向对象综合练习(拼图小游戏),用java图形化界面实现拼图小游戏

1. 设计游戏的目的 锻炼逻辑思维能力利用Java的图形化界面&#xff0c;写一个项目&#xff0c;知道前面学习的知识点在实际开发中的应用场景 2. 游戏的最终效果呈现 Hello&#xff0c;各位同学大家好。今天&#xff0c;我们要写一个非常有意思的小游戏 —《拼图小游戏》 我们…

【机器学习】循环神经网络(三)

四、序列预测问题 循环神经网络实现的序列到序列的映射&#xff08;Recurrent Neural Network based Sequence-to-Sequence Mapping&#xff09;是一种使用循环神经网络来将一个序列数据映射到另一个序列数据的方法&#xff0c;它可以用于机器翻译、文本摘要、对话生成等任务。…