MongoDB 最佳實踐

c5p1ng發表於2021-03-02

關於安全

為MongoDB叢集啟用認證鑑權

MongoDB伺服器在預設安裝下不啟用鑑權。這意味著每個人都可以直接連線到mongod例項並執行任意資料庫操作。

為不同使用者分配不同的角色許可權

MongoDB支援按角色定義的許可權系統。你應該基於“最少許可權”準則,顯式的為使用者分配僅需要的相應許可權。

使用中央鑑權伺服器

儘可能使用LDAP、Kerbero之類的中央鑑權伺服器,並使用強口令策略。

為需要訪問MongoDB的應用伺服器建立白名單(防火牆配置)

如果你的伺服器有多個網路卡,建議只在內網的IP上監聽服務。

對敏感資料使用加密引擎

MongoDB企業版支援儲存加密,對涉及到客戶的敏感資料應該使用加密引擎來保護資料。

關於部署

至少使用3個資料節點的複製集

MongoDB的建議最小部署是3個資料節點構成的複製集。複製集可以提供以下優點:

  • 系統99.999% 高可用
  • 自動故障切換
  • 資料冗餘
  • 容災部署
  • 讀寫分離

不用太早分片

分片可以用來擴充套件你係統的讀寫能力,但是分片也會帶來不少新的挑戰比如說管理上的複雜度,成本的增加,選擇合適片鍵的挑戰性等等。一般來說,你應該先窮盡了其他的效能調優的選項以後才開始考慮分片,比如說,索引優化,模式優化,程式碼優化,硬體資源優化,IO優化等。

選擇合適的分片數

分片的一些觸發條件為:

  • 資料總量太大,無法在一臺伺服器上管理
  • 併發量太高,一臺伺服器無法及時處理
  • 磁碟IO壓力太大
  • 單機系統記憶體不夠大,無法裝下熱資料
  • 伺服器網路卡處理能力達到瓶頸
  • 多地部署情況下希望支援本地化讀寫
    取決於你分片的觸發條件,你可以按照總的需求 然後除以每一臺伺服器的能力來確定所需的分片數。

為每個分片部署足夠的複製整合員

分片之間的資料互相不復制。每個分片的資料必須在分片內保證高可用。因此,對每一個分片MongoDB要求至少部署3個資料節點來保證該分片在絕大部分時間都不會因為主節點當機而造成資料不可用。

選擇合適的片鍵

在分片場景下, 最重要的一個考量是選擇合適的片鍵。選擇片鍵需要考慮到應用的讀寫模式。通常來說一個片鍵要麼是對寫操作優化,要麼是對讀操作優化。要根據哪種操作更加頻繁而進行相應的權衡。

  • 片鍵值應該具有很高的基數,或者說,這個片鍵在集合內有很多不同的值,例如_id就是一個基數很高的片鍵因為_id值不會重複
  • 片鍵一般不應該是持續增長的,比如說timestamp就是個持續增長的片鍵。此類片鍵容易造成熱分片現象,即新的寫入集中到某一個分片上
  • 好的片鍵應該會讓查詢定向到某一個(或幾個)分片上從而提高查詢效率。一般來說這個意味著片鍵應該包括最常用查詢用到的欄位
  • 好的片鍵應該足夠分散,讓新的插入可以分佈到多個分片上從而提高併發寫入率。
  • 可以使用幾個欄位的組合來組成片鍵,以達到幾個不同的目的(基數,分散性,及查詢定向等)

關於系統

使用SSD 或RAID10 來提高儲存IOPS能力

MongoDB是一個高效能高併發的資料庫,其大部分的IO操作為隨機更新。一般來說本機自帶的SSD是最佳的儲存方案。如果使用普通的硬碟,建議使用RAID10條帶化來提高IO通道的併發能力。

為Data和Journal/log使用單獨的物理卷

MongoDB很多的效能瓶頸和IO相關。建議為日誌盤(Journal和系統日誌)單獨設定一個物理卷,減少對資料盤IO的資源佔用。
系統日誌可以直接在命令列或者配置檔案引數內指定。Journal日誌不支援直接指定到另外的目錄,可以通過對Journal目錄建立symbol link的方式來解決。

使用XFS 檔案系統

MongoDB在WiredTiger儲存引擎下建議使用XFS檔案系統。Ext4最為常見,但是由於ext檔案系統的內部journal和WiredTiger有所衝突,所以在IO壓力較大情況下表現不佳。

WiredTiger下謹慎使用超大快取

