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);