版本: 2.2.0
以下內容涉及之前的文章<MongoDb move chunk 故障分析和處理 (SERVER-5351)> .
在前面的文章<MongoDb move chunk 故障分析和處理 (SERVER-5351)> 提到的問題, 我們move chunk失敗了. 問題的調查直指變數 vector _slaves, 該變數只會在關閉mongod和啟動mongod的時候做clear操作, 而其他時候只會增或者改.從我們move chunk日誌裡面看出來, 我們的mongod的_slaves變數裡面應該有 5/2+1 或者 4/2+1 個元素, 那麼怎麼來的呢? 我們明明只有3個secondary.
經過代碼的追查, 發現了幾個點
1) 在secondary mongod的rs.me裡有唯一一條記錄, 我們暫時叫它作 M. M有兩個關鍵的元素 _id 和 host, _id是這個mongod的一個唯一id, 由ObjectId產生, host是這個mongod部署的機器的網域名稱.
2) 在move chunk的過程中primary會向secondary擷取一個remoteid, 也就是上面提到的_id
3) secondary在啟動後, 一旦需要擷取記錄M, 則會去rs.me裡面查, 源碼碼是這樣的:
bool replHandshake(DBClientConnection *conn) { string myname = getHostName(); BSONObj me; { Lock::DBWrite l("local"); if ( ! Helpers::getSingleton( "local.me" , me ) || //從資料庫local.me裡面取出來機器的id, 這個就是所謂的_remoteId ! me.hasField("host") || me["host"].String() != myname ) {//這條記錄裡面的host和機器當前的host不同, 則需要清空重新設定, 此時就會重設_id Helpers::emptyCollection("local.me"); BSONObjBuilder b; b.appendOID( "_id" , 0 , true );//如果從local.me裡面取不到id, 則建立一個id, 作為所謂的_remoteId b.append( "host", myname ); me = b.obj(); Helpers::putSingleton( "local.me" , me ); } } BSONObjBuilder cmd; //把id封裝為 {"handshake": _remoteId}, 這個和我們在日誌裡面看到的一致, _remoteId在bsonobj裡面的key是 "handshake" cmd.appendAs( me["_id"] , "handshake" ); if (theReplSet) { cmd.append("member", theReplSet->selfId()); } BSONObj res; bool ok = conn->runCommand( "admin" , cmd.obj() , res );//執行handshake命令 return true;}
4) 所以, 如果某一個作為secondary的mongod的機器改過網域名稱, 那麼這個mongod就前後兩次的rs.me裡文檔的_id就不一樣, 我們稱id1為改網域名稱前的_id, id2為改網域名稱後的_id. 那麼前後兩次做過move chunk的時候分別有一個optime1和optime2. 這樣 (id1,optime1) 和 (id2,optime2)都在_slaves裡面, 但是表示的是同一個mongod, 而且optime1是早就過時的了. 這樣就導致了_slaves裡面的元素個數比實際secondary多.(ps: _slaves的元素不止這兩個值, 只是這兩個值是最重要的, 確定唯一的)
5) 經過確認, 我們的叢集確實經曆過搬機房, 修改網域名稱, 重啟過這些部署了secondary的機器. 加上我自己在另外搭建的叢集上面實驗, 2次修改網域名稱之後就發生了move chunk失敗的現象, 加上日誌的輸出, 和第4)點的結論一致.
到此為止, 總結一下
聯絡另外一篇博文<MongoDb move chunk 故障分析和處理>, _slaves這個變數是primary用來確定自己需要去跟蹤多少個secondary的同步狀態, map<Ident,OpTime> _slaves 的key值是secondary機器的唯一標識, value是Oplog同步到哪一條狀態. 只要是曾經有一次作為secondary去跟primary同步資料, 那麼primary的_slaves裡面就會記錄這個機器的Ident(是_remoteId和host(在我們的叢集裡面用ip, 我們ip沒有修改)和ns(日誌顯示是local.oplog.rs)的組合), Ident確定了唯一的一條_slaves記錄.
如果修改過網域名稱, 那麼就會導致新的Ident被插入到_slaves裡面, 其對應的opTime會非常過時, 導致movechunk的時候, 要的等帶多數secondary同步完成發生了錯誤.