WiredTiger 對寫操作的落盤是非同步發生的。預設是60秒做一次checkpoint。做checkpoint需要對記憶體內所有髒資料遍歷以便整理然後把這些資料寫入硬碟。如果快取超大(如大於128G),那麼這個checkpoint時間就需要較長時間。在checkpoint期間資料寫入效能會受到影響。目前建議實際快取設定在64GB或以下。

關閉 Transparent Huge Pages

Transparent Huge Pages (THP) 是Linux的一種記憶體管理優化手段,通過使用更大的記憶體頁來減少Translation Lookaside Buffer(TLB)的額外開銷。 MongoDB資料庫大部分是比較分散的小量資料讀寫,THP對MongoDB這種工況會有負面的影響所以建議關閉。
http://docs.mongoing.com/manual-zh/tutorial/transparent-huge-pages.html

啟用Log Rotation

防止MongoDB 的log檔案無限增大,佔用太多磁碟空間。好的實踐是啟用log rotation並及時清理歷史日誌檔案。

分配足夠的Oplog空間

足夠的Oplog空間可以保證有足夠的時間讓你從頭恢復一個從節點,或者對從節點執行一些比較耗時的維護操作。假設你最長的下線維護操作需要H小時,那麼你的Oplog 一般至少要保證可以儲存 H 2 或者 H3 小時的oplog。

關閉資料庫檔案的 atime

禁止系統對檔案的訪問時間更新會有效提高檔案讀取的效能。這個可以通過在 /etc/fstab 檔案中增加 noatime 引數來實現。例如:
/dev/xvdb /data ext4 noatime 0 0
修改完檔案後重新 mount就可以:
mount -o remount /data

提高預設檔案描述符和程式/執行緒數限制

Linux預設的檔案描述符數和最大程式數對於MongoDB來說一般會太低。建議把這個數值設為64000。因為MongoDB伺服器對每一個資料庫檔案以及每一個客戶端連線都需要用到一個檔案描述符。如果這個數字太小的話在大規模併發操作情況下可能會出錯或無法響應。 你可以通過以下命令來修改這些值:
ulimit -n 64000 ulimit -u 64000

禁止 NUMA

在一個使用NUMA技術的多處理器Linux 系統上,你應該禁止NUMA的使用。MongoDB在NUMA環境下執行效能有時候會可能變慢,特別是在程式負載很高的情況下。

預讀值(readahead)設定

預讀值是檔案作業系統的一個優化手段,大致就是在程式請求讀取一個頁面的時候,檔案系統會同時讀取下面的幾個頁面並返回。這原因是因為很多時候IO最費時的磁碟尋道。通過預讀,系統可以提前把緊接著的資料同時返回。假設程式是在做一個連續讀的操作,那麼這樣可以節省很多磁碟尋道時間。
MongoDB很多時候會做隨機訪問。對於隨機訪問,這個預讀值應該設定的較小為好.一般來說32是一個不錯的選擇。你可以使用下述命令來顯示當前系統的預讀值:
blockdev --report
要更改預讀值,可以用以下命令:
blockdev --setra 32

使用NTP時間伺服器

在使用MongoDB複製集或者分片叢集的時候,注意一定要使用NTP時間伺服器。這樣可以保證MongoDB叢集成原則之間正確同步。

關於索引

為你的每一個查詢建立合適的索引

這個是針對於資料量較大比如說超過幾十上百萬(文件數目)數量級的集合。如果沒有索引MongoDB需要把所有的Document從盤上讀到記憶體,這會對MongoDB伺服器造成較大的壓力並影響到其他請求的執行。

建立合適的組合索引,不要依賴於交叉索引

如果你的查詢會使用到多個欄位,MongoDB有兩個索引技術可以使用:交叉索引和組合索引。交叉索引就是針對每個欄位單獨建立一個單欄位索引,然後在查詢執行時候使用相應的單欄位索引進行索引交叉而得到查詢結果。交叉索引目前觸發率較低,所以如果你有一個多欄位查詢的時候,建議使用組合索引能夠保證索引正常的使用。
例如,如果應用需要查詢所有年齡小於30歲的運動員:
db.athelets.find({sport: "marathon", location: "sz", age: {$lt: 30}}})
那麼你可能需要這樣的一個索引:
db.athelets.ensureIndex({sport:1, location:1, age:1});

組合索引欄位順序:匹配條件在前,範圍條件在後(Equality First, Range After)

以上文為例子,在建立組合索引時如果條件有匹配和範圍之分,那麼匹配條件(sport: “marathon”) 應該在組合索引的前面。範圍條件(age: <30)欄位應該放在組合索引的後面。

