MongoDB 最佳实践:为什么升级之后负载升高了
撸代码:splitChunk、balancer与moveChunk
当通过 mongos 发生插入和更新删除操作时,mongos 会估算对应 chunks 的数据量的大小,满足条件会触发 splitChunk 的操作,splitChunk 之后可能会导致集群的 chunk 分布不均匀。balancer 检测数据的分布情况,当数据分配不均匀时,发起 moveChunk 任务,将数据从 chunks 较多的分片迁移到 chunks 较少的分片,迁移之后源节点会异步删除迁移走的 chunk 数据。
3.2 版本和 4.0 版本,此部分逻辑最大的区别就是, 3.2 版本 balancer 在 mongos,4.0 版本在 config(3.4版本开始 ),moveChunk 过程和删除数据的逻辑基本没有差异。
splitChunk
split chunks 一般是在插入、更新、删除数据时,由 mongos 发出到分片的 splitVector 命令,此时分片才会判断是否需要 split 。但是 mongos 并不知道每个 chunk 真正的数据量,是利用一个简单的估算算法判断的。
-
启动时,mongos 默认每个 chunk 的原始大小为 0-1/5 maxChunkSize 范围取个随机值 ;
-
之后 chunk 内数据,每次 update/insert 操作时,chunkSize = chunkSize + docSize;
-
当 chunkSize > maxChunkSize/5 时,触发一次可能 split chunk 的操作;到 分片mongod 执行 splitVector 命令 ,splitVector 命令返回 chunk 的分割点,如果返回为空那么不需要 split ,否则 继续 splitChunk .
也就是说,splitChunk 操作有滞后性,即使数据分布均衡,也有可能 splitChunk 执行时间的差异导致 chunks 分布存在中间的不均匀状态,导致大量的 moveChunk 。
balancer
无论 3.2 还是 4.0 的 balancer ,默认的检测周期为 10s , 如果发生了 moveChunk ,检测周期为 1s 。balancer 基本过程也大致相同:
-
config.shards 读取分片信息 ;
-
config.collections 读取所有集合信息,并且随机排序保存到一个数组中。
-
对每个集合从 config.chunks 读取 chunks 的信息;
-
含有最多 chunks 数量 (maxChunksNum)的分片为源分片,含有最少 chunks 数量(minChunksNum)的分片为目的分片; 如果 maxChunksNum – minChunksNum 大于迁移的 阈值 (threshold), 那么就是不均衡状态,需要迁移,源分片的 chunks 第一个 chunk 为待迁移的 chunk ,构造一个迁移任务(源分片,目的分片,chunk) ;
每次 balancer 会检测所有集合的情况,每个集合最多一个迁移任务 ; 而且构造迁移任务时,如果某个集合含有最多数量的分片或者最少数量 chunks 的分片,已经属于某一个迁移任务,那么此集合本轮 balancer 不会发生迁移。最后,本次检测出的迁移任务完成以后才开始下次 balancer 过程。
balancer 过程中,会对集合做一次随机排序,当有多个集合的数据需要均衡时,迁移时也是随机的,并不是迁移完一个集合开始下一个集合。
重点关注上述的迁移阈值,就是这个迁移的阈值 threshold 在 3.2 和 4.0 版本有所不同。
3.2 版本, chunks 数量小于 20 的时候为 2, 小于 80 的时候为 4, 大于 80 的时候为 8 。也就是说假设两分片集群,某个表有 100 个chunk , 每个分片分别有 47 和 53 个chunk 。那么此时 balance 认为是均衡的,不会发生迁移。
int threshold = 8; if (balancedLastTime || distribution.totalChunks() < 20) threshold = 2; else if (distribution.totalChunks() < 80) threshold = 4;
4.0 版本,chunks 数量差距大于 2 的时候就会发生迁移。同样的上述例子中,每个分片分别有 47 和 53 个 chunk 时, balance 认为是不均衡的,会发生迁移。
const size_t kDefaultImbalanceThreshold = 2; const size_t kAggressiveImbalanceThreshold = 1; const size_t imbalanceThreshold = (shouldAggressivelyBalance || distribution.totalChunks() < 20) ? kAggressiveImbalanceThreshold: kDefaultImbalanceThreshold; // 这里虽然有个 1 ,但是实际差距为 1 的时候不会发生迁移,因为判断迁移时,还有一个指标:平均每个分片的最大 chunks 数量,只有当 chunks 数量大于这个值的时候才会发生迁移。 const size_t idealNumberOfChunksPerShardForTag = (totalNumberOfChunksWithTag / totalNumberOfShardsWithTag) + (totalNumberOfChunksWithTag % totalNumberOfShardsWithTag ? 1 : 0);