MongoDB 運維實戰總結

MongoDB 運維實戰總結

[TOC]

一、MongoDB 集群簡介

MongoDB是一個基于分布式文件存儲的數據庫,其目的在于為WEB應用提供可擴展的高性能數據存儲解決方案。下面將以3臺機器介紹最常見的集群方案。具體介紹,可以查看官網 https://docs.mongodb.com/v3.4/introduction/

[圖片上傳失敗...(image-9d8f08-1537238445300)]

1、集群組件的介紹

  • mongos(路由處理):
    作為Client與MongoDB集群的請求入口,所有用戶請求都會透過Mongos協調,它會將數據請求發到對應的Shard(mongod)服務器上,再將數據合并后回傳給用戶。
  • config server(配置節點):
    即:配置服務器;主要保存數據庫的元數據,包含數據的分布(分片)以及數據結構,mongos收到client發出的需求后,會從config server加載配置信息并緩存于內存中。
    一般在生產環境會配置不只一臺config server,因為它保存的元數據極為重要,若損壞則影響整個集群運作。
  • shard(分片實例存儲數據):
    shard就是分片。MongoDB利用分片的機制來實現數據分布存儲與處理,達到橫向擴容的目的。默認情況下,數據在分片之間會自動進行移轉,以達到平衡,此動作是靠一個叫平衡器(balancer)的機制達成。
  • replica set(副本集):
    副本集實現了數據庫高可用,若沒做副本集,則一旦存放數據的服務器節點掛掉,數據就丟失了,相反若配置了副本集,則同樣的數據會保存在副本服務器中(副本節點),一般副本集包含了一個主節點與多個副本節點,必要時還會配置arbiter(仲裁結點)作為節點掛掉時投票用。
  • arbiter(仲裁節點):
    仲裁服務器本身不包含數據,僅能在主節點故障時,檢測所有副本服務器并選舉出新的主節點,其實現方式是通過主節點、副本節點、仲裁服務器之間的心跳(Heart beat)實現。

2、MongoDB應用場景

  • 網站數據:適合實時的插入,更新與查詢,并具備網站實時數據存儲所需的復制及高度伸縮性。
  • 緩存:由于性能很高,也適合作為信息基礎設施的緩存層。在系統重啟之后,搭建的持久化緩存可以避免下層的數據源過載。
  • 大尺寸、低價值的數據:使用傳統的關系數據庫存儲一些數據時可能會比較貴,在此之前,很多程序員往往會選擇傳統的文件進行存儲。
  • 高伸縮性的場景:非常適合由數十或者數百臺服務器組成的數據庫。
  • 用于對象及JSON數據的存儲:MongoDB的BSON數據格式非常適合文檔格式化的存儲及查詢。

3、選用MongoDB的緣由

選用MongoDB的數據是以BSON的數據格式,高度伸縮方便擴展,并且數據水平擴展非常簡單,支持海量數據存儲,性能強悍。

二、集群的監測

1、監測數據庫存儲統計信息

docker中進入mongos或shard實例,執行以下命令:

docker exec -it mongos bash;
mongo --port 20001;
use admin;
db.auth("root","XXX");

說明:通過此命令,可以查詢集群的成員的集合數量、索引數量等相關數據。

db.stats();

[圖片上傳失敗...(image-573c7a-1537238445300)]

2、查看數據庫的統計信息

說明:通過此命令,可以查看操作數量、內存使用狀況、網絡io等

db.runCommand( { serverStatus: 1 } );

[圖片上傳失敗...(image-325ffa-1537238445300)]

3、檢查復制集成員狀態

rs.status();

[圖片上傳失敗...(image-bfb25f-1537238445300)]

三、基本的運維操作

1、設置和查看慢查詢

# 設置慢查詢
db.setProfilingLevel(1,200);
# 查看慢查詢級別
db.getProfilingLevel();
# 查詢慢查詢日志,此命令是針對于某一庫進行設置
db.system.profile.find({ ns : 'dbName.collectionName'}).limit(10).sort( { ts : -1 } ).pretty();

2、查看執行操作時間較長的動作

db.currentOp({"active" : true,"secs_running" : { "$gt" : 2000 }});

[圖片上傳失敗...(image-941cf-1537238445300)]

3、動態調整日志級別和設置緩存大小

# 設置日志級別參數
db.adminCommand( { "getParameter": 1, "logLevel":1});
# 設置cache大小參數
db.adminCommand( { "setParameter": 1, "wiredTigerEngineRuntimeConfig": "cache_size=4G"});

4、添加和移除復制集成員

# 查看復制集成員
rs.status().members;
# 添加成員
rs.add('127.0.0.1:20001');
# 移除成員
rs.remove('127.0.0.1:20001');

5、設置數據庫和集合分片

