這場MongDB事故暴露的潛在危機,你是否也正在忽視?
一、MongoDB特性
MongoDB是一個可擴展的高性能基于文檔的NoSQL數(shù)據(jù)庫,具備但不限于以下特性:
無數(shù)據(jù)結(jié)構(gòu)限制和高性能
- MongoDB以文檔結(jié)構(gòu)的存儲方式,能夠更便捷的獲取數(shù)據(jù);
- MongoDB沒有表結(jié)構(gòu)的概念,每條記錄可以有完全不同的結(jié)構(gòu),業(yè)務(wù)開發(fā)方便快捷,而SQL數(shù)據(jù)庫需要事先定義表結(jié)構(gòu)再使用;
- 在簡單的業(yè)務(wù)結(jié)構(gòu)下,其性能高于MySQL,如:MongoDB不指定_id插入>MySQL不指定主鍵插入>MySQL指定主鍵插入。
豐富的支持
- Redis的key-value(只能按key查詢,靈活性和易用性不足),而MongoDB支持單鍵索引、多鍵索引、數(shù)組索引、全文索引、地理位置索引、過期索引等;
- MongoDB執(zhí)行支持范圍查詢,正則表達(dá)式查詢,對子文檔屬性的查詢等,是最像SQL數(shù)據(jù)庫的NoSQL數(shù)據(jù)庫;
- 內(nèi)置GridFS,支持大容量的存儲。
方便的冗余與擴展
- MongoDB的單機可靠性較差,但其復(fù)制集可保證數(shù)據(jù)安全;
- 分片擴展可處理大數(shù)據(jù)量的業(yè)務(wù),其副本、分片伸縮擴展方便,維護(hù)簡單。
良好的支持
- 官方擁有完善的文檔;
- 齊全的驅(qū)動支持:官方提供了多數(shù)流行編程語言的驅(qū)動支持;
- 擁有諸多第三方社區(qū)論壇。
二、MongoDB故障經(jīng)歷
基于MongoDB的優(yōu)異性能,在我司也越來越多的使用。隨著業(yè)務(wù)變更,負(fù)載增加,一些問題也逐漸暴露出來,在我司內(nèi)部最近就遭遇多起MongoDB故障,下面簡單介紹其中一個:
業(yè)務(wù)背景
該套系統(tǒng)為內(nèi)部主機監(jiān)控系統(tǒng),包括IO、CPU、內(nèi)存、文件系統(tǒng)以及網(wǎng)絡(luò)數(shù)據(jù)等,根據(jù)其監(jiān)控項目本身特性,采樣頻率幾秒到10分鐘不等,因接入監(jiān)控的主機數(shù)量較多,每日數(shù)據(jù)量較大。
該業(yè)務(wù)初始使用了3分片的cluster,分片規(guī)劃由業(yè)務(wù)側(cè)同事規(guī)劃實施。
故障過程
業(yè)務(wù)側(cè)反饋特定某些時段業(yè)務(wù)無法連接到MongoDB,往往幾分鐘后自動恢復(fù)了正常,且最近業(yè)務(wù)響應(yīng)比以前明顯減慢。
故障分析
接到反饋后,檢查MongoDB日志,發(fā)現(xiàn)階段性出現(xiàn)連接用盡的告警,猜測可能是業(yè)務(wù)最近有過調(diào)整。后經(jīng)業(yè)務(wù)同事確認(rèn),最近確認(rèn)新接入了一批主機到該監(jiān)控系統(tǒng)。
- MongoDB連接數(shù)、maxConns參數(shù)和os參數(shù)配置的單個進(jìn)程能打開的最大文件描述符數(shù)總量的80%決定的,取兩個之間的最小值;
- maxConns配置為3000,系統(tǒng)open files參數(shù)為1024;
- MongoDB最大連接數(shù)為(open files value) * 80%=1024* 80%=819。
理論值和故障時情況匹配,判定造成業(yè)務(wù)間斷性無法連接的原因是參數(shù)設(shè)置不合理。
間斷性出現(xiàn)和業(yè)務(wù)模式有關(guān)系,前面提到業(yè)務(wù)不同指標(biāo)間隔幾秒到十分鐘采樣,采樣入庫峰值時部分業(yè)務(wù)失敗。
臨時處置
得益于MongoDB的副本集方便調(diào)整的特點,滾動修改了各個節(jié)點參數(shù)(調(diào)整open files value到64000),并重啟MongoDB服務(wù)。
深入分析
此次故障原因很簡單,但這引發(fā)了我們一些反思:
- 我們的使用方式真的正確嗎?
- 我們的配置真的合理嗎?
- 我們關(guān)注過性能嗎?
- 我們的設(shè)計真的合理么?
帶著這樣的問題,我們重新審視分析了該系統(tǒng)的使用情況。不查不知道,一查下一跳,真的檢查出諸多問題:
- 操作系統(tǒng)所有參數(shù)均為默認(rèn)參數(shù);
- MongoDB除必要參數(shù)外均為默認(rèn)配置;
- MongoDB集合未分片或分片不合理,不能發(fā)揮MongoDB分片的優(yōu)勢,所有負(fù)載都集中在一個分片。
調(diào)整優(yōu)化
針對存在的問題做了以下幾方面的調(diào)整:
調(diào)整系統(tǒng)參數(shù)
- /etc/security/limits.conf
- mongo soft nofile 64000
- mongo hard nofile 64000
- mongo soft nproc 32000
- mongo hard nproc 32000
- /etc/sysctl.conf
- fs.file-max=98000
- kernel.pid_max=64000
- kernel.threads-max=64000
- vm.max_map_count=128000
此外禁用了numa,Transparent HugePages及修改了磁盤調(diào)度策略:
開啟MongoDB Profile
- profile = 1
- slowms = 200
結(jié)合業(yè)務(wù)重新設(shè)置集合片鍵
重新設(shè)計分片是本次調(diào)整的重點,那么為什么說當(dāng)前系統(tǒng)的分片不合理,分片的依據(jù)是什么呢?這里先說一下我們評估的依據(jù):
好的片鍵
- 將插入數(shù)據(jù)均勻分布到各個分片上;
- 保證CRUD操作能夠利用局部性;
- 有足夠的粒度進(jìn)行塊拆分;
- 片鍵上必須有索引,最好選擇業(yè)務(wù)會用到的索引字段分片。
不好的片鍵
- 小基數(shù)片鍵:隨著數(shù)據(jù)的增加,一個chunk逐漸變大,無法繼續(xù)分裂,只能添加硬盤來保證足夠的空間存儲數(shù)據(jù);
- 避免升序片鍵:范圍分片的范圍為正負(fù)無窮,如果使用升序片鍵(包含object_id及時間,自增主鍵等),那么最近的數(shù)據(jù)始終存儲在一個分片,不能充分利用到分片帶來的好處;
- 隨機片鍵:隨著數(shù)據(jù)的增大,由于其隨機性,分片間的數(shù)據(jù)平衡可能需要加載大量的塊到內(nèi)存和引發(fā)大量IO,導(dǎo)致性能降低。
當(dāng)前數(shù)據(jù)庫內(nèi)僅有少數(shù)集合進(jìn)行了分片,并且片鍵均為時間類型,造成負(fù)載集中在其中一個分片上。我們希望能找到一種既能保證足夠的粒度,不會造成巨型chunk,也能控制分片粒度,不會降低效率。
按照上述原則,我們搭建了測試環(huán)境,與業(yè)務(wù)同事共同討論并進(jìn)行了多次測試,嘗試了不同的分片組合及分片方式,對比了不同分片下的業(yè)務(wù)性能,我們總結(jié)出如下規(guī)則:
{Locality: 1,search : 1}:第一個字段控制局部的數(shù)據(jù),第二個字段為常用的搜索字段;第一個字段為粗粒度字段,第二個字段為細(xì)粒度字段。
結(jié)果對比
在整個調(diào)整過程中,經(jīng)歷了多次壓測,下面展示部分測試數(shù)據(jù):
服務(wù)器ip+信息采集時間組合分片測試數(shù)據(jù)
上表為最終的片鍵改造方案下的部分壓測數(shù)據(jù),可以看出,調(diào)整前后性能提升較大。
三、心得
我們整理一下mongodb在咪咕的運維心得,希望能拋磚引玉。
運維流程機制
- 務(wù)必建立完善的運維管理流程,故障處理機制等;
- 規(guī)范化、模板化:對日常運維務(wù)必做到規(guī)范化,比如安裝規(guī)范,避免人為原因重復(fù)踩坑。
硬件配置
確保內(nèi)存設(shè)置能滿足性能需求:確保內(nèi)存>索引容量+高頻訪問數(shù)據(jù)容量
- 大多數(shù)情況下,MongoDB熱數(shù)據(jù)(索引和最頻繁訪問的數(shù)據(jù))全部緩存在RAM中時性能最好;
- 相對于其它優(yōu)化,擴大內(nèi)存的效果尤為顯著;如果熱數(shù)據(jù)超過了單個服務(wù)器的RAM,此時往往需要考慮擴大內(nèi)存或者分片。
使用SSD磁盤
- 寫操作負(fù)載高的應(yīng)用采用SSD:SSD提供強大隨機讀取性能,大部分情況下符合MongoDB的數(shù)據(jù)訪問模式。
使用RAID
- 出于安全和性能考慮,可采用合適的RAID模式,推薦RAID-10。
選用多核和更快的CPU
- MongoDB在更快的CPU上提供更好的性能,且WiredTiger存儲引擎能夠充分利用多核處理器資源(并發(fā)線程數(shù)和cpu核心數(shù)量相等)。
系統(tǒng)配置
開啟NTP時間同步
- 使用復(fù)制集或者分片集群需要開啟NTP時間同步,這對于MongoDB正常運行尤為重要。
禁用NUMA
- MongoDB運行在NUMA系統(tǒng)上會導(dǎo)致性能下降,因此需關(guān)閉NUMA配置。
- linux6 修改/boot/grub/grub.conf中kernel,添加numa=off
- linux7 修改/etc/grub2.cfg中l(wèi)inux16部分添加numa=off
禁用Transparent Huge Pages
- 數(shù)據(jù)庫往往具有稀疏而不是連續(xù)的內(nèi)存訪問模式。應(yīng)該在Linux機器上禁用THP以確保使用MongoDB獲得最佳性能。
- kernel 參數(shù)添加transparent_hugepage=never
設(shè)置readahead
- 預(yù)讀值是文件操作系統(tǒng)的一個優(yōu)化手段,程序請求讀取一個頁面的時候,文件系統(tǒng)會同時讀取下面的幾個頁面并返回。
- 設(shè)置合理的readahead值有利于提高M(jìn)ongoDB性能,使用MMAPv1引擎推薦設(shè)置為32或16,對于WiredTiger無論何種存儲介質(zhì)都建議設(shè)置為0。
- blockdev --report
- blockdev --setra 0 /dev/sda
設(shè)置合適的磁盤調(diào)度策略
- 磁盤調(diào)度策略應(yīng)當(dāng)根據(jù)應(yīng)用類型和硬件配置進(jìn)行設(shè)置,對于MongoDB,推薦使用noop。
- sed -i '/vmlinuz-/s/$/ elevator=deadline/' /boot/grub/grub.conf
文件系統(tǒng)選擇
- MongoDB在WiredTiger存儲引擎下建議使用XFS文件系統(tǒng)。
關(guān)閉數(shù)據(jù)庫文件的atime
- 操作系統(tǒng)會維護(hù)文件最后的訪問時間metadata,對于數(shù)據(jù)庫意味著每次文件系統(tǒng)每訪問一個頁就會提交一個寫操作,這將降低整個數(shù)據(jù)庫的性能,禁止系統(tǒng)對文件的訪問時間更新會有效提高文件讀取的性能。
- /dev/xvdb /data xfs noatime,nodiratime 0 0
設(shè)置合理的系統(tǒng)內(nèi)核參數(shù)
系統(tǒng)為防止單個用戶/進(jìn)程占用大量資源(比如線程、文件等),在內(nèi)核參數(shù)上進(jìn)行了限制,這些限制默認(rèn)值較低,這會導(dǎo)致MongoDB運行可能遭遇一些不必要的問題。因此應(yīng)當(dāng)根據(jù)實際情況對內(nèi)核參數(shù)進(jìn)行適當(dāng)調(diào)整。
- mongo soft nofile 64000
- mongo hard nofile 64000
- mongo soft nproc 32000
- mongo hard nproc 32000
- fs.file-max=98000
- kernel.pid_max=64000
- kernel.threads-max=64000
- vm.max_map_count=128000
MongoDB配置
盡量避免使用單機
- 單機不具備容錯能力,生產(chǎn)中應(yīng)當(dāng)盡量避免使用,如處于某些限制只能使用單機,那么需要確保擁有完善的備份機制和故障恢復(fù)機制。
每臺服務(wù)承載一個MongoDB實例
- 為獲得最佳性能,每個服務(wù)器只部署一個MongoDB實例,降低資源爭奪;如一臺服務(wù)器上需要運行多個MongoDB實例,應(yīng)當(dāng)為每個實例分配合理的內(nèi)存,避免內(nèi)存爭奪導(dǎo)致oom。
分片使用多路查詢路由
- 在不同服務(wù)器上部署mongos,最好將mongos部署在應(yīng)用服務(wù)器上,應(yīng)用連接本機的mongos。
存儲引擎配置數(shù)據(jù)壓縮
- MongoDB在使用WiredTiger和encrypted引擎時默認(rèn)開啟了壓縮,壓縮比約為70%--80%;
- MongoDB WiredTiger默認(rèn)使用Snappy,該選項消耗較低的CPU資源獲得較高的壓縮率,此外提供zlib選項,該選項比Snappy擁有更高的壓縮率,但會消耗更多的CPU資源。
設(shè)置合理的Path
- 如條件允許,將數(shù)據(jù)和索引目錄分開,每個目錄掛在不同的硬盤設(shè)備,將數(shù)據(jù)和目錄存放到不同的物理設(shè)備。
- 啟用directoryPerDB,每個數(shù)據(jù)庫不同目錄,每個目錄掛在不同的設(shè)備。
設(shè)置合理的oplogsize
- 設(shè)置足夠的oplog大小,確保足夠的同步/維護(hù)時間窗口,避免因oplogsize太小導(dǎo)致同步中斷。
啟用安全認(rèn)證
- 啟用安全認(rèn)證會降低MongoDB性能,出于安全考慮,任然建議開啟安全認(rèn)證,除非MongoDB運行在安全的網(wǎng)絡(luò)環(huán)境之內(nèi)。
選擇合適的片鍵
- 片鍵的選擇對于分片集群的性能至關(guān)重要,合理的片鍵可以提高M(jìn)ongoDB整體性能,糟糕的片鍵可能會讓你的MongoDB集群不如單機MongoDB。
好的片鍵應(yīng)當(dāng)具有以下特征:
- 將插入數(shù)據(jù)均勻分布到各個分片上;
- 保證CRUD操作能夠利用局部性;
- 有足夠的粒度進(jìn)行塊拆分;
- 片鍵上必須有索引,因此選擇業(yè)務(wù)會用到的索引字段分片,好處是可以避免索引浪費,減少空間和性能損失。
善用索引
- 如果沒有索引MongoDB需要把所有的Document從盤上讀到內(nèi)存,這會對MongoDB服務(wù)器造成較大的壓力并影響到其他請求的執(zhí)行。
- 同時,應(yīng)當(dāng)根據(jù)業(yè)務(wù)選擇合適的索引屬性,比如可以利用TTL自動刪除過期的數(shù)據(jù)。
避免索引濫用
- 不依賴于每個字段的獨立索引,合適的組合索引相比于每個字段創(chuàng)建索引占用存儲空間更小且同樣能提升效率,但需要注意組合索引字段順序及排序問題。
監(jiān)控profile
- 開啟mongodb的profile對該實例的操作進(jìn)行監(jiān)控,為性能優(yōu)化提供依據(jù);
啟用Log Rotation
- MongoDB默認(rèn)情況下不會自動的切換日志的,這將會導(dǎo)致日志逐漸增大,在繁忙的業(yè)務(wù)下,日志增長快。查看某一時段的日志極不方便。需要對MongoDB日志文件進(jìn)行切換,根據(jù)實際需求保留若干天。
程序合理配置驅(qū)動
- 程序應(yīng)根據(jù)MongoDB架構(gòu)和業(yè)務(wù)需求配置驅(qū)動程序,從而實現(xiàn)讀寫分離、故障轉(zhuǎn)移等。