MongoDB sharding 之 MoveChunk的实现过程

本文深入探讨MongoDB分片集群中Balancer的工作原理,包括其如何通过后台线程保证不同分片间chunk的均匀分布,以及BalancerPolicy类在决定哪些chunk需要迁移中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在分片集群模式下, 有一个Balancer的类,它启动一个名字为Balancer的后台线程, 用来保证不同分片之间chunk数的均匀。 每一个Mongos都有自己的Balancer, 不同的Balancer之间通过分布式锁来进行控制, 保证某个一时间只有一个Balancer在工作。
先看一下Balancer的定义:

class Balancer : public BackgroundJob {
public:
    Balancer();
    virtual ~Balancer();

    // BackgroundJob定义了该函数, 是Balancer执行任务的函数
    virtual void run();

    virtual std::string name() const {
        return "Balancer";
    }

private:
    // 标记该Balancer对应的Mongos的ip:port
    std::string _myid;

    // Balancer开始执行的时间
    time_t _started;

    // 上一次迁移的chunk的数量
    int _balancedLastTime;

    // 该类用来决定哪些chunk要迁移
    std::unique_ptr<BalancerPolicy> _policy;
}

我们看到整个Balancer::Run是整个迁移函数的入口, 它首先启动一个Balancer的线程, 然后检查与config server以及各个shard的网络是相通的; 接着就进入balance round-loop, 对于每一个round, 首先会做一些校验, 然后试图得到分布式锁, 验证metadata是否与config server一致, 若果这些全部都没有问题, 才正式进行迁移的准备工作, 通过函数Balancer::_doBalanceRound()得到所有需要迁移的chunk列表,左后将candidate chunks 通过MoveChunk command进行迁移工作。
这里, BalancerPolicy类是一个特殊的类, 它只有一个函数balance(), 来负责按照一定的策略, 进行判断某个shard是否有需要迁移的chunks。

void Balancer::_doBalanceRound(OperationContext* txn,
                               ForwardingCatalogManager::ScopedDistLock* distLock,
                               vector<shared_ptr<MigrateInfo>>* candidateChunks) {
    vector<CollectionType> collections;
    ShardInfoMap shardInfo;
    Status loadStatus = DistributionStatus::populateShardInfoMap(txn, &shardInfo);
  
    // For each collection, check if the balancing policy recommends moving anything around.
    for (const auto& coll : collections) {
        // Skip collections for which balancing is disabled
        const NamespaceString& nss = coll.getNs();

        std::vector<ChunkType> allNsChunks;
        Status status = grid.catalogManager(txn)->getChunks(txn,
                                                            BSON(ChunkType::ns(nss.ns())),
                                                            BSON(ChunkType::min() << 1),
                                                            boost::none,  // all chunks
                                                            &allNsChunks,
                                                            nullptr);

        set<BSONObj> allChunkMinimums;
        map<string, vector<ChunkType>> shardToChunksMap;

        for (const ChunkType& chunk : allNsChunks) {
            allChunkMinimums.insert(chunk.getMin().getOwned());

            vector<ChunkType>& chunksList = shardToChunksMap[chunk.getShard()];
            chunksList.push_back(chunk);
        }

        for (ShardInfoMap::const_iterator i = shardInfo.begin(); i != shardInfo.end(); ++i) {
            // This loop just makes sure there is an entry in shardToChunksMap for every shard
            shardToChunksMap[i->first];
        }

        DistributionStatus distStatus(shardInfo, shardToChunksMap);

        auto statusGetDb = grid.catalogCache()->getDatabase(txn, nss.db().toString());
        shared_ptr<DBConfig> cfg = statusGetDb.getValue();

        // This line reloads the chunk manager once if this process doesn't know the collection
        // is sharded yet.
        shared_ptr<ChunkManager> cm = cfg->getChunkManagerIfExists(txn, nss.ns(), true);

        shared_ptr<MigrateInfo> migrateInfo(
            _policy->balance(nss.ns(), distStatus, _balancedLastTime));
        if (migrateInfo) {
            candidateChunks->push_back(migrateInfo);
        }
    }
}

在获得了candidateChunks之后, 要开始讲这些chunks逐个的调用迁移命令:

int Balancer::_moveChunks(OperationContext* txn,
                          const vector<shared_ptr<MigrateInfo>>& candidateChunks,
                          const WriteConcernOptions* writeConcern,
                          bool waitForDelete) {
    int movedCount = 0;

    for (const auto& migrateInfo : candidateChunks) {
       ...
   
            shared_ptr<DBConfig> cfg =
                uassertStatusOK(grid.catalogCache()->getDatabase(txn, nss.db().toString()));


            shared_ptr<ChunkManager> cm = cfg->getChunkManager(txn, migrateInfo->ns);
            ChunkPtr c = cm->findIntersectingChunk(txn, migrateInfo->chunk.min);

            if (c->getMin().woCompare(migrateInfo->chunk.min) ||
                c->getMax().woCompare(migrateInfo->chunk.max)) {
                // Likely a split happened somewhere, so force reload the chunk manager
                cm = cfg->getChunkManager(txn, migrateInfo->ns, true);
                invariant(cm);

                c = cm->findIntersectingChunk(txn, migrateInfo->chunk.min);
    
            // moveAndCommit是会调用moveChunk command后端的接口来进行迁移任务
            BSONObj res;
            if (c->moveAndCommit(txn,
                                 migrateInfo->to,
                                 Chunk::MaxChunkSize,
                                 writeConcern,
                                 waitForDelete,
                                 0, /* maxTimeMS */
                                 res)) {
                movedCount++;
                continue;
            }
           ...

    return movedCount;
}

上述, 是整个迁移任务的整个流程。接下来是在db端MoveChunk command的执行过程。MoveChunkCommand负责执行具体的迁移过程:

  • 解析MoveChunkCommand的参数;

  • 获取分布式锁以确保元数据的稳定;

  • 迁移过程
    获取所有的要迁移的数据的recordID, 以便在迁移过程中进行最少的搜索。在获取所有的rcordId的时候需要集合所, 之后集合锁被解锁, 这为repair或者compact提供了时机。 注意, 数据修改没有问题, 因为我们注册了过程中的修改。

  • 等待知道迁移完成;

  • 加锁 (关键区)
    a) 更新我的配置, 实际上是加锁;
    b) 迁移结束;
    c) 更新配置服务器;
    d) 将更改记录到配置服务器;

  • 等待本地数据的cursor失效;

  • 删除本地的数据;

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值