MongoDB sharding 之 MoveChunk的實現過程

在分片集羣模式下, 有一個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失效;

  • 刪除本地的數據;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章