本文是“我和MongoDB的故事”征文比赛的二等奖得主李鹏冲的文章。下面我们一起来欣赏下。
问题
近期线上一个三分片植入从3.2版本升级到4.0版本以后,使用重组的CPU的负载升高了很多(10%-> 40%),除了版本的升级,项目逻辑和操作量均无变化。关闭Balancer以后CPU负载回归正常,稳定在10%以下。否则,只能经常关闭当前正在写入表的balancer,每周两次打开balancer开启均衡,在此期间处理器的CPU负载持续稳定在40%。有3个分片,除了MongoDB版本的变化,项目本身的逻辑无任何变化。那么升级以后CPU负载加载变化的背后是什么原因呢?
监控与日志
首先可以明确,升级以后CPU负载升高和balancer迁移数据有关。观察升级以后4.0版本,周二打开balancer期间的负载情况和mongostat结果:
<一个 HREF = “ http://www.mongoing.com/wp-content/uploads/2020/02/监控与日志.JPG ” > < IMG SRC = “ http://www.mongoing.com/wp-content / uploads / 2020/02 /监控与日志.jpg “ alt = ”监控与日志“ width = ” 627 “ height = ” 610 “ class = ” alignnone size-full wp-image-31668 “ /> </ a >
可以发现,CPU负载升高和删除数据的情况很吻合。而迁移数据后的源数据需要删除迁移走的数据,所以肯定有大量的删除。迁移数据之后的删除也会有如下的日志:
<代码> 53094:2019-10-08T10:09:24.035199 + 08:00我共享[集合范围删除器] dt2log.tbl_log_item_20191001范围[{ _ id:-3074457345618258602602},{ _ id:-3033667061349287050})53095:2019 -10-08T10:09:24.035222 + 08:00 I SHARDING [集合范围删除器]等待dt2log.tbl_log_item_20191001范围[{id:-3074 457345618258602},{ id:-3033667061349287050})53096:2019-10-08T10:09 :24.035274 + 08:00余分片[收藏范围删除器]在dt2log.tbl_log_item_20191001范围[{id最终DELE婷文件:-3074457345618258602},{ id:-3033667061349287050})</代码>
因此从监控和日志判断,CPU负载较高主要是因为迁移数据之后的删除原因。而且生成的表都是{_id:hashed}分片类型的表,数据量较大,但是每条数据较小,平均每个chunk 10w +的文档数,删除数据速度约200-300 / s,所以移动一个chunk导致的删除就会持续10分钟左右。
统计最近2个周期,开启balancer以后moveChunk的情况:
<一个 HREF = “ http://www.mongoing.com/wp-content/uploads/2020/02/周期表.JPG ” > < IMG SRC = “ http://www.mongoing.com/wp-content/ uploads / 2020/02 /周期表.jpg “ alt = ”周期表“ width = ” 738 “ height = ” 266 “ class = ” alignnone size-full wp-image-31669 “ /> </ a >
从上表可知知此场景下,{_id:hashed}分片类型集合数据基本已经均匀了,不必重启开启balancer。因为每个块文档数量减少,删除会比较耗资源。
关闭表的balancer可以解决升级之后负载升高的问题,但是竟然是为什么升级到4.0之后CPU负载较高,而3.2版本稳定在低位呢?这只有可能是一个原因:4.0版本更容易发生的moveChunk,持续的删除数据导致CPU负载一直较高; 3.2版本的以上发生moveChunk,不用删除数据所以负载很低。
所以本次问题的根本是:4.0版本和3.2版本的balancer与moveChunk的逻辑是否有区别?同样的操作,为什么4.0版本的存在可能的moveChunk?
撸代码:splitChunk,balancer与moveChunk
当通过mongos发生插入和更新删除操作时,mongos会更改对应块的数据量的大小,满足条件会触发splitChunk的操作,splitChunk之后可能会导致该块的分布不均匀。balancer检测到数据的分布情况,当数据分配不均匀时,启动moveChunk任务,将数据从大块变为离散的分片迁移到大块替换其的分片,迁移之后源节点会删除删除迁移走的大块数据。
3.2版本和4.0版本,此部分逻辑最大的区别就是,3.2版本balancer在mongos,4.0版本在config(3.4版本开始),moveChunk过程和删除数据的逻辑基本没有差异。
splitChunk
一般是在插入,更新,删除数据时,由mongos发出到分片的splitVector命令,此时分片才会判断是否需要split。但是mongos并不知道每个chunk真正的数据量,是利用一个简单的预测算法判断的。
- 启动时,mongos默认每个块的原始大小为0-1 / 5 maxChunkSize范围取个随机值;- 之后块内数据,每次更新/插入操作时,chunkSize = chunkSize + docSize;- 当chunkSize> maxChunkSize / 5时,触发一次可能split chunk的操作;到分片mongod执行splitVector命令,splitVector命令返回chunk的分割点,如果返回为空那么不需要split,否则继续splitChunk。
也就是说,splitChunk操作有滞后性,即使数据分布均衡,也有可能splitChunk执行时间的差异导致块分布存在中间的不均匀状态,导致大量的moveChunk。
平衡器
无论3.2还是4.0的balancer,交替的检测周期为10s,如果发生了moveChunk,检测周期为1s。balancer基本过程也大致相同:
- config.shards读取分片信息; -config.collections读取所有集合信息,并随机排序保存到一个数组中;-对每个集合从config.chunks读取块的信息;-包含最多的块数量(maxChunksNum)的分片为源分片,包含最小块数量(minChunksNum)的分片为目的分片;如果maxChunksNum-minChunksNum大于迁移的阈值(阈值),那么就是不均衡状态,需要迁移,源分片的块第一个chunk为待迁移的chunk,构造一个迁移任务(源分片,目的分片,chunk )。
每次balancer会检测所有集合的情况,每个集合最多一个迁移任务;而且构造迁移任务时,如果某个集合包含最多数量的分片或者最少数量的块,的分片,已经属于某个迁移任务,那么那么集合本轮平衡器将不会发生迁移。最后,本次检测出的迁移任务完成以后才开始下一平衡程序。
balancer过程中,估计集合做一次随机排序,当有多个集合的数据需要均衡时,迁移时也是随机的,并不是迁移完一个集合开始下一个集合。
重点关注上述的迁移阈值,就是这个迁移的阈值threshold在3.2和4.0版本有所不同。
3.2版本,chunks数量小于20的时候为2,小于80的时候为4,大于80的时候为8。假设假设两分片重叠,大约有100个块,每个分片分别有47和53个大块。那么此时balance认为是均衡的,不会发生迁移。
<code> int阈值= 8; 如果(balancedLastTime || distribution.totalChunks()<20)阈值= 2; 否则(distribution.totalChunks()<80)阈值= 4; </代码>
4.0版本,大块数量差异大于2的时候就会发生迁移。同样的上述示例中,每个分片分别有47和53个chunk时,balance认为是不均衡的,会发生迁移。
<代码> const size_t kDefaultImbalanceThreshold = 2; const size_t kAggressiveImbalanceThreshold = 1; const size_t不平衡阈值=(应该AggressivelyBalance || distribution.totalChunks()<20)吗?kAggressiveImbalanceThreshold:kDefaultImbalanceThreshold; //这里虽然有个1,但是实际差异为1的时候不会发生迁移,因为判断迁移时,还有一个指标:平均每个分片的最大chunks数量,只有当chunk数量大于这个值的时候常量size_t idealNumberOfChunksPerShardForTag =(totalNumberOfChunksWithTag / totalNumberOfShardsWithTag)+(totalNumberOfChunksWithTag%totalNumberOfShardsWithTag?1:0);发生转移。</ code>
关于此阈值,官方文档也有介绍:
为了最大程度地减少平衡对集群的影响,平衡器仅在分片集合的块分配达到某些阈值之后才开始平衡。阈值适用于集合中具有最多块的碎片与该集合中具有最少块的碎片之间的块数差异。平衡器具有以下阈值:
<一个HREF =“ http://www.mongoing.com/wp-content/uploads/2020/02/Chunk.jpg ”> <IMG SRC =“ http://www.mongoing.com/wp-content/uploads /2020/02/Chunk.jpg“ alt =”大块“ width =” 406“ height =” 179“ class =” alignnone size-full wp-image-31670“ /> </ a>
当该集合的任何两个分片上的块数之差小于2或块迁移失败时,平衡器将停止在目标集合上运行。
但是从代码上,从3.4版本开始,此阈值的逻辑就已经变化了,但是文档并没有更新。
moveChunk
moveChunk是一个比较复杂的动作,大致过程如下:
<一个HREF =“ http://www.mongoing.com/wp-content/uploads/2020/02/moveChunk.jpg ”> <IMG SRC =” http://www.mongoing.com/wp-content/uploads /2020/02/moveChunk.jpg“ alt =” moveChunk“ width =” 911“ height =” 819“ class =” alignnone size-full wp-image-31671“ /> </ a>
目的分片,首先要删除要移动的chunk的数据。所以会有一个删除任务。
可以在config.settings设置_secondaryThrottle和waitForDelete设置moveChunk过程中插入数据和删除数据的编写关注点
- _secondaryThrottle:真表示平衡器插入数据时,至少等待一个二级节点回复;虚假表示不等待写到二级节点;也可以直接设置为写担忧,则迁移时使用这个写的关注。3.2版本默认为真,3.4开始版本默认为false; - waitForDelete:迁移一个块数据以后,是否同步等待数据删除完毕;默认为假,由一个单独的线程异步删除孤儿数据。
设置方式如下:
<代码>使用config db.settings.update({“ _id”:“ balancer”},{$ set:{“ _secondaryThrottle”:{“ w”:“多数”},“ _ waitForDelete”:true}},{upsert :true}) </代码>
3.2版本_secondaryThrottle默认true,3.4开始版本默认false,所以3 .2版本和4.0版本moveChunk迁移数据时,4.0版本会更改完成,迁移中目的分片的每秒插入量级也会更多,对CPU负载也会有些许的影响。
另外,3.4.18 / 3.6.10 / 4.0.5及之后版本,还有以下参数(参数)调整插入数据的速度:
- migrateCloneInsertionBatchDelayMS:迁移数据时,每次插入的间隔,默认0不等待- migrateCloneInsertionBatchSize:迁移数据时,每次插入的数量,默认为0无限制。
设置方式如下:
<代码> db.adminCommand({setParameter:1,migrateCloneInsertionBatchDelayMS:0})db.adminCommand({setParameter:1,migrateCloneInsertionBatchSize:0}) </代码>
异步删除数据线程
3.2和4.0版本的异步删除线程具体实现略有不同,但是,根本过程还是一致的,用一个磁盘保存需要删除的范围,循环的取值的数据删除数据。因此,删除数据线程是按照块进入的的顺序,逐个删除。总入口:
<代码> 3.2版本db / range_deleter.cpp线程入口RangeDeleter :: doWork()4.0版本db / s / metadata_manager.cpp scheduleCleanup时有一个唯一的线程执行清理任务 </ code >
4.0版本在删除数据时,按批删除数据,每次删除数量计算方式如下:
<代码> maxToDelete = rangeDeleterBatchSize.load(); 如果(maxToDelete <= 0){maxToDelete = std :: max(int(internalQueryExecYieldIterations.load()),1); // 128} </ code>
有适当的参数可以灵活的控制删除速度,交替情况下,900s之后开始清除块的数据,每次清理128个文档,每隔20ms删除一次。具体通过以下参数设置:
- rangeDeleterBatchDelayMS:删除每个块数据的时候分批删除,每批之间间隔的时间,单位几十分钟,20分钟;-internalQueryExecYieldIterations:替代为128; -rangeDeleterBatchSize:每次删除数据的数量,交替为为0;为0时,则每次删除的数量为max(internalQueryExecYieldIterations,1),-orphanCleanupDelaySecs:moveChunk之后重复删除数据的时间,单位,大约900 s
总结
- moveChunk可能对系统的负载产生影响,主要是删除数据阶段的影响,一般是迁移中的插入数据影响较小;-3.4及之后的版本存在平衡器迁移阈值前缀的问题,可能会更交替的产生moveChunk;-文档数据多而小的表,而且是散列的分片,本应预分配一定的chunk之后永久负载常数; 4.0版本均衡阈值很小,更容易发生迁移,替换的迁移之后删除数据导致负载较高。
作者:李鹏冲
网易游戏高级运维工程师,MongoDB和MySQL数据库爱好者,目前专注于SAAS平台的开发与运维工作。
感谢<一个HREF =“ https://www.mongodb.com/zh ”> MongoDB的官方</一>,<一HREF =“ http://www.jinmuinfo.com/ ”>锦木信息</一>和<一个HREF =“ https://tapdata.net/ ”> Tapdata </一>对活动的大力支持!