MongoDB分片迁移原理与源码(4)

本文涉及的产品
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
简介:

MongoDB分片迁移原理与源码
异步删除数据

在from shard将迁移结果提交到config服务器成功后,from shard就会执行删除原数据的操作;如果迁移的参数"waitForDelete"为false,则触发异步删除。"waitForDelete"的默认参数即是false,即异步删除是默认设计。

将此次迁移的数据范围调用cleanUpRange()函数进行后续处理。

默认情况下,900s 以后开始清理 chunks 的数据,每次清理 128 个文档,每隔 20ms 删除一次。具体通过以下参数设置:

rangeDeleterBatchDelayMS: 删除每个 chunk 数据的时候分批次删除,每批之间间隔的时间,单位 ms,默认 20ms;

internalQueryExecYieldIterations: 默认为 128;

rangeDeleterBatchSize:每次删除数据的数量,默认即为0;为0时 ,则每次删除的数量为max(internalQueryExecYieldIterations,1),

orphanCleanupDelaySecs: moveChunk 以后延迟删除数据的时间,单位 s ,默认 900 s

const ChunkRange range(_args.getMinKey(), _args.getMaxKey());

auto notification = [&] {
auto const whenToClean = _args.getWaitForDelete() ? CollectionShardingRuntime::kNow

                                                : CollectionShardingRuntime::kDelayed;

UninterruptibleLockGuard noInterrupt(opCtx->lockState());
AutoGetCollection autoColl(opCtx, getNss(), MODE_IS);
return CollectionShardingRuntime::get(opCtx, getNss())->cleanUpRange(range, whenToClean);
}();

// 默认的异步删除时间
//MONGO_EXPORT_SERVER_PARAMETER(orphanCleanupDelaySecs, int, 900); // 900s = 15m
auto CollectionShardingRuntime::cleanUpRange(ChunkRange const& range, CleanWhen when)
-> CleanupNotification {
Date_t time = (when == kNow) ? Date_t{} : Date_t::now() +

       stdx::chrono::seconds{orphanCleanupDelaySecs.load()};

return _metadataManager->cleanUpRange(range, time);
}

再删除之前,还要判断是否满足没有任何基于该chunk的查询了:如果没有则放到删除队列中,等删除时间到了;如果还有查询,则放到另外一个孤儿文档队列,后续再删除;

auto MetadataManager::cleanUpRange(ChunkRange const& range, Date_t whenToDelete)
-> CleanupNotification {
stdx::lock_guard lg(_managerLock);
invariant(!_metadata.empty());

auto* const activeMetadata = _metadata.back().get();
auto* const overlapMetadata = _findNewestOverlappingMetadata(lg, range);

if (overlapMetadata == activeMetadata) {

   return Status{ErrorCodes::RangeOverlapConflict,
                 str::stream() << "Requested deletion range overlaps a live shard chunk"};

}

if (rangeMapOverlaps(_receivingChunks, range.getMin(), range.getMax())) {

   return Status{ErrorCodes::RangeOverlapConflict,
                 str::stream() << "Requested deletion range overlaps a chunk being"
                                  " migrated in"};

}

if (!overlapMetadata) {

   //如果没有基于该chunk的查询了,则把该数据块放到删除队列中.
   const auto whenStr = (whenToDelete == Date_t{}) ? "immediate"_sd : "deferred"_sd;
   log() << "Scheduling " << whenStr << " deletion of " << _nss.ns() << " range "
         << redact(range.toString());
   return _pushRangeToClean(lg, range, whenToDelete);

}

log() << "Deletion of " << _nss.ns() << " range " << redact(range.toString())

     << " will be scheduled after all possibly dependent queries finish";

//如果还有查询,则放到孤儿文档的队列中,后续再删除.
auto& orphans = overlapMetadata->orphans;
orphans.emplace_back(ChunkRange(range.getMin().getOwned(), range.getMax().getOwned()),

                    whenToDelete);

return orphans.back().notification;
}

根据删除时间,则定是否放到最终的异步删除的任务线程中scheduleCleanup()

auto MetadataManager::_pushRangeToClean(WithLock lock, ChunkRange const& range, Date_t when)
-> CleanupNotification {
std::list ranges;
ranges.emplace_back(ChunkRange(range.getMin().getOwned(), range.getMax().getOwned()), when);
auto& notifn = ranges.back().notification;
_pushListToClean(lock, std::move(ranges));
return notifn;
}

void MetadataManager::_pushListToClean(WithLock, std::list ranges) {
auto when = _rangesToClean.add(std::move(ranges));
if (when) {

   scheduleCleanup(
       _executor, _nss, _metadata.back()->metadata.getCollVersion().epoch(), *when);

}
invariant(ranges.empty());
}

void scheduleCleanup(executor::TaskExecutor* executor,

                NamespaceString nss,
                OID epoch,
                Date_t when) {

LOG(1) << "Scheduling cleanup on " << nss.ns() << " at " << when;
auto swCallbackHandle = executor->scheduleWorkAt(

   when, [ executor, nss = std::move(nss), epoch = std::move(epoch) ](auto&) {
       Client::initThreadIfNotAlready("Collection Range Deleter");
       auto uniqueOpCtx = Client::getCurrent()->makeOperationContext();
       auto opCtx = uniqueOpCtx.get();

       const int maxToDelete = std::max(int(internalQueryExecYieldIterations.load()), 1);

       MONGO_FAIL_POINT_PAUSE_WHILE_SET(suspendRangeDeletion);

       //执行真正的删除,但是每批只删除maxToDelete(默认128)个文档;每批间隔时间默认为rangeDeleterBatchDelayMS(20)毫秒。
       //最终删除调用的是collection->deleteDocument()删除集合文档的接口,完成文档删除
       auto next = CollectionRangeDeleter::cleanUpNextRange(opCtx, nss, epoch, maxToDelete);
       if (next) {
           scheduleCleanup(executor, std::move(nss), std::move(epoch), *next);
      }
  });

if (!swCallbackHandle.isOK()) {

   log() << "Failed to schedule the orphan data cleanup task"
         << causedBy(redact(swCallbackHandle.getStatus()));

}
}

