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