在分片集群模式下, 有一个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失效;
-
删除本地的数据;