问题
孤儿文档(orphaned document)

在第一章已经说过,由于数据块的迁移不是原子操作,导致从拷贝数据到异步删除数据,中间任何地方出错,都会导致产生孤儿文档。

孤儿文档会造成数据的不一致,甚至一个数据块迁移了一部分然后被打断,后续相同的数据块重新迁移的时候,有可能造成迁移始终不成功的问题。
4.0 版本中迁移触发的阈值太低,导致迁移产生的性能问题太高

该问题主要从参考文献中得出来的结论。详情可参考《MongoDB疑难解析:为什么升级之后负载升高了》

除此之外,由于整个迁移不是原子的,且存在异步过程,导致中间失败,产生其他问题的可能。
总结

MongoDB基于分片集群架构,实现了存储能力和服务能力的水平扩展,实现了管理海量数据的能力;并且基于自身架构的特点和优势,解决了如下问题:

可靠性。各个shard和config server基于副本集架构,实现了数据冗余和容错能力;

可用性。除了副本集架构的可用性的提高,一个shard出问题也不影响其他分片,以及整个分片集群继续服务的能力;

一致性。用户通过哪个mongos访问分片集群,都可以获得正确的数据;

伸缩性。非常方便的实现了增加和删除分片的功能,极为方便的实现了水平扩容;

性能。整个集群的服务分摊到了各个shard上,而且基于动态均衡,实现了性能的最大化。

综上,MongoDB的分片集群,还挺好。
参考文档

MongoDB官方文档 

孤儿文档是怎样产生的(MongoDB orphaned document) 

MongoDB疑难解析:为什么升级之后负载升高了? 

由数据迁移至MongoDB导致的数据不一致问题及解决方案
相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。 &nbsp; 相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
目录
相关文章
|
2月前
|
存储 缓存 NoSQL
MongoDB内部的存储原理
这篇文章详细介绍了MongoDB的内部存储原理,包括存储引擎WiredTiger的架构、btree与b+tree的比较、cache机制、page结构、写操作流程、checkpoint和WAL日志,以及分布式存储的架构。
55 1
MongoDB内部的存储原理
|
21天前
|
存储 NoSQL 前端开发
MongoDB 分片
10月更文挑战第17天
28 2
|
1月前
|
NoSQL MongoDB 数据库
使用NimoShake将数据从AWS DynamoDB迁移至阿里云MongoDB
使用NimoShake将数据从AWS DynamoDB迁移至阿里云MongoDB
|
2月前
|
存储 监控 NoSQL
*MongoDB的水平扩展主要通过分片技术实
*MongoDB的水平扩展主要通过分片技术实
42 5
|
2月前
|
存储 NoSQL 前端开发
MongoDB 分片总结
这篇文章总结了MongoDB分片的概念、集群结构、分片实例、配置和测试过程。
58 6
|
3月前
|
JSON NoSQL Ubuntu
在Ubuntu 14.04上如何备份、恢复和迁移MongoDB数据库
在Ubuntu 14.04上如何备份、恢复和迁移MongoDB数据库
88 1
|
3月前
|
NoSQL MongoDB 数据库
DTS 的惊天挑战:迁移海量 MongoDB 数据时,捍卫数据准确完整的生死之战!
【8月更文挑战第7天】在数字化时代,大数据量的MongoDB迁移至关重要。DTS(数据传输服务)通过全面的数据评估、可靠的传输机制(如事务保证一致性)、异常处理(如回滚或重试),以及迁移后的数据校验来确保数据准确无损。DTS还处理数据转换与映射,即使面对不同数据库结构也能保持数据完整性,为企业提供可靠的数据迁移解决方案。
62 2
|
4月前
|
存储 NoSQL MongoDB
MongoDB 索引原理与索引优化
MongoDB 索引原理与索引优化
91 1
|
4月前
|
DataWorks NoSQL fastjson
DataWorks操作报错合集之DataX进行MongoDB全量迁移的过程中,DataX的MongoDB Reader插件在初始化阶段找不到Fastjson 2.x版本的类库,该怎么办
DataWorks是阿里云提供的一站式大数据开发与治理平台,支持数据集成、数据开发、数据服务、数据质量管理、数据安全管理等全流程数据处理。在使用DataWorks过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
|
3月前
|
存储 运维 NoSQL
轻松上手:逐步搭建你的高可用MongoDB集群(分片)
【8月更文挑战第13天】在数据激增的背景下,传统单机数据库难以胜任。MongoDB作为流行NoSQL数据库,采用分片技术实现水平扩展,有效处理海量数据。分片将数据分散存储,提高并发处理能力和容错性,是高可用架构基石。构建MongoDB集群需理解shard、config server和router三组件协同工作原理。通过具体实例演示集群搭建流程,包括各组件的启动及配置,确保数据高可用性和系统稳定性。合理规划与实践可构建高效稳定的MongoDB集群,满足业务需求并支持未来扩展。
83 0