Mongodb源码分析之balancer(均衡)分析

数据库 其他数据库 MongoDB
在之前的Mongodb源码分析之Mongos分析中,介绍了mongos的启动流程,在那篇文章的结尾,介绍了mongos使用balancer来进行均衡,今天就继续讲其实现方式。

在之前的Mongodb源码分析之Mongos分析中,介绍了mongos的启动流程,在那篇文章的结尾,介绍了mongos使用balancer来进行均衡,今天就继续讲其实现方式。

首先我们看一下Balancer及相关实现策略的类图:

 

可以看到Balancer类里包含一个BalancerPolicy,其指向一个均衡策略,该策略会实现查找并收集要迁移的chunk。

这里先看一下Balancer的类定义,如下:

  1. //balace.h  
  2. class Balancer : public BackgroundJob {  
  3. public:  
  4. Balancer();  
  5. virtual ~Balancer();  
  6. // BackgroundJob methods  
  7. virtual void run();  
  8. virtual string name() const { return "Balancer"; }  
  9.  
  10. private:  
  11. typedef BalancerPolicy::ChunkInfo CandidateChunk;  
  12. typedef shared_ptr<CandidateChunk> CandidateChunkPtr;  
  13.  
  14. //mongos名称(hostname:port)  
  15. string _myid;  
  16.  
  17. // Balancer 启动时间  
  18. time_t _started;  
  19.  
  20. // 前移的chunks数量  
  21. int _balancedLastTime;  
  22.  
  23. // 均衡策略(确定要迁移的chunks)  
  24. BalancerPolicy* _policy;  
  25.  
  26. //初始化,检查balancer 能否链接到servers.该方法可能抛出网络异常  
  27. bool _init();  
  28.  
  29. /**  
  30.  * 收集关于shards及chunks的信息,以及可能需要迁移的chunks  
  31.  * @param conn: 指向config server(s)连接  
  32.  * @param candidateChunks (IN/OUT): 可能需要迁移的chunks  
  33.  */ 
  34. void _doBalanceRound( DBClientBase& conn, vector<CandidateChunkPtr>* candidateChunks );  
  35.  
  36. /**  
  37.  * 逐个迁移chunk.并返回最终迁移的chunk数量  
  38.  * @param candidateChunks 可能需要迁移的chunks  
  39.  * @return number of chunks effectively moved  
  40.  */ 
  41. int _moveChunks( const vector<CandidateChunkPtr>* candidateChunks );  
  42.  
  43. /*在config server(s)中标记并前balancer为活动状态.*/ 
  44. void _ping( DBClientBase& conn );  
  45.  
  46. //当configdb中的所有服务均可用时,返回true  
  47. bool _checkOIDs();  
  48. }; 

