生產(chǎn)事故(MongoDB數(shù)據(jù)分布不均解決方案)
事故集合:
Shard replset_sh1 at replset_sh1/mongodb01:27001,mongodb02:27001
data : 2207.7GiB docs : 2112558418 chunks : 62935
estimated data per chunk : 35.92MiB
estimated docs per chunk : 33567
Shard replset_sh2 at replset_sh2/mongodb02:27002,mongodb03:27002
data : 2193.32GiB docs : 2088697495 chunks : 63267
estimated data per chunk : 35.49MiB
estimated docs per chunk : 33014
Shard replset_sh3 at replset_sh3/mongodb03:27003,mongodb04:27003
data : 5501.55GiB docs : 5470146991 chunks : 101582
estimated data per chunk : 55.45MiB
estimated docs per chunk : 53849
Shard replset_sh4 at replset_sh4/mongodb04:27004,mongodb05:27004
data : 2220.35GiB docs : 2121870478 chunks : 62831
estimated data per chunk : 36.18MiB
estimated docs per chunk : 33771
Shard replset_sh5 at replset_sh5/mongodb01:27005,mongodb05:27005
data : 2295.51GiB docs : 2188687470 chunks : 64778
estimated data per chunk : 36.28MiB
estimated docs per chunk : 33787
Shard replset_sh6 at replset_sh6/mongodb06:27006,mongodb07:27006
data : 212.64GiB docs : 203616005 chunks : 6822
estimated data per chunk : 31.91MiB
estimated docs per chunk : 29846
Shard replset_sh7 at replset_sh7/mongodb07:27007,mongodb08:27007
data : 93.86GiB docs : 91177954 chunks : 2989
estimated data per chunk : 32.15MiB
estimated docs per chunk : 30504
Shard replset_sh8 at replset_sh8/mongodb06:27008,mongodb08:27008
data : 194.38GiB docs : 188053173 chunks : 6168
estimated data per chunk : 32.27MiB
estimated docs per chunk : 30488
Totals
data : 14919.34GiB docs : 14464807984 chunks : 371372
Shard replset_sh1 contains 14.79% data, 14.6% docs in cluster, avg obj size on shard : 1KiB
Shard replset_sh2 contains 14.7% data, 14.43% docs in cluster, avg obj size on shard : 1KiB
Shard replset_sh3 contains 36.87% data, 37.81% docs in cluster, avg obj size on shard : 1KiB
Shard replset_sh4 contains 14.88% data, 14.66% docs in cluster, avg obj size on shard : 1KiB
Shard replset_sh5 contains 15.38% data, 15.13% docs in cluster, avg obj size on shard : 1KiB
Shard replset_sh6 contains 1.42% data, 1.4% docs in cluster, avg obj size on shard : 1KiB
Shard replset_sh7 contains 0.62% data, 0.63% docs in cluster, avg obj size on shard : 1KiB
Shard replset_sh8 contains 1.3% data, 1.3% docs in cluster, avg obj size on shard : 1KiB
可以很明顯可以看到我們這個集合的數(shù)據(jù)嚴重分布不均勻绝骚。
一共有8個分片禀挫,面對這個情況我首先想到的是手動拆分數(shù)據(jù)塊侨把,但這不是解決此問題的根本辦法生真。
? ? ? ? 造成此次生產(chǎn)事故的首要原因就是片鍵選擇上的問題短荐,由于片鍵選擇失誤笛谦,在數(shù)據(jù)量級不大的時候數(shù)據(jù)看起來還是很健康的型雳,但隨著數(shù)據(jù)量的暴漲奕锌,問題就慢慢浮出了水面倡勇,我們使用的組合片鍵并不是無規(guī)律的逞刷,片鍵內(nèi)容是線性增長的,這就導致了數(shù)據(jù)的不正常聚集妻熊。由于數(shù)據(jù)分布不均勻夸浅,我們有兩個分片的磁盤使用率接近80%,數(shù)據(jù)還在持續(xù)增長扔役,這個問題必須盡快解決帆喇。
涉及到此次事故的集合一共有三個,總數(shù)據(jù)量加起來接近30T亿胸,數(shù)據(jù)總量300億左右坯钦。
下面是我解決此問題的解決方案:
方案一:
第一步:創(chuàng)建一個新的分片表,片鍵我選擇_id做hashed分片侈玄,并提前分好了數(shù)據(jù)塊婉刀,降低在恢復期間頻繁切割數(shù)據(jù)造成的服務器壓力。
sh.shardCollection("loan_his.collection",{_id:"hashed"},false,{numInitialChunks:1024})
第二步:單獨連接各個分片將8個分片的數(shù)據(jù)全量備份:
nohup mongodump -u loan_his -p loan_his --authenticationDatabase loan_his? -h ${replset} --db loan_his --collection ${collectionName} --query '{"txdt": { $lte: "2019-07-09"} }' -o ${bak_dir} &>>? ${log} &
你可能會問為什么不連接mongos,因為我在連接mongos做數(shù)據(jù)備份時出現(xiàn)了以下異常:
2019-07-08T16:10:03.886+0800? ? Failed: error writing data for collection `loan_his.ods_cus_trad` to disk: error reading collection: operation was interrupted
可能是因為集合內(nèi)的數(shù)據(jù)壞塊吧拗馒,此異常信息是我備份了將近70%的數(shù)據(jù)后突然拋出的異常信息路星。
除了這個原因,單獨備份各個分片的數(shù)據(jù)后你能夠自由控制恢復數(shù)據(jù)的時間窗口诱桂,不會因為恢復單個數(shù)據(jù)文件時間較長洋丐,突發(fā)意外情況導致恢復中斷從頭再來的窘境。能夠根據(jù)服務器的狀態(tài)避開高峰期來進行數(shù)據(jù)恢復挥等。
備份期間我發(fā)現(xiàn)了有時候備份出來的總文檔數(shù)和 db.collection.getShardDistribution() 查看的文檔數(shù)不一致友绝,我還以為是備份期間出了問題,但我刪除當前備份文件后重新備份出來的文檔數(shù)還是和之前一樣肝劲。目前不知道是怎么回事迁客,懷疑是壞的數(shù)據(jù)塊引發(fā)的我問題郭宝,備份出來的數(shù)據(jù)一般會比原數(shù)據(jù)量多幾萬條數(shù)據(jù),有時候會少一些掷漱。
第三步:恢復數(shù)據(jù):
mongorestore -u loan_his -p loan_his --authenticationDatabase loan_his -h 10.0.156.9:27017 --db loan_his? --collection? ${collectionName_two}? /mongodb/${collectionName}/replset_sh2/loan_his/${collectionName}.bson? &>>? ${log}
在恢復數(shù)據(jù)前千萬要記得不要創(chuàng)建索引粘室!否則性能極差,速度非常非常慢卜范!在使用mongodump工具備份時衔统,在數(shù)據(jù)文件的同級目錄下會有一個 XXXXX.metadata.json 索引文件,默認會在數(shù)據(jù)恢復完畢后執(zhí)行創(chuàng)建索引的操作海雪。
此處有坑需要注意:因為備份出來的數(shù)據(jù)是由原表備份出來的锦爵,那這個索引文件也是原表的索引,由于原表我使用的是組合片鍵做的分片奥裸,所以在原表內(nèi)會存在一個由片鍵組成的組合索引险掀,并且不是后臺創(chuàng)建的組合索引!M逯妗樟氢!這意味著如果你使用此索引文件來給新表創(chuàng)建索引,會造成這個集群處于阻塞狀態(tài)创倔,無法響應任何操作N撕Α!直至索引創(chuàng)建完畢畦攘。所以你可以將這個索引文件備份到其它目錄以作參考霸妹,然后將原文件刪除就可以了,恢復數(shù)據(jù)時不會有其它的問題知押。
如果恢復期間出現(xiàn)了意外情況導致恢復失敗叹螟,比如節(jié)點宕機什么的,不需要擔心台盯,重新執(zhí)行恢復程序罢绽,數(shù)據(jù)文件不會重復增加,因為備份出來的數(shù)據(jù)文件包含mongodb自帶的 Objectld對象_id ,導入時静盅,如果已存在此ID,將不會插入數(shù)據(jù)良价。注意:在不同集合是允許出現(xiàn)相同ID的,所以在使用方案二恢復數(shù)據(jù)時蒿叠,新產(chǎn)生的數(shù)據(jù)不能通過新表A備份出來匯入新表C明垢,需要通過原始數(shù)據(jù)文件重新導入。
第四步:創(chuàng)建索引:
待所有數(shù)據(jù)恢復完畢后再創(chuàng)建索引市咽,一定要記得后臺創(chuàng)建H!施绎!你也可以將索引拆分溯革,一個一個的來贞绳。如果覺得此操作對業(yè)務影響較大,請看本文最后的解決方案致稀。
mongo 10.0.156.2:27017/loan_his -uloan_his -ploan_his -eval 'db.getSiblingDB("loan_his").runCommand({createIndexes: "collection",indexes: [{"v":2,"key":{"_id":1},"name":"_id_","ns":"loan_his.collection"},{"v":2,"key":{"opnode":1.0,"txdt":1.0,"acct":1.0,"crdno":1.0},"name":"opnode_1_txdt_1_acct_1_crdno_1","ns":"loan_his.collection"},{"v":2,"key":{"txdt":1.0,"opnode":1.0,"acct":1.0,"crdno":1.0,"pbknum":1.0},"name":"txdt_1_opnode_1_acct_1_crdno_1_pbknum_1","ns":"loan_his.collection","background":true},{"v":2,"key":{"acct":1.0,"txdt":1.0,"opnode":1.0},"name":"acct_1_txdt_1_opnode_1","ns":"loan_his.collection","background":true},{"v":2,"key":{"crdno":1.0,"txdt":1.0,"opnode":1.0},"name":"crdno_1_txdt_1_opnode_1","ns":"loan_his.collection","background":true},{"v":2,"key":{"pbknum":1.0,"txdt":1.0,"opnode":1.0},"name":"pbknum_1_txdt_1_opnode_1","ns":"loan_his.collection","background":true}]})'
停止失控索引:
一旦你觸發(fā)一個索引冈闭,簡單的重啟服務并不能解決這個問題,因為MongoDB會繼續(xù)重啟前的建索引的工作豺裆。如果之前你運行后臺建索引任務拒秘,在服務重啟后它會變成前臺運行的任務。在這種情況下臭猜,重啟會讓問題變得更糟糕。MongoDB提供了選項“noIndexBuildRetry”押蚤,它會指示MongoDB重啟后不再繼續(xù)沒建完的索引蔑歌。如果不小心在前臺創(chuàng)建了索引導致集群不可用,可以使用--noIndexBuildRetry 參數(shù)重啟各個分片來停止索引的創(chuàng)建過程揽碘,只用重啟主節(jié)點就可以了次屠。如果是在后臺創(chuàng)建索引,重啟時記得加上--noIndexBuildRetry雳刺,否則重啟后創(chuàng)建索引的線程會重新被喚醒劫灶,并由后臺創(chuàng)建變?yōu)榍芭_創(chuàng)建,導致整個集群不可用掖桦。
mongod -f $CONFIGFILE --noIndexBuildRetry
此方案遷移期間不用通知業(yè)務系統(tǒng)做變更本昏,把數(shù)據(jù)遷移完畢后,通知業(yè)務系統(tǒng)將表名變更枪汪,弊端就是在你遷移的過程中數(shù)據(jù)還是會持續(xù)增長的涌穆,問題分片的磁盤容量會越來越少。
方案二:
為了避免在遷移期間數(shù)據(jù)仍在增長雀久,導致數(shù)據(jù)還沒遷移完畢磁盤就爆滿的情況宿稀,可以選擇停止往舊表B內(nèi)寫入數(shù)據(jù),創(chuàng)建一個健康的新表A赖捌,新的數(shù)據(jù)往新表A內(nèi)寫祝沸,具體的查詢方案需要應用系統(tǒng)的配合。然后將舊表B的數(shù)據(jù)遷移至新表C中越庇,最終將新表A的數(shù)據(jù)匯入新表C , 完成數(shù)據(jù)遷移罩锐。此次遷移數(shù)據(jù)耗時共9個月!T没摹唯欣!片鍵一定要慎重選擇,因為我們使用的MongoDB是3.4.7版本的搬味,不支持修改片鍵境氢,最新版本支持片鍵的修改蟀拷。
接下來介紹數(shù)據(jù)量較大時如何構(gòu)建索引--減少業(yè)務最少影響
在數(shù)據(jù)量較大或請求量較大,直接建立索引對性能有顯著影響時,可以利用復制集(數(shù)據(jù)量較大時一般為線上環(huán)境,使用復制集為必然選擇或者使用分片.)中部分機器宕機不影響復制集工作的特性,繼而建立索引。
(1)首先把 secondary server 停止萍聊,再注釋 --replSet 參數(shù)问芬,并且更改 MongoDB port 之后重新啟動 MongoDB,這時候 MongoDB 將進入 standalone 模式寿桨;
(2).在 standalone 模式下運行命令 ensureIndex 建立索引此衅,使用 foreground 方式運行也可以,建議使用background方式運行亭螟;
(3)建立索引完畢之后關(guān)閉 secondary server 按正常方式啟動;
4.根據(jù)上述 1~3 的步驟輪流為 secondary 建立索引挡鞍,最后把 primary server 臨時轉(zhuǎn)換為 secondary server,同樣按 1~3 的方法建立索引预烙,再把其轉(zhuǎn)換為 primary server墨微。
日志內(nèi)容大致如下:
2019-09-24T18:51:39.003+0800 I - ? ? ?? [conn33] ? Index Build: 838416900/876543270 95%
2019-09-24T20:10:08.360+0800 I INDEX ?? [conn33]? done building bottom layer, going to commit
2019-09-24T20:10:26.001+0800 I - ? ? ?? [conn33] ? Index: (2/3) BTree Bottom Up Progress: 11684400/876543270 1%
done building bottom layer, going to commit
?