# 在mongos admin庫設置庫允許分片
sh.enableSharding("dbName");
# 在mongos 的admin庫設置集合分片片鍵
sh.shardCollection("dbName.collectionName", { filedName: 1} );

6、添加和移除分片

# 查看分片狀態
sh.status();
# 在mongos執行添加分片(可以為單個實例或復制集)
db.runCommand( { removeShard: "shardName" } );
db.runCommand({addshard:"rs1/ip-1:20001,ip-2:20001,ip-3:20001"});
# 在mongos執行移除分片
db.runCommand( { removeShard: "shard3" } );
# 在mongos執行刷新mongos配置信息
db.runCommand("flushRouterConfig"));

說明:移除分片命令至少執行兩次才能成功刪除,執行到state為completed才真正刪除,否則就是沒用刪除成功,該分片處于{"draining" : true}狀態,該狀態下不但該分片沒用刪除成功,而且還影響接下來刪除其他分片操作,遇到該狀態再執行一次removeshard即可,最好就是刪除分片時一直重復執行刪除命令,直到state為completed; 還有一個需要注意的地方就是:被成功刪除的分片如果想要再加入集群時,必須將data數據目錄清理干凈才可以再加入集群,否則即使能加入成功也不會存儲數據,集合都不會被創建 另外:在刪除分片的時有可能整個過程出現無限{"draining" : true}狀態,等多久還是這樣,而且分片上面的塊一個都沒有移動到別的分片,解決辦法是:在config的config數據庫的shard集合中找到該分片的信息,并將draining字段由True改為False,再繼續試著刪除操作” 上面這句會立即返回,實際在后臺執行。 在數據移除的過程當中,一定要注意實例的日志信息,可能出現數據塊在遷移的過程中,始終找不到邊界條件,導致一直數據遷移不成功,一直重試,解決方案是刪除邊界數據,重啟實例;。如果此分片為主分片,需要先遷移主分片。db.runCommand( { movePrimary: "XXX", to: "other" });在完成刪除后,所有mongos上運行下面命令,再對外提供服務,當然也可以重新啟動所有mongos實例 。

7、數據的導入導出

# 導出允許指定導出條件和字段
mongoexport -h 127.0.0.1 --port 20001 -uxxx -pxxx -d xxx -c mobileIndex -o XXX.txt 
mongoimport -h 127.0.0.1 --port 20001 -uxxx -pxxx -d xxx -c mobileIndex --file XXX.txt

四、MongoDB數據遷移

1、遷移復制集當中的成員

  1. 關閉 mongod 實例,為了確保安全關閉,使用 shutdown 命令;
  2. 將數據目錄(即 dbPath )轉移到新機器上;
  3. 在新機器上啟動 mongod,其中節點的數據目錄為copy的文件目錄 ;
  4. 連接到復制集當前的主節點上;

如果新節點的地址發生變化,使用 rs.reconfig() 更新 復制集配置文檔 ;
舉例,下面的命令過程將成員中位于第 2 位的地址進行更新:

cfg = rs.conf()
cfg.members[2].host = "127.0.0.1:27017"
rs.reconfig(cfg)

使用 rs.conf() 確認使用了新的配置.
等待所有成員恢復正常,使用 rs.status() 檢測成員狀態。

2、遷移復制集主節點

在遷移主節點的時候,需要復制集選舉出一個新的主節點,在進行選舉的時候,復制集將讀寫,通常,這只會持續很短的時間,不過,應該盡可能在影響較小的時間段內遷移主節點.

  1. 將主節點降級,以使得正常的 failover開始.要將主節點降級,連接到一個主節點,使用 replSetStepDown方法或者使用rs.stepDown()方法,下面的例子使用了 rs.stepDown()方法進行降級:

    rs.stepDown()
    
  2. 等主節點降級為從節點,另一個成員成為 PRIMARY 之后,可以按照 “遷移復制集的一個成員”遷移這個降級了的節點.可以使用 rs.status()來確認狀態的改變。

3、從復制集其他節點恢復數據

MongoDB 通過復制集能保證高可靠的數據存儲,通常生產環境建議使用「3節點復制集」,這樣即使其中一個節點崩潰了無法啟動,我們可以直接將其數據清掉,重新啟動后,以全新的 Secondary 節點加入復制集,或者是將其他節點的數據復制過來,重新啟動節點,它會自動的同步數據,這樣也就達到了恢復數據的目的。

1、關閉需要數據同步的節點

docker stop node;  # docker環境中
db.shutdownServer({timeoutSecs: 60}); # 非docker環境

2、拷貝目標節點機器的數據存儲目錄(/dbPath)到當前機器的指定目錄。

scp 目標節點 shard/data -> 當前節點 shard/data

3、當前節點以復制過來的數據文件啟動節點

4、將新的節點添加到復制集