可以看出balancer继承自BackgroundJob,所以它是以后台方式运行的。了解了该类的方法和属性之后,下面我们着手看一下mongos主函数中启动balancer.go()的调用流程。因为balancer继承自BackgroundJob,所以还要看一下BackgroundJob里go()方法的执行代码, 如下:

  1. //background.cpp 线程方式运行下面的jobBody方法  
  2. BackgroundJob& BackgroundJob::go() {  
  3. boost::thread t( boost::bind( &BackgroundJob::jobBody , this, _status ) );  
  4. return *this;  
  5. }  
  6.  
  7. ////background.cpp. Background object can be only be destroyed after jobBody() ran  
  8. void BackgroundJob::jobBody( boost::shared_ptr<JobStatus> status ) {  
  9. ....  
  10. const string threadName = name();  
  11. if( ! threadName.empty() )  
  12. setThreadName( threadName.c_str() );  
  13.  
  14. try {  
  15. run();//到这里,mongos开始执行子类balancer中的run方法  
  16. }  
  17. ....  
  18.  
  19. if( status->deleteSelf )  
  20. delete this;  

上面代码最终会将执行流程转到balancer类的run()方法,如下

  1. void Balancer::run() {  
  2.  
  3. /* this is the body of a BackgroundJob so if we throw   
  4. here we're basically ending the balancer thread prematurely */ 
  5. while ( ! inShutdown() ) {  
  6.  
  7. if ( ! _init() ) {//检查balancer是否链到config server和其它shard上  
  8. log() << "will retry to initialize balancer in one minute" << endl;  
  9. sleepsecs( 60 );  
  10. continue;  
  11. }  
  12.  
  13. break;  
  14. }  
  15. //构造链接串信息  
  16. ConnectionString config = configServer.getConnectionString();  
  17. //声明分布式锁  
  18. DistributedLock balanceLock( config , "balancer" );  
  19.  
  20. while ( ! inShutdown() ) {//一直循环直到程序中断或关闭  
  21.  
  22. try {  
  23.  
  24. // 判断chunk均衡功能是否有效  
  25. if ( ! grid.shouldBalance() ) {  
  26. log(1) << "skipping balancing round because balancing is disabled" << endl;  
  27. sleepsecs( 30 );  
  28. continue;  
  29. }  
  30.  
  31. //从链接池中获取一个链接对象,如无链接则直接创建。更多内容详见connpool.cpp文件的  
  32. //DBClientBase* DBConnectionPool::get(const string& host) 方法.  
  33. ScopedDbConnection conn( config );  
  34.  
  35. _ping( conn.conn() );//标识链到config server的balancer为活动(live)状态  
  36. if ( ! _checkOIDs() ) {  
  37. uassert( 13258 , "oids broken after resetting!" , _checkOIDs() );  
  38. }  
  39.  
  40. //重载Shard集合信息(shard 状态)  
  41. Shard::reloadShardInfo();  
  42.  
  43. //声明balance锁对象balanceLock  
  44.  
  45. dist_lock_try lk( &balanceLock , "doing balance round" );  
  46. if ( ! lk.got() ) {  
  47. log(1) << "skipping balancing round because another balancer is active" << endl;  
  48. conn.done();  
  49.  
  50. sleepsecs( 30 ); // no need to wake up soon  
  51. continue;  
  52. }  
  53.  
  54. log(1) << "*** start balancing round" << endl;  
  55.  
  56. vector<CandidateChunkPtr> candidateChunks;  
  57. //获取在shard集合中建议迁移的chunk信息(包含要迁移到的目标shard信息)  
  58. _doBalanceRound( conn.conn() , &candidateChunks );  
  59. if ( candidateChunks.size() == 0 ) {//是否有要移动的chunk  
  60. log(1) << "no need to move any chunk" << endl;  
  61. }  
  62. else//开始迁移并返回最终迁移数量 {  
  63. _balancedLastTime = _moveChunks( &candidateChunks );  
  64. }  
  65.  
  66. log(1) << "*** end of balancing round" << endl;  
  67. conn.done();//将conn放到链接池中(为其它后续操作使用)  
  68.  
  69. sleepsecs( _balancedLastTime ? 5 : 10 );  
  70. }  
  71. catch ( std::exception& e ) {  
  72. log() << "caught exception while doing balance: " << e.what() << endl;  
  73.  
  74. // Just to match the opening statement if in log level 1  
  75. log(1) << "*** End of balancing round" << endl;  
  76.  
  77. sleepsecs( 30 ); // sleep a fair amount b/c of error  
  78. continue;  
  79. }  
  80. }  

上面方法中主要是先构造链接串,进而构造连接实例(注:这里使用了链接池的概念,我会在后续章节中专门介绍其实现机制)。之后刷新sharding中的相关信息(确保其有效性),之后调用_doBalanceRound()方法来收集可能要迁移的chunk(s)信息并最终完成迁移(使用_moveChunks方法)。

下面我们就着重看一下这两个方法的具体实现.

首先是_doBalanceRound方法:

  1.    //balance.cpp  
  2. void Balancer::_doBalanceRound( DBClientBase& conn, vector<CandidateChunkPtr>* candidateChunks ) {  
  3. assert( candidateChunks );  
  4.  
  5. // 1. 通过查询ShardsNS::collections来检查是否有可用sharded集合来均衡chunk  
  6. auto_ptr<DBClientCursor> cursor = conn.query( ShardNS::collection , BSONObj() );  
  7. vector< string > collections;  
  8. while ( cursor->more() ) {  
  9. BSONObj col = cursor->next();  
  10.  
  11. // sharded collections will have a shard "key".  
  12. if ( ! col["key"].eoo() )  
  13. collections.push_back( col["_id"].String() );  
  14. }  
  15. cursor.reset();  
  16.  
  17. if ( collections.empty() ) {  
  18. log(1) << "no collections to balance" << endl;  
  19. return;  
  20. }  
  21.  
  22. //获取一个需要均衡的shard信息列表,表中shard信息包括maxsize, currsiez, drain, hsopsqueued  
  23. vector<Shard> allShards;  
  24. Shard::getAllShards( allShards );  
  25. if ( allShards.size() < 2) {  
  26. log(1) << "can't balance without more active shards" << endl;  
  27. return;  
  28. }  
  29. //获取allShards的相应状态信息交绑定到shardLimitMap相应元素中,该shardLimitMap是一个从shardId到对象(BSONObj)的映射  
  30. map< string, BSONObj > shardLimitsMap;  
  31. for ( vector<Shard>::const_iterator it = allShards.begin(); it != allShards.end(); ++it ) {  
  32. const Shard& s = *it;  
  33. ShardStatus status = s.getStatus();  
  34.    
  35. //最大值 (单位:兆字节, 0为不限制)  
  36.  
  37. BSONObj limitsObj = BSON( ShardFields::maxSize( s.getMaxSize() ) <<     
  38.  LimitsFields::currSize( status.mapped() ) << //当前时间状态的信息  
  39.  hardFields::draining( s.isDraining() )  << //当前的shard是否正在被移除  
  40.  LimitsFields::hasOpsQueued( status.hasOpsQueued() )//是否有回写的队列信息  
  41. );  
  42.  
  43. shardLimitsMap[ s.getName() ] = limitsObj;  
  44. }  
  45.  
  46. //遍历collections集合,根据均衡策略(balancing policy) ,检查是否有要迁移的chunk信息  
  47. for (vector<string>::const_iterator it = collections.begin(); it != collections.end(); ++it ) {  
  48. const string& ns = *it;//集合的名空间  
  49. map< string,vector<BSONObj> > shardToChunksMap;//从shardId 到chunks 的映射  
  50. cursor = conn.query( ShardNS::chunk , QUERY( "ns" << ns ).sort( "min" ) );  
  51. while ( cursor->more() ) {  
  52. BSONObj chunk = cursor->next();  
  53. //以chunk所属的shard为标识,获取一个chunks的集合来收集位于同一shard的chunk  
  54. vector<BSONObj>& chunks = shardToChunksMap[chunk["shard"].String()];  
  55. chunks.push_back( chunk.getOwned() );  
  56. }  
  57. cursor.reset();  
  58.  
  59. if (shardToChunksMap.empty()) {  
  60. log(1) << "skipping empty collection (" << ns << ")";  
  61. continue;  
  62. }  
  63.  
  64. for ( vector<Shard>::iterator i=allShards.begin(); i!=allShards.end(); ++i ) {  
  65. // this just makes sure there is an entry in shardToChunksMap for every shard  
  66. Shard s = *i;  
  67. shardToChunksMap[s.getName()].size();  
  68. }  
  69. //找出要迁移的chunk,包括源及目标(要迁移到的)chunk的起始地址  
  70. CandidateChunk* p = _policy->balance( ns , shardLimitsMap , shardToChunksMap , _balancedLastTime /*number of moved chunks in last round*/);  
  71.    if ( p ) candidateChunks->push_back( CandidateChunkPtr( p ) );//存到要均衡的chunk集合中  
  72. }  

上面的_doBalanceRound方法主要构造shardLimitsMap,shardToChunksMap这两个实例对象集合(map<>类型),其中:

shardLimitsMap:用于收集shard集合中一些“起数量限制”作用的参数,如maxsize,draining,hasOpsQueued等,因为这几个参数如果超出范围或为true时,相应shard 是不可以提供迁移服务的。
shardToChunksMap:用于收集当前shard中的chunk信息,以便后面的遍历操作。

收集了这些信息之后,通过调用 _policy->balance()方法来找出可能需要迁移的chunk().

#p#

下面就看一下该均衡策略的具体实现(具体内容参见注释):

  1. //balacer_policy.cpp  
  2. BalancerPolicy::ChunkInfo* BalancerPolicy::balance( const string& ns,  
  3. const ShardToLimitsMap& shardToLimitsMap,  
  4. const ShardToChunksMap& shardToChunksMap,  
  5. int balancedLastTime ) {  
  6. pair<string,unsigned> min("",numeric_limits<unsigned>::max());  
  7. pair<string,unsigned> max("",0);  
  8. vector<string> drainingShards;  
  9. //遍历shard集合,找到min,max的匹配对象,以及draining的Shard信息  
  10. for (ShardToChunksIter i = shardToChunksMap.begin(); i!=shardToChunksMap.end(); ++i ) {  
  11.  
  12. // 遍历shard,并查看其容量或可用空间是否被耗尽  
  13. const string& shard = i->first;  
  14. BSONObj shardLimits;  
  15. ShardToLimitsIter it = shardToLimitsMap.find( shard );  
  16. if ( it != shardToLimitsMap.end() ) shardLimits = it->second;//获取shard的信息,包括maxsize, currsiez, drain, hsopsqueued  
  17. const bool maxedOut = isSizeMaxed( shardLimits );//shard是否已满  
  18. const bool draining = isDraining( shardLimits );//shard是否移除  
  19. const bool opsQueued = hasOpsQueued( shardLimits );//shard是否有写回队列  
  20.  
  21. //是否合适接收chunk,满足下面三个条件之一,则视为不合适  
  22. // + maxed out shards  
  23. // + draining shards  
  24. // + shards with operations queued for writeback  
  25. const unsigned size = i->second.size();//获取当前shard里的chunk数  
  26. if ( ! maxedOut && ! draining && ! opsQueued ) {  
  27. if ( size < min.second ) {//如果当前shard中chunk数与min比较,找出最小size的shard  
  28. min = make_pair( shard , size );  
  29. }  
  30. }  
  31.  
  32. // 检查shard 是否应该迁移(chunk donor)  
  33. // Draining shards 比 overloaded shards优先级低  
  34. if ( size > max.second ) {  
  35. max = make_pair( shard , size );//找出最大size的shard  
  36. }  
  37. if ( draining && (size > 0)) {  
  38. drainingShards.push_back( shard );  
  39. }  
  40. }  
  41.  
  42. // 如果chunk没有合适的shard接收, 意味着上面循环中都是类以draining等情况  
  43. if ( min.second == numeric_limits<unsigned>::max() ) {  
  44. log() << "no availalable shards to take chunks" << endl;  
  45. return NULL;  
  46. }  
  47.  
  48. log(1) << "collection : " << ns << endl;  
  49. log(1) << "donor  : " << max.second << " chunks on " << max.first << endl;  
  50. log(1) << "receiver   : " << min.second << " chunks on " << min.first << endl;  
  51. if ( ! drainingShards.empty() ) {  
  52. string drainingStr;  
  53. joinStringDelim( drainingShards, &drainingStr, ',' );//用逗号将drainingShards连接起来  
  54. log(1) << "draining   : " << ! drainingShards.empty() << "(" << drainingShards.size() << ")" << endl;  
  55. }  
  56.  
  57. // 通过优先级解决不均衡问题.  
  58. const int imbalance = max.second - min.second;//找出shard中最不均衡的size的差距  
  59. const int threshold = balancedLastTime ? 2 : 8;  
  60. string from, to;  
  61. if ( imbalance >= threshold /*临界点*/) {  
  62. from = max.first;//将shard中chunk最多的作为源  
  63. to = min.first;//将shard中chunk最小的作为要迁移的目的地  
  64. }  
  65. else if ( ! drainingShards.empty() ) {  
  66. //对于那些draining的shard,随机取出其中一个  
  67. from = drainingShards[ rand() % drainingShards.size() ];  
  68. to = min.first;  
  69. }  
  70. else {  
  71. // 如已均衡,则返回  
  72. return NULL;  
  73. }  
  74.  
  75. //找出要迁移的chunk集合的起始位置  
  76. const vector<BSONObj>& chunksFrom = shardToChunksMap.find( from )->second;  
  77. const vector<BSONObj>& chunksTo = shardToChunksMap.find( to )->second;//找出要迁移到的chunk集合目标位置  
  78. BSONObj chunkToMove = pickChunk( chunksFrom , chunksTo );//最终选出(校正)要迁移的chunk的起始位置  
  79. log() << "chose [" << from << "] to [" << to << "] " << chunkToMove << endl;  
  80. //返回上面balaner的操作结果来执行后续的移动chunk操作  
  81. return new ChunkInfo( ns, to, from, chunkToMove );  

上面方法通过计算各个shard中的当前chunk数量来推算出那个shard相对较空,并将其放到to(目标shard),之后对可能要迁移的chunk进行校验,这里使用了pickChunk()方法,该方法具体实现如下:

  1. //balancer_policy.cpp  
  2. //找出需要被迁移的chunk, 这里要考虑to端可能比from端chunks更多的情况  
  3. BSONObj BalancerPolicy::pickChunk( const vector<BSONObj>& from, const vector<BSONObj>& to ) {  
  4. // It is possible for a donor ('from') shard to have less chunks than a recevier one ('to')  
  5. // if the donor is in draining mode.  
  6. if ( to.size() == 0 )//如果目标位置为空,表示可以将from中数据全部迁移过去  
  7. return from[0];  
  8. /**wo='well ordered'.  fields must be in same order in each object.  
  9.    Ordering is with respect to the signs of the elements  
  10.    and allows ascending / descending key mixing.  
  11.    @return  <0 if l<r. 0 if l==r. >0 if l>r  
  12. */  
  13. //如果要迁移的chunk中最小值与目标位置的最大值相同,表示可以将from中数据全部迁移过去  
  14. if ( from[0]["min"].Obj().woCompare( to[to.size()-1]["max"].Obj() , BSONObj() , false ) == 0 )  
  15. return from[0];  
  16. //如果要迁移的chunk中最大值与目标位置的最小值相同,表示可以将from中最后一个chunk迁移过去  
  17. if ( from[from.size()-1]["max"].Obj().woCompare( to[0]["min"].Obj() , BSONObj() , false ) == 0 )  
  18. return from[from.size()-1];  
  19.  
  20. return from[0];  

完成了校验之后,得到的就是真正要迁移的chunk的启始地址,之后就可以进行迁移了。到这里,我们还要将执行流程跳回到Balancer::run()方法里,看一下最终完成迁移工作的方法movechunk()的实现流程:

  1. //balance.cpp文件  
  2. int Balancer::_moveChunks( const vector<CandidateChunkPtr>* candidateChunks ) {  
  3. //最终迁移的chunk数  
  4. int movedCount = 0;  
  5. //遍历要迁移chunks并逐一开始迁移  
  6. for ( vector<CandidateChunkPtr>::const_iterator it = candidateChunks->begin(); it != candidateChunks->end(); ++it ) {  
  7. const CandidateChunk& chunkInfo = *it->get();  
  8. //获取当前chunk要使用的db配置信息  
  9. DBConfigPtr cfg = grid.getDBConfig( chunkInfo.ns );  
  10. assert( cfg );  
  11. //声明ChunkManager使用它来  
  12. ChunkManagerPtr cm = cfg->getChunkManager( chunkInfo.ns );  
  13. assert( cm );  
  14. //获取要迁移的chunk起始地址  
  15. const BSONObj& chunkToMove = chunkInfo.chunk;  
  16. ChunkPtr c = cm->findChunk( chunkToMove["min"].Obj() );  
  17. //下面判断执行两次,防止执行split之后,系统在reload 情况下chunk可能出现min,max不一致情况  
  18. if ( c->getMin().woCompare( chunkToMove["min"].Obj() ) || c->getMax().woCompare( chunkToMove["max"].Obj() ) ) {  
  19. // 这里主要防止别处执行 split 操作造成负作用  
  20. cm = cfg->getChunkManager( chunkInfo.ns , true /* reload */);  
  21. assert( cm );  
  22.  
  23. c = cm->findChunk( chunkToMove["min"].Obj() );  
  24. if ( c->getMin().woCompare( chunkToMove["min"].Obj() ) || c->getMax().woCompare( chunkToMove["max"].Obj() ) ) {  
  25. log() << "chunk mismatch after reload, ignoring will retry issue cm: " 
  26.   << c->getMin() << " min: " << chunkToMove["min"].Obj() << endl;  
  27. continue;  
  28. }  
  29. }  
  30.  
  31. BSONObj res;  
  32. //将chunk, 从当前的shard ,移动到指定的shard,并累加迁移数量  
  33. if ( c->moveAndCommit( Shard::make( chunkInfo.to ) , Chunk::MaxChunkSize , res ) ) {  
  34. movedCount++;  
  35. continue;  
  36. }  
  37. //如迁移不成功,记入日志  
  38. // the move requires acquiring the collection metadata's lock, which can fail  
  39. log() << "balacer move failed: " << res << " from: " << chunkInfo.from << " to: " << chunkInfo.to  
  40.   << " chunk: " << chunkToMove << endl;  
  41. //chunk是否达到允许移动的最大尺寸,如果是,则对当前shard执行split操作  
  42. if ( res["chunkTooBig"].trueValue() ) {  
  43. // reload just to be safe  
  44. cm = cfg->getChunkManager( chunkInfo.ns );  
  45. assert( cm );  
  46. c = cm->findChunk( chunkToMove["min"].Obj() );  
  47.  
  48. log() << "forcing a split because migrate failed for size reasons" << endl;  
  49.  
  50. res = BSONObj();  
  51. //对当前的shards进行分割(获取适合的分割点),该方法有些复杂,我会抽时间写文章介绍  
  52. c->singleSplit( true , res );  
  53. log() << "forced split results: " << res << endl;  
  54.  
  55. // TODO: if the split fails, mark as jumbo SERVER-2571  
  56. }  
  57. }  
  58.  
  59. return movedCount;  

上面代码就是依次遍历要迁移的chunk,分别根据其ns信息获取相应的ChunkManager(该类主要执行chunk的管理,比如CRUD等),之后就通过该ChunkManager找出当前chunk中最小的值(min:参见chunk.h文件,我这里把min,max理解为当前chunk中最小和最大记录对象信息)chunk信息,并开始迁移。

按照惯例,这里还是用一个时序列来大体回顾一下balancer的执行流程,如下:

 

好了,今天的内容就先到这里了。

原文链接:http://www.cnblogs.com/daizhj/archive/2011/05/23/mongos_balancer_source_code.html

【编辑推荐】

  1. Mongodb源码分析之Mongos分析
  2. Mongodb源码分析--内存文件映射(MMAP)
  3. 走进MongoDB的世界 展开MongoDB的学习之旅
  4. 浅析Mongodb源码之游标Cursor
  5. 野心勃勃的NoSQL新贵 MongoDB应用实战

 

责任编辑:艾婧 来源: 博客园
相关推荐

2011-05-26 10:05:48

MongoDB

2011-04-29 13:40:37

MongoDBCommand

2012-09-20 10:07:29

Nginx源码分析Web服务器

2023-02-26 08:42:10

源码demouseEffect

2011-04-25 17:15:39

MongodbMMAP

2021-07-06 09:29:38

Cobar源码AST

2021-03-23 09:17:58

SpringMVCHttpServletJavaEE

2010-05-04 16:59:52

DNS负载均衡

2020-07-28 08:54:39

内核通信Netlink

2023-03-30 13:32:51

负载均衡器HDFS

2012-09-06 10:07:26

jQuery

2021-09-05 07:35:58

lifecycleAndroid组件原理

2023-11-09 09:08:38

RibbonSpring

2009-07-08 13:22:30

JDK源码分析Set

2022-01-06 07:06:52

KubernetesResourceAPI

2017-01-12 14:52:03

JVMFinalRefere源码

2022-08-27 08:02:09

SQL函数语法

2022-05-30 07:36:54

vmstoragevmselect

2011-03-15 11:33:18

iptables

2014-08-26 11:11:57

AsyncHttpCl源码分析
点赞
收藏

51CTO技术栈公众号