MongoDB 副本集是通过什么方式同步数据呢?
MongoDB 副本集数据同步所依赖的核心是 Oplog
,它就像 MySQL 中的 Binlog
一样,记录着主节点上执行的每一个操作!而 Secondary
通过复制 Oplog
并应用的方式来进行数据同步。
Oplog
就是一个大小固定、循环复用的日志文件(默认分配 5%
的可用空间,即64位),当 Secondary
落后 Primary
很多,直到 oplog
被复写,那只能重新全量同步,而拉取全量同步代价特别高,直接影响 Primary
的读写性能。
我们可以用 –oplogSize
选项指定具体 Oplog
大小,设置合适的大小在生产应用中是非常重要的一个环节。
MongoDB 副本集是实时同步吗?
这其实也是在问数据库一致性的问题:
- MySQL 的半同步复制模式保证数据库的强一致
- Oracle DataGuard 的最大保护模式也能够保证数据库的强一致
- 而 MongoDB 可以通过
getLastError
命令来保证写入的安全,但其毕竟不是事务操作,无法做到数据的强一致。
MongoDB 副本集 Secondary
通常会落后几毫秒,如果有加载问题、配置错误、网络故障等原因,延迟可能会更大。
MongoDB 副本集是如何进行选举的?
选举我们可以简单理解为如何从集群节点中选择合适的节点提升为 Primary
的过程。跟很多 NoSQL
数据库一样,MongoDB 副本集采用的是 Bully
算法。
大致思想是集群中每个成员都能够声明自己是主节点并通知到其他节点,被其他节点接受的节点才能成为主节点。
MongoDB 副本集有着 “大多数” 的概念,在进行选举时必须遵循 “大多数” 规则,节点在得到大多数支持时才能成为主节点,而副本集中节点存活数量必须大于 “大多数” 的数量。
下列列举一些成员总数选举需满足的 “大多数” 数量对应关系
成员总数 | 大多数 |
---|---|
1 | 1 |
2 | 2 |
3 | 2 |
4 | 3 |
5 | 3 |
6 | 4 |
7 | 4 |
8 | 5 |
9 | 5 |
10 | 6 |
11 | 6 |
12 | 7 |
💡 即:需要的大多数节点数量计算为
majority = (n / 2) + 1
MongoDB 什么情况下会触发选举?
- 初始化副本集时;
- 备份节点无法和主节点通讯时(可能主节点宕或网络原因);
Primary
手动降级,rs.stepDown(sec)
,默认60s
。
选举步骤
- 获取每个服务器节点的最后操作时间戳。每个 mongodb 都有
oplog
机制会记录本机的操作,方便和主服务器进行对比数据是否同步还可以用于错误恢复; - 如果集群中大部分服务器宕机了,保留活着的节点都为
secondary
状态并停止选举; - 如果集群中选举出来的主节点或者所有从节点最后一次同步时间看起来很旧了,停止选举等待人工操作;
- 如果上面都没有问题就选择最后操作时间戳最新(保证数据最新)的服务器节点作为主节点。
MongoDB 副本集成员节点数量为偶数个会有问题吗?
❓ 有些人可能在设计 MongoDB 副本集架构过程中会产生成员节点必须是奇数个的误区,让我们来看看为啥?
以该图为例子,现在成员节点有 4 个,包含了 1 个 Primary
和 3 个 Secondary
节点,假如 Primary
因为服务器异常宕机了,那么还剩余 3 个节点可以选举,只要满足 “大多数” 2个即可以选出新的 Primary
。
因此,在单机房内不管副本集成员节点数为偶数还是奇数都是没有问题的!
❓ 为啥强调是 “单机房” 内呢?
以上图为例子,节点分布两个机房 IDC1 和 IDC2,每个机房的成员节点数量一致,在两个机房之间心跳中断时,整个集群就会出现无法选举 Primary
的问题,这就是 MongoDB
副本集中的脑裂。
还有另外的一种“脑裂”情况如下,当两个机房的网络通讯中断,而机房内的成员节点能满足 “大多数” 的选举规则的时候,就会变成两个独立的 MongoDB 副本集群!
如何防止脑裂?
方案一:大多数成员在一个中心
需求: 副本集的 Primary
总在主数据中心
缺点:如果主数据中心挂掉了,就没有可用的 Primary
节点了
方案二:引入独立仲裁节点
需求:跨机房容灾
缺点:额外需要第三个机房
分片的数据是如何存储的?
Chunk 是什么?
在一个 shard server
内部,MongoDB 还会把数据分为 chunks
,每个 chunk
代表这个 shard server
内部一部分数据。chunk
的产生,会有以下两个用途:
Splitting
: 当一个chunk
的大小超过配置中的chunk size
时,MongoDB 的后台进程会把这个chunk
切分成更小的chunk
,从而避免chunk
过大的情况Balancing
: 在 MongoDB 中,balancer
是一个后台进程,负责chunk
的迁移,从而均衡各个shard server
的负载,系统初始 1 个chunk
,chunk size
默认值64M
,生产库上选择适合业务的chunk size
是最好的。MongoDB 会自动拆分和迁移chunks
。
分片集群的数据分布(shard
节点)
- 使用
chunk
来存储数据 - 进群搭建完成之后,默认开启一个
chunk
,大小是64M
,范围为[minKey, maxKey]
- 存储需求超过
64M
,chunk
会进行分裂,如果单位时间存储需求很大,设置更大的chunk
chunk
会被自动均衡迁移
chunk分裂及迁移
chunk
分裂的时机:在插入和更新,读数据不会分裂。
随着数据的增长,其中的数据大小超过了配置的 chunk size
,默认是 64M
,则这个 chunk
就会分裂成两个。数据的增长会让 chunk
分裂得越来越多。
这时候,各个 shard
上的 chunk
数量就会不平衡。这时候,mongos
中的 balancer
组件(参考官网说明) 就会执行自动平衡。把 chunk
从 chunk
数量最多的 shard
节点挪动到数量最少的节点。
块迁移阈值
为了最大限度地减少平衡对集群的影响, 平衡器 balancer
仅在分片集合的块分布达到特定阈值后才开始平衡。 阈值适用于具有最多集合块的分片与具有最少集合块的分片之间的块数量差异。平衡器具有以下阈值:
chunk 数量 | 迁移阈值 |
---|---|
少于 20 个 | 2 |
20 ~ 79 | 4 |
80 及以上 | 8 |
🟡 ️当该集合的任意两个分片上的块数之差小于2或块迁移失败时,平衡器将停止在目标集合上运行。
块迁移过程
- 平衡器
balancer
进程将moveChunk
命令发送到源分片。 - 源通过内部命令开始移动
moveChunk
。在迁移过程中,对块的操作会路由到源分片。源分片负责传入的块写入操作。 - 目标分片构建源所需但目标上不存在的任何索引。
- 目标分片开始请求块中的文档并开始接收数据的副本。
- 接收到块中的最终文档后,目标分片启动同步过程,以确保它具有迁移过程中发生的迁移文档的更改。
- 完全同步后,源分片连接到配置数据库
config server
并使用块的新位置更新集群元数据。 - 源分片完成元数据更新后,一旦块上没有打开的游标,源分片就会删除其文档副本。
Chunk Size 应该如何选择呢?
适合业务的chunksize是最好的。
chunk
的分裂和迁移非常消耗 IO 资源;因此 chunkSize
的选择对分裂及迁移的影响是巨大的!
- MongoDB 默认的
chunkSize
为64MB
,如无特殊需求,建议保持默认值
;chunkSize
会直接影响到chunk
分裂、迁移的行为。 chunkSize
越小,chunk
分裂及迁移越多越快,数据分布越均衡,而且路由节点会消耗更多的资源;反之,chunkSize
越大,chunk
分裂及迁移会更少,但可能导致数据分布不均,IO消耗也高。chunkSize
太小,容易出现jumbo chunk
(即shardKey
的某个取值出现频率很高,这些文档只能放到一个chunk
里,无法再分裂)而无法迁移;chunkSize
太大,则可能出现chunk
内文档数太多(chunk
内文档数不能超过250000
)而无法迁移。chunk
自动分裂只会在数据写入时触发,所以如果将chunkSize
改小,系统需要一定的时间来将chunk
分裂到指定的大小。chunk
只会分裂,不会合并,所以即使将chunkSize
改大,现有的chunk
数量不会减少,但chunk
大小会随着写入不断增长,直到达到目标大小。
chunksize | splitting次数 | 跨分片数 | 数据均匀 | 网络传输次数 | 迁移次数 | 单次迁移传输量 | 查询速度 |
---|---|---|---|---|---|---|---|
变大 | 减少 | 变少 | 不太均匀 | 变少 | 变少 | 变大 | 变快 |
变小 | 增多 | 变多 | 更均匀 | 变多 | 变多 | 变小 | 变慢 |
MongoDB 中 Collection 的数据是根据什么进行分片的呢?
分片键(Shard key)
📕 分片键就是在集合中选一个字段或者组合字段,用该键的值作为数据拆分的依据。
分片键必须满足如下条件:
- 分片键是不可变(指数据里面的分片键字段值不能被更新)
- 分片键必须有索引,而且是升序的
- 分片键大小限制
512bytes
- 分片键用于路由查询,如果查询语句控制不当(比如不通过
_id
或者分片键的单挑查询会出问题) - 键的文档(也不支持空值插入)
当集合不存在的情况下,使用 sh.shardCollection
命令来分片集合会自动创建索引!
一个自增的分片键对写入和数据均匀分布就不是很好,因为自增的片键总会在一个分片上写入(因为新的文档总是被添加到当前具有最大值的分片上),后续达到某个阀值可能会写到别的分片。但是按照片键查询会非常高效。
哈希分片(Hash Sharding)
🔖 分片过程中利用哈希索引作为分片,基于哈希片键最大的好处就是保证数据在各个节点分布基本均匀。(官网说明入口)
对于基于哈希的分片,MongoDB 计算字段的哈希值,并用这个哈希值来创建数据块。在使用基于哈希分片的系统中,拥有相近分片键的文档很可能不会存储在同一个数据块中,因此数据的分离性更好一些。
注意:MongoDB 4.4及以上版本可以支持 复合索引字段
进行哈希分片。
范围分片(range sharding)
🔖 基于范围的分片涉及将数据划分为由分片键值确定的连续范围。在此模型中,具有近似分片键值的文档可能位于同一块或分片中。这允许高效查询,读取连续范围内的目标文档。然而,由于分片键选择不当,读取和写入性能可能会降低。(官网说明入口)
如果没有手动指定分片方法的情况下,基于范围的分片(range sharding
) 是默认的分片方法!
哈希和范围的结合
如下是基于 X 索引字段进行范围分片,但是随着 X 的增长,大于 20 的数据全部进入了 Chunk C, 这导致了数据的不均衡。
这时对 X 索引字段建哈希索引,可以使数据均匀分配:
通过 Zone 来管理分片数据分配
🔖 在分片集群中,您可以创建代表一组分片的区域,并将一个或多个分片键值范围与该区域相关联。MongoDB 仅将属于区域范围的读取和写入路由到区域内的那些分片。(官网说明入口)
应用区域(zone
)的一些常见部署模式如下:
- 将指定的数据放在指定的分片上。
- 确保最相关的数据驻留在地理上最靠近应用程序服务器的分片上。
- 根据分片硬件的配置/性能将数据路由到分片。
下图说明了具有三个分片和两个区域的分片集群。
- A区域代表下边界为 1 且上限为 10 的范围。
- B区域代表下边界为 10 且上限为 20 的范围。
- 分片
Alpha
和Beta
具有 A 区域 和 B 区。分片Charlie
没有与之关联的区域。 群集处于稳定状态。