# 進入復制集的主節點,執行添加新的節點命令
rs.add("hostNameNew:portNew"); 
# 等待所有成員恢復正常,檢測成員狀態
rs.status();
# 移除原來的節點
rs.remove("hostNameOld>:portOld"); 

五、MongoDB線上問題場景解決

1、MongoDB 新建索引導致庫被鎖

問題說明:某線上千萬級別集合,為優化業務,直接執行新建索引命令,導致整個庫被鎖,應用服務出現不可用。

解決方案:找出此操作進程,并且殺死。改為后臺新建索引,速度很會慢,但是不會影響業務,該索引只會在新建完成之后,才會生效;

# 查詢運行時間超過200ms操作     
db.currentOp({"active" : true,"secs_running" : { "$gt" : 2000 }}) ;
# 殺死執行時間過長操作操作
db.killOp(opid)
# 后臺新建索引
db.collectionNmae.ensureIndex({filedName:1}, {background:true});

2、MongoDB沒有限制內存,導致實例退出

問題說明:生產環境某臺機器啟動多個mongod實例,運行一段時間過后,進程莫名被殺死;

解決方案:現在MongoDB使用WiredTiger作為默認存儲引擎,MongoDB同時使用WiredTiger內部緩存和文件系統緩存。從3.4開始,WiredTiger內部緩存默認使用較大的一個:50%(RAM - 1 GB),或256 MB。
例如,在總共4GB RAM的系統上,WiredTiger緩存將使用1.5GB的RAM()。相反,具有總共1.25 GB RAM的系統將為WiredTiger緩存分配256 MB,因為這超過總RAM的一半減去1千兆字節()。0.5 * (4 GB - 1GB) = 1.5 GB``0.5 * (1.25 GB - 1 GB) = 128 MB < 256 MB。如果一臺機器存在多個實例,在內存不足的情景在,操作系統會殺死部分進程;

# 要調整WiredTiger內部緩存的大小,調節cache規模不需要重啟服務,我們可以動態調整:
db.adminCommand( { "setParameter": 1, "wiredTigerEngineRuntimeConfig": "cache_size=xxG"})

3、MongoDB刪除數據,不釋放磁盤空間

問題說明:在刪除大量數據(本人操作的數據量在2000萬+)的情景下,并且在生產環境中請求量較大,此時機器的cpu負載會顯得很高,甚至機器卡頓無法操作,這樣的操作應該謹慎分批量操作;在刪除命令執行結束之后,發現磁盤的數據量大小并沒有改變。

解決方案:

  • 方案一:我們可以使用MongoDB提供的在線數據收縮的功能,通過Compact命令db.collectionName.runCommand("compact")進行Collection級別的數據收縮,去除集合所在文件碎片。此命令是以Online的方式提供收縮,收縮的同時會影響到線上的服務。為了解決這個問題,可以先在從節點執行磁盤整理命令,操作結束后,再切換主節點,將原來的主節點變為從節點,重新執行Compact命令即可。

  • 方案二:使用從節點重新同步,secondary節點重同步,刪除secondary節點中指定數據,使之與primary重新開始數據同步。當副本集成員數據太過陳舊,也可以使用重新同步。數據的重新同步與直接復制數據文件不同,MongoDB會只同步數據,因此重同步完成后的數據文件是沒有空集合的,以此實現了磁盤空間的回收。

    1. 若是primary節點,先強制將之變為secondary節點,否則跳過此步驟:rs.stepdown(120);
    2. 然后在primary上刪除secondary節點:rs.remove("IP:port");
    3. 刪除secondary節點dbpath下的所有文件
    4. 將節點重新加入集群,然后使之自動進行數據的同步:rs.add("IP:port");
    5. 等數據同步完成后,循環1-4的步驟可以將集群中所有節點的磁盤空間釋放

    針對一些特殊情況,不能下線secondary節點的,可以新增一個節點到副本集中,然后secondary就自動開始數據的同步了。總的來說,重同步的方法是比較好的,第一基本不會阻塞副本集的讀寫,第二消耗的時間相對前兩種比較短。

4、MongoDB機器負載極高

問題說明:此情景是在客戶請求較大的情景性,由于部署MongoDB的機器包含一主一從,MongoDB使得IO100%,數據庫阻塞,出現大量慢查詢,進而導致機器負載極高,應用服務完全不可用。

解決方案:在沒有機器及時擴容的狀況下,首要任務便是減小機器的IO,在一臺機器出現一主一從,在大量數據寫入的情況下,會互相搶占IO資源。于是此時摒棄了MongoDB高可用的特點,摘掉了復制集當中的從節點,保證每臺機器只有一個節點可以占用磁盤資源。之后,機器負載立馬下來,服務變為正常可用狀態,但是此時MongoDB無法保證數據的完整性,一旦有主節點掛掉便會丟失數據。此方案只是臨時方法,根本解決是可以增加機器的內存、使用固態硬盤,或者采用增加分片集來減少單個機器的讀寫壓力。

