共计 4672 个字符,预计需要花费 12 分钟才能阅读完成。
本篇内容主要讲解“Mongos 与集群均衡怎么理解”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让丸趣 TV 小编来带大家学习“Mongos 与集群均衡怎么理解”吧!
mongodb 可以以单复制集的方式运行,client 直连 mongod 读取数据。
单复制集的方式下,数据的水平扩展的责任推给了业务层解决(分实例,分库分表),mongodb 原生提供集群方案,该方案的简要架构如下:
mongodb 集群是一个典型的去中心化分布式集群。mongodb 集群主要为用户解决了如下问题:
元数据的一致性与高可用(Consistency + Partition Torrence)
业务数据的多备份容灾(由复制集技术保证)
动态自动分片
动态自动数据均衡
下文通过介绍 mongodb 集群中各个组成部分,逐步深入剖析 mongodb 集群原理。
ConfigServer
mongodb 元数据全部存放在 configServer 中,configServer 是由一组(至少三个)mongod 实例组成的集群。
configServer 的唯一功能是提供元数据的增删改查。和大多数元数据管理系统(etcd,zookeeper)类似,也是保证一致性与分区容错性。本身不具备中心化的调度功能。
ConfigServer 与复制集
ConfigServer 的分区容错性 (P) 和数据一致性 (C) 是复制集本身的性质。
MongoDb 的读写一致性由 WriteConcern 和 ReadConcern 两个参数保证。
两者组合可以得到不同的一致性等级。
指定 writeConcern:majority 可以保证写入数据不丢失,不会因选举新主节点而被回滚掉。
readConcern:majority + writeConcern:majority 可以保证强一致性的读
readConcern:local + writeConcern:majority 可以保证最终一致性的读
mongodb 对 configServer 全部指定 writeConcern:majority 的写入方式,因此元数据可以保证不丢失。
对 configServer 的读指定了 ReadPreference:PrimaryOnly 的方式,在 CAP 中舍弃了 A 与 P 得到了元数据的强一致性读。
Mongos 数据自动分片
对于一个读写操作,mongos 需要知道应该将其路由到哪个复制集上,mongos 通过将片键空间划分为若干个区间,计算出一个操作的片键的所属区间对应的复制集来实现路由。
Collection1 被划分为 4 个 chunk,其中
chunk1 包含(-INF,1) , chunk3 包含[20, 99) 的数据,放在 shard1 上。
chunk2 包含 [1,20), chunk4 包含[99, INF) 的数据,放在 shard2 上。
chunk 的信息存放在 configServer 的 mongod 实例的 config.chunks 表中,格式如下:
{
_id : mydb.foo-a_\ cat\ ,
lastmod : Timestamp(1000, 3),
lastmodEpoch : ObjectId(5078407bd58b175c5c225fdc),
ns : mydb.foo ,
min : { animal : cat },
max : { animal : dog },
shard : shard0004
}
值得注意的是:chunk 是一个逻辑上的组织结构,并不涉及到底层的文件组织方式。
启发式触发 chunk 分裂
mongodb 默认配置下,每个 chunk 大小为 16MB。超过该大小就需要执行 chunk 分裂。chunk 分裂是由 mongos 发起的,而数据放在 mongod 处,因此 mongos 无法准确判断每个增删改操作后某个 chunk 的数据实际大小。因此 mongos 采用了一种启发式的触发分裂方式:
mongos 在内存中记录一份 chunk_id – incr_delta 的哈希表。
对于 insert 和 update 操作,估算出 incr_delta 的上界(WriteOp::targetWrites), 当 incr_delta 超过阈值时,执行 chunk 分裂。
值得注意的是:
1) chunk_id- incr_delta 是维护在 mongos 内存里的一份数据,重启后丢失
2) 不同 mongos 之间的这份数据相互独立
3) 不带 shardkey 的 update 无法对 chunk_id- incr_delta 作用
因此这个启发式的分裂方式很不精确,而除了手工以命令的方式分裂之外,这是 mongos 自带的唯一的 chunk 分裂方式。
chunk 分裂的执行过程
1) 向对应的 mongod 发起 splitVector 命令,获得一个 chunk 的可分裂点
2) mongos 拿到这些分裂点后,向 mongod 发起 splitChunk 命令
splitVector 执行过程:
1) 计算出 collection 的文档的 avgRecSize= coll.size/ coll.count
2) 计算出分裂后的 chunk 中,每个 chunk 应该有的 count 数,split_count = maxChunkSize / (2 * avgRecSize)
3) 线性遍历 collection 的 shardkey 对应的 index 的 [chunk_min_index, chunk_max_index] 范围,在遍历过程中利用 split_count 分割出若干 spli
splitChunk 执行过程:
1) 获得待执行 collection 的分布式锁(向 configSvr 的 mongod 中写入一条记录实现)
2) 刷新(向 configSvr 读取)本 shard 的版本号,检查是否和命令发起者携带的版本号一致
3) 向 configSvr 中写入分裂后的 chunk 信息,成功后修改本地的 chunk 信息与 shard 的版本号
4) 向 configSvr 中写入变更日志
5) 通知 mongos 操作完成,mongos 修改自身元数据
chunk 分裂的执行流程图:
问题与思考
问题一:为何 mongos 在接收到 splitVector 的返回后,执行 splitChunk 要放在 mongod 执行而不是 mongos 中呢,为何不是 mongos 自己执行完了 splitChunk 再通知 mongod 修改元数据?
我们知道 chunk 元数据在三个地方持有,分别是 configServer,mongos,mongod。如果 chunk 元信息由 mongos 更改,则其他 mongos 与 mongod 都无法第一时间获得最新元数据。可能会发生这样的问题,如下图描述:
Mongos 对元数据的修改还没有被 mongod 与其他 mongos 感知,其他 mongos 与 mongod 的版本号保持一致,导致其他 mongos 写入错误的 chunk。
如果 chunk 元信息由 mongod 更改,mongod 先于所有的 mongos 感知到本 shard 的元数据被更改,由于 mongos 对 mongod 的写入请求都会带有版本号(以发起者 mongos 的 POV 持有的版本号),mongod 发现一个读写带有的版本号低于自身版本号时就会返回 StaleShardingError,从而避免对错误的 chunk 进行读写。
Mongos 对读写的路由
读请求:
mongos 将读请求路由到对应的 shard 上,如果得到 StaleShardingError,则刷新本地的元数据(从 configServer 读取最新元数据)并重试。
写请求:
mongos 将写请求路由到对应的 shard 上,如果得到 StaleShardingError,并不会像读请求一样重试,这样做并不合理,截至当前版本,mongos 也只是列出了一个 TODO(batch_write_exec.cpp:185)
185 // TODO: It may be necessary to refresh the cache if stale, or maybe just
186 // cancel and retarget the batch
chunk 迁移
chunk 迁移由 balancer 模块执行,balancer 模块并不是一个独立的 service,而是 mongos 的一个线程模块。同一时间只有一个 balancer 模块在执行,这一点是 mongos 在 configServer 中注册分布式锁来保证的。
balancer 对于每一个 collection 的 chunk 分布,计算出这个 collection 需要进行迁移的 chunk,以及每个 chunk 需要迁移到哪个 shard 上。计算的过程在 BalancerPolicy 类中,比较琐碎。
chunk 迁移.Step1
MigrationManager::scheduleMigrations balancer 对于每一个 collection,尝试获得该 collection 的分布式锁(向 configSvr 申请),如果获得失败,表明该 collection 已有正在执行的搬迁任务。这一点说明对于同一张表统一时刻只能有一个搬迁任务。如果这张表分布在不同的 shard 上,完全隔离的 IO 条件可以提高并发,不过 mongos 并没有利用起来这一点。
如果获得锁成功,则向源 shard 发起 moveChunk 命令
chunk 迁移.Step2
mongod 执行 moveChunk 命令
cloneStage
1) 源 mongod 根据需要迁移的 chunk 的上下限构造好查询计划,基于分片索引的扫描查询。并向目标 mongod 发起 recvChunkStart 指令,让目标 chunk 开始进入数据拉取阶段。
2) 源 mongod 对此阶段的修改,将 id 字段 buffer 在内存里(MigrationChunkClonerSourceLegacy 类),为了防止搬迁时速度过慢 buffer 无限制增长,buffer 大小设置为 500MB,在搬迁过程中 key 的更改量超过 buffer 大小会导致搬迁失败。
3) 目标 mongod 在接收到 recvChunkStart 命令后
a. 基于 chunk 的 range,将本 mongod 上的可能脏数据清理掉
b. 向源发起_migrateClone 指定,通过 1)中构造好的基于分配索引的扫描查询得到该 chunk 数据的 snapshot
c. 拷贝完 snapshot 后,向源发起_transferMods 命令,将 2)中维护在内存 buffer 中的修改
d. 源在收到_transferMods 后,通过记录的 objid 查询对应的 collection,将真实数据返回给目标。
e. 目标在收完_transferMods 阶段的数据后,进入 steady 状态,等待源接下来的命令。这里有必要说明的是:用户数据源源不断的写入,理论上_transferMods 阶段会一直有新数据,但是必须要找到一个点截断数据流,将源的数据(搬迁对应的 chunk 的数据)设置为不可写,才能发起路由更改。因此这里所说的“_transferMods 阶段的所有数据”只是针对于某个时间点,这个时间点过后依然会有新数据进来。
f. 源心跳检查目标是否已经处于 steady 状态,如果是,则封禁 chunk 的写入,向目标发起_recvChunkCommit 命令,之后源的 chunk 上就无修改了。
g. 目标收到_recvChunkCommit 命令后,拉取源 chunk 上的修改并执行,执行成功后源解禁路由并清理源 chunk 的数据
到此,相信大家对“Mongos 与集群均衡怎么理解”有了更深的了解,不妨来实际操作一番吧!这里是丸趣 TV 网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!