儘可能使用覆蓋索引(Covered Index)

有些時候你的查詢只需要返回很少甚至只是一個欄位,例如,希望查詢所有虹橋機場出發的所有航班的目的地。已有的索引是:
{origin: 1, dest: 1}
如果正常的查詢會是這樣(只需要返回目的地機場):
db.flights.find({origin:"hongqiao"}, {dest:1});
這樣的查詢預設會包含_id 欄位,所以需要掃描匹配的文件並取回結果。相反,如果使用這個查詢語句:
db.flights.find({origin:"hongqiao"}, {_id:0, dest:1});
MongoDB則可以直接從索引中取得所有需要返回的值,而無需掃描實際文件(文件可能需要從硬碟裡調入到記憶體)

建索引要在後臺執行

在對一個集合建立索引時,該集合所在的資料庫將不接受其他讀寫操作。對資料量的集合建索引,建議使用後臺執行選項 {background: true}

程式配置

設定合適的MongoDB連線池大小 (Connections Per Host)

Java驅動的預設連線池大小是100。建議按照應用的實際情況做調整。對壓力較小的應用可以適當調小減少對應用伺服器的資源佔用。

正確使用寫關注設定(Write Concern)

MongoDB的建議最小部署是一個複製集,包含3個資料節點。預設情況下應用的寫操作(更新,插入或者刪除)在主節點上完成後就會立即返回。寫操作則通過OPLOG方式在後臺非同步方式複製到其他節點。在極端情況下,這些寫操作可能還未在複製到從節點的時候主節點就出現當機。這個時候發生主備節點切換,原主節點的寫操作會被回滾到檔案而對應用不可見。為防止這種情況出現,MongoDB建議對重要的資料使用 {w: “marjority”} 的選項。{w: “majority”} 可以保證資料在複製到多數節點後才返回成功結果。使用該機制可以有效防止資料回滾的發生。
另外你可以使用 {j:1} (可以和 w:”majrotiy” 結合使用) 來指定資料必須在寫入WAL日誌之後才嚮應用返回成功確認。這個會導致寫入效能有所下降,但是對於重要的資料可以考慮使用。

正確使用讀選項設定(Read Preference)

MongoDB由於是一個分散式系統,一份資料會在多個節點上進行復制。從哪個節點上讀資料,要根據應用讀資料的需求而定。以下是集中可以配置的讀選項:

  • primary: 預設,在主節點上讀資料
  • priaryPreferred: 先從主節點上讀,如果為成功再到任意一臺從節點上讀
  • secondary: 在從節點上讀資料(當有多臺節點的時候,隨機的使用某一臺從節點)
  • secondaryPreferred: 首先從從節點上讀,如果從節點由於某種原因不能提供服務,則從主節點上進行讀
  • nearest: 從距離最近的節點來讀。距離由ping操作的時間來決定。
    除第一個選項之外,其他讀選項都存在讀到的資料不是最新的可能。原因是資料的複製是後臺非同步完成的。

不要例項化多個MongoClient

MongoClient是個執行緒安全的類,自帶執行緒池。通常在一個JVM內不要例項化多個MongoClient例項,避免連線數過多和資源的不必要浪費。

對寫操作使用Retry機制

MongoDB使用複製集技術可以實現99.999%的高可用。當一臺主節點不能寫入時,系統會自動故障轉移到另一臺節點。轉移可能會耗時幾秒鐘,在這期間應用應該捕獲相應的Exception並執行重試操作。重試應該有backoff機制,例如,分別在1s,2s,4s,8s等時候進行重試。

避免使用太長的欄位名

MongoDB 沒有表結構定義。每個文件的結構由每個文件內部的欄位決定。所有欄位名會在每個文件內重複。使用太長的欄位名字會導致對記憶體、網路頻寬更多的需求。(由於壓縮技術,長欄位名對硬碟上的儲存不會有太多佔用)

使用投射 (projection)來減少返回的內容

MongoDB 支援類似於SQL語句裡面的select,可以對返回的欄位進行過濾。使用Projection可以減少返回的內容,降低網路傳輸的量和程式碼中轉化成物件所需的時間。

使用TTL來自動刪除過期的資料

很多時候我們用MongoDB來儲存一些時效性的資料,如7天的監控資料。與其自己寫個後臺指令碼定期清理過期資料,你可以使用TTL索引來讓MongoDB自動刪除過期資料:
db.data.ensureIndex({create_time:1}, {expireAfterSeconds: 7*24*3600})

相關文章