# 進入主節點,執行移除成員的命令
rs.remove("127.0.0.1:20001");
# 注意:切勿直接關停實例

5、MongoDB分片鍵選擇不當導致熱讀熱寫

問題說明:生產環境中,某一集合的片鍵使用了與_id生成方式相似,含有時間序列的字段作為升序片鍵,導致數據寫入時都在一個數據塊,隨著數據量增大,會造成數據遷移到前面的分區,造成系統資源的占用,偶爾出現慢查詢。

解決方案:臨時方案設置數據遷移的窗口,放在在正常的時間區段,對業務造成影響。根本解決是更換片鍵。

# 連接mongos實例,執行以下命令
db.settings.update({ _id : "balancer" }, { $set : { activeWindow : { start : "23:00", stop : "4:00" } } }, true );
# 查看均衡窗口
sh.getBalancerWindow();

六、MongoDB優化建議

1、應用層面優化

  1. 查詢優化:確認你的查詢是否充分利用到了索引,用explain命令查看一下查詢執行的情況,添加必要的索引,避免掃表操作。
  2. 合理設計分片鍵:增量sharding-key:適合于可劃分范圍的字段,比如integer、float、date類型的,查詢時比較快。隨機sharding-key: 適用于寫操作頻繁的場景,而這種情況下如果在一個shard上進行會使得這個shard負載比其他高,不夠均衡,故而希望能hash查詢key,將寫分布在多個shard上進行,考慮復合key作為sharding key, 總的原則是查詢快,盡量減少跨shard查詢,balance均衡次數少;單一遞增的sharding key,可能會造成寫數據全部在最后一片上,最后一片的寫壓力增大,數據量增大,會造成數據遷移到前面的分區。MongoDB默認是單條記錄16M,尤其在使用GFS的時候,一定要注意shrading-key的設計。不合理的sharding-key會出現,多個文檔,在一個chunks上,同時,因為GFS中存貯的往往是大文件,導致MongoDB在做balance的時候無法通過sharding-key來把這多個文檔分開到不同的shard上, 這時候MongoDB會不斷報錯最后導致MongoDB倒掉。解決辦法:加大chunks大小(治標),設計合理的sharding-key(治本)。
  3. 通過profile來監控數據:進行優化查看當前是否開啟profile功能 用命令db.getProfilingLevel() 返回level等級,值為0|1|2,分別代表意思:0代表關閉,1代表記錄慢命令,2代表全部。開啟profile功能命令為 db.setProfilingLevel(level); #level等級,值level為1的時候,慢命令默認值為100ms,更改為db.setProfilingLevel(level,slowms)如db.setProfilingLevel(1,50)這樣就更改為50毫秒通過db.system.profile.find() 查看當前的監控日志。

2、硬件層面優化

  1. 確定熱數據大小:可能你的數據集非常大,但是這并不那么重要,重要的是你的熱數據集有多大,你經常訪問的數據有多大(包括經常訪問的數據和所有索引數據)。使用MongoDB,你最好保證你的熱數據在你機器的內存大小之下,保證內存能容納所有熱數據;
  2. 選擇正確的文件系統:MongoDB的數據文件是采用的預分配模式,并且在Replication里面,Master和Replica Sets的非Arbiter節點都是會預先創建足夠的空文件用以存儲操作日志。這些文件分配操作在一些文件系統上可能會非常慢,導致進程被Block。所以我們應該選擇那些空間分配快速的文件系統。這里的結論是盡量不要用ext3,用ext4或xfs;

3、架構上的優化

? 盡可能讓主從節點分攤在不同的機器上,避免IO操作的與MongoDB在同一臺機器;

七、總結

MongoDB具有高性能、易擴展、易上手等特點,在正確使用的情況下,其本身性能還是非常強悍,在一些關鍵點如片鍵的選擇、內存的大小和磁盤IO,往往是限制其性能的最大瓶頸。針對于片鍵,在業務系統初期,可以先不對集合進行數據分片,因為分片鍵一旦確定就無法修改,后期可根據業務系統的情況,認真篩選字段。一般情況下,不建議使用升序片鍵(是一種隨著時間穩定增長的字段,自增長的主鍵是升序鍵 ),因為這個會導致局部的熱讀熱寫,不能發揮分片集群的真正實力。建議使用hash片鍵或者隨機分發的片鍵,這樣可以保證數據的均勻分發在分片節點;針對于內存,建議內存的大小能夠包含熱數據的大小加索引大小,保證內存能容納所有熱數據 。針對于磁盤資源,MongoDB的高速讀寫是以磁盤的IO作為基礎,為了保證其性能,建議將主從節點以及高IO的應用分離,以保證IO資源盡可能不存在搶占。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374