58同城作為中國最大的生活服務(wù)平臺,涵蓋了房產(chǎn)荣月、招聘、二手梳毙、二手車哺窄、黃頁等核心業(yè)務(wù)。58同城發(fā)展之初账锹,大規(guī)模使用關(guān)系型數(shù)據(jù)庫(SQL Server萌业、MySQL等),隨著業(yè)務(wù)擴展速度增加牌废,數(shù)據(jù)量和并發(fā)量演變的越來越有挑戰(zhàn)咽白,此階段58的數(shù)據(jù)存儲架構(gòu)也需要相應(yīng)的調(diào)整以更好的滿足業(yè)務(wù)快速發(fā)展的需求。MongoDB經(jīng)過幾個版本的迭代鸟缕,到2.0.0以后晶框,變的越來越穩(wěn)定,它具備的高性能懂从、高擴展性授段、Auto-Sharding、Free-Schema番甩、類SQL的豐富查詢和索引等特性侵贵,非常誘惑,同時58同城在一些典型業(yè)務(wù)場景下使用MongoDB也較合適缘薛,2011年窍育,我們開始使用MongoDB,逐步擴大了使用的業(yè)務(wù)線宴胧,覆蓋了58幫幫漱抓、58交友、58招聘恕齐、信息質(zhì)量等等多條業(yè)務(wù)線乞娄。隨著58每天處理的海量數(shù)據(jù)越來越大,并呈現(xiàn)不斷增多的趨勢,這為MongoDB在存儲與處理方面帶來了諸多的挑戰(zhàn)仪或。面對百億量級的數(shù)據(jù)确镊,我們該如何存儲與處理,本文將詳細介紹MongoDB遇到的問題以及最終如何“完美”解決范删。
本文詳細講述MongoDB在58同城的應(yīng)用實踐:MongoDB在58同城的使用情況蕾域;為什么要使用MongoDB;MongoDB在58同城的架構(gòu)設(shè)計與實踐到旦;針對業(yè)務(wù)場景我們在MongoDB中如何設(shè)計庫和表束铭;數(shù)據(jù)量增大和業(yè)務(wù)并發(fā),我們遇到典型問題及其解決方案厢绝;MongoDB如何監(jiān)控。
MongoDB在58同城的使用情況
MongoDB在58同城的眾多業(yè)務(wù)線都有大規(guī)模使用:58轉(zhuǎn)轉(zhuǎn)带猴、58幫幫昔汉、58交友、58招聘拴清、58信息質(zhì)量靶病、58測試應(yīng)用等,如[圖1]所示口予。
圖1 MongoDB典型的使用場景:轉(zhuǎn)轉(zhuǎn)
為什么要使用MongoDB娄周?
MongoDB這個來源英文單詞“humongous”,homongous這個單詞的意思是“巨大的”沪停、“奇大無比的”煤辨,從MongoDB單詞本身可以看出它的目標(biāo)是提供海量數(shù)據(jù)的存儲以及管理能力。MongoDB是一款面向文檔的NoSQL數(shù)據(jù)庫木张,MongoDB具備較好的擴展性以及高可用性众辨,在數(shù)據(jù)復(fù)制方面,支持Master-Slaver(主從)和Replica-Set(副本集)等兩種方式舷礼。通過這兩種方式可以使得我們非常方便的擴展數(shù)據(jù)鹃彻。MongoDB較高的性能也是它強有力的賣點之一,存儲引擎使用的內(nèi)存映射文件(MMAP的方式)妻献,將內(nèi)存管理工作交給操作系統(tǒng)去處理蛛株。MMAP的機制,數(shù)據(jù)的操作寫內(nèi)存即是寫磁盤育拨,在保證數(shù)據(jù)一致性的前提下谨履,提供了較高的性能。除此之外至朗,MongoDB還具備了豐富的查詢支持屉符、較多類型的索引支持以及Auto-Sharding的功能。在所有的NoSQL產(chǎn)品中,MongoDB對查詢的支持是最類似于傳統(tǒng)的RDBMS矗钟,這也使得應(yīng)用方可以較快的從RDBMS轉(zhuǎn)換到MonogoDB唆香。
在58同城,我們的業(yè)務(wù)特點是具有較高的訪問量吨艇,并可以按照業(yè)務(wù)進行垂直的拆分躬它,在每個業(yè)務(wù)線內(nèi)部通過MongoDB提供兩種擴展機制,當(dāng)業(yè)務(wù)存儲量和訪問量變大东涡,我們可以較易擴展冯吓。同時我們的業(yè)務(wù)類型對事務(wù)性要求低,綜合業(yè)務(wù)這幾點特性疮跑,在58同城使用MongoDB是較合適的组贺。
如何使用MongoDB?
MongoDB作為一款NoSQL數(shù)據(jù)庫產(chǎn)品祖娘,F(xiàn)ree Schema是它的特性之一失尖,在設(shè)計我們的數(shù)據(jù)存儲時耸峭,不需要我們固定Schema砾肺,提供給業(yè)務(wù)應(yīng)用方較高的自由度。那么問題來了共苛,F(xiàn)ree Schema真的Free嗎琼富?第一:Free Schema意味著重復(fù)Schema仪吧。在MongoDB數(shù)據(jù)存儲的時候,不但要存儲數(shù)據(jù)本身鞠眉,Schema(字段key)本身也要重復(fù)的存儲(例如:{“name”:”zhuanzhuan”, “infoid”:1,“infocontent”:”這個是轉(zhuǎn)轉(zhuǎn)商品”})薯鼠,必然會造成存儲空間的增大。第二:Free Schema意味著All Schema械蹋,任何一個需要調(diào)用MongoDB數(shù)據(jù)存儲的地方都需要記錄數(shù)據(jù)存儲的Schema人断,這樣才能較好的解析和處理,必然會造成業(yè)務(wù)應(yīng)用方的復(fù)雜度朝蜘。那么我們?nèi)绾螒?yīng)對呢恶迈?在字段名Key選取方面,我們盡可能減少字段名Key的長度谱醇,比如:name字段名使用n來代替暇仲,infoid字段名使用i來代替,infocontent字段名使用c來代替(例如:{“n”:”zhuanzhuan”, “i”:1, “c”:”這個是轉(zhuǎn)轉(zhuǎn)商品”})副渴。使用較短的字段名會帶來較差的可讀性奈附,我們通過在使用做字段名映射的方式( #defineZZ_NAME? ("n")),解決了這個問題煮剧;同時在數(shù)據(jù)存儲方面我們啟用了數(shù)據(jù)存儲的壓縮斥滤,盡可能減少數(shù)據(jù)存儲的量将鸵。
MongoDB提供了自動分片(Auto-Sharding)的功能,經(jīng)過我們的實際測試和線上驗證佑颇,并沒有使用這個功能顶掉。我們啟用了MongoDB的庫級Sharding;在CollectionSharding方面挑胸,我們使用手動Sharding的方式痒筒,水平切分?jǐn)?shù)據(jù)量較大的文檔。
MongoDB的存儲文檔必須要有一個“_id”字段茬贵,可以認為是“主鍵”簿透。這個字段值可以是任何類型,默認一個ObjectId對象解藻,這個對象使用了12個字節(jié)的存儲空間老充,每個字節(jié)存儲兩位16進制數(shù)字,是一個24位的字符串螟左。這個存儲空間消耗較大蚂维,我們實際使用情況是在應(yīng)用程序端,使用其他的類型(比如int)替換掉到路狮,一方面可以減少存儲空間,另外一方面可以較少MongoDB服務(wù)端生成“_id”字段的開銷蔚约。在每一個集合中奄妨,每個文檔都有唯一的“_id”標(biāo)示,來確保集中每個文檔的唯一性苹祟。而在不同集合中砸抛,不同集合中的文檔“_id”是可以相同的。比如有2個集合Collection_A和Collection_B树枫,Collection_A中有一個文檔的“_id”為1024直焙,在Collection_B中的一個文檔的“_id”也可以為1024。
MongoDB集群部署
MongoDB集群部署我們采用了Sharding+Replica-Set的部署方式砂轻。整個集群有Shard Server節(jié)點(存儲節(jié)點奔誓,采用了Replica-Set的復(fù)制方式)、Config Server節(jié)點(配置節(jié)點)搔涝、Router Server(路由節(jié)點厨喂、Arbiter Server(投票節(jié)點)組成。每一類節(jié)點都有多個冗余構(gòu)成庄呈。滿足58業(yè)務(wù)場景的一個典型MongoDB集群部署架構(gòu)如下所示[圖2]:
圖2 58同城典型業(yè)務(wù)MongoDB集群部署架構(gòu)
在部署架構(gòu)中蜕煌,當(dāng)數(shù)據(jù)存儲量變大后,我們較易增加Shard Server分片诬留。Replica-Set的復(fù)制方式斜纪,分片內(nèi)部可以自由增減數(shù)據(jù)存儲節(jié)點贫母。在節(jié)點故障發(fā)生時候,可以自動切換盒刚。同時我們采用了讀寫分離的方式腺劣,為整個集群提供更好的數(shù)據(jù)讀寫服務(wù)。
圖3 Auto-Sharding MAY is not that Reliable
針對業(yè)務(wù)場景我們在MongoDB中如何設(shè)計庫和表
MongoDB本身提供了Auto-Sharding的功能伪冰,這個智能的功能作為MongoDB的最具賣點的特性之一誓酒,真的非常靠譜嗎[圖3]贮聂?也許理想是豐滿的靠柑,現(xiàn)實是骨干滴。首先是在Sharding Key選擇上吓懈,如果選擇了單一的Sharding Key歼冰,會造成分片不均衡,一些分片數(shù)據(jù)比較多耻警,一些分片數(shù)據(jù)較少隔嫡,無法充分利用每個分片集群的能力。為了彌補單一Sharding Key的缺點甘穿,引入復(fù)合Sharing Key腮恩,然而復(fù)合Sharding Key會造成性能的消耗;第二count值計算不準(zhǔn)確的問題温兼,數(shù)據(jù)Chunk在分片之間遷移時秸滴,特定數(shù)據(jù)可能會被計算2次,造成count值計算偏大的問題募判;第三Balancer的穩(wěn)定性&智能性問題荡含,Sharing的遷移發(fā)生時間不確定,一旦發(fā)生數(shù)據(jù)遷移會造成整個系統(tǒng)的吞吐量急劇下降届垫。為了應(yīng)對Sharding遷移的不確定性释液,我們可以強制指定Sharding遷移的時間點,具體遷移時間點依據(jù)業(yè)務(wù)訪問的低峰期装处。比如IM系統(tǒng)误债,我們的流量低峰期是在凌晨1點到6點,那么我們可以在這段時間內(nèi)開啟Sharding遷移功能妄迁,允許數(shù)據(jù)的遷移找前,其他的時間不進行數(shù)據(jù)的遷移,從而做到對Sharding遷移的完全掌控判族,避免掉未知時間Sharding遷移帶來的一些風(fēng)險躺盛。
如何設(shè)計庫(DataBase)?
我們的MongoDB集群線上環(huán)境全部禁用了Auto-Sharding功能形帮。如上節(jié)所示槽惫,僅僅提供了指定時間段的數(shù)據(jù)遷移功能周叮。線上的數(shù)據(jù)我們開啟了庫級的分片,通過db.runCommand({“enablesharding”: “im”});命令指定界斜。并且我們通過db.runCommand({movePrimary:“im”, to: “sharding1”});命令指定特定庫到某一固定分片上仿耽。通過這樣的方式,我們保證了數(shù)據(jù)的無遷移性各薇,避免了Auto-Sharding帶來的一系列問題项贺,數(shù)據(jù)完全可控,從實際使用情況來看峭判,效果也較好开缎。既然我們關(guān)閉了Auto-Sharding的功能,就要求對業(yè)務(wù)的數(shù)據(jù)增加情況提前做好預(yù)估林螃,詳細了解業(yè)務(wù)半年甚至一年后的數(shù)據(jù)增長情況奕删,在設(shè)計MongoDB庫時需要做好規(guī)劃:確定數(shù)據(jù)規(guī)模、確定數(shù)據(jù)庫分片數(shù)量等疗认,避免數(shù)據(jù)庫頻繁的重構(gòu)和遷移情況發(fā)生完残。那么問題來了,針對MongoDB横漏,我們?nèi)绾巫龊萌萘恳?guī)劃谨设?MongoDB集群高性能本質(zhì)是MMAP機制,對機器內(nèi)存的依賴較重缎浇,因此我們要求業(yè)務(wù)熱點數(shù)據(jù)和索引的總量要能全部放入內(nèi)存中扎拣,即:Memory > Index + Hot Data。一旦數(shù)據(jù)頻繁地Swap华畏,必然會造成MongoDB集群性能的下降。當(dāng)內(nèi)存成為瓶頸時尊蚁,我們可以通過Scale Up或者Scale Out的方式進行擴展亡笑。第二:我們知道MongoDB的數(shù)據(jù)庫是按文件來存儲的:例如:db1下的所有collection都放在一組文件內(nèi)db1.0,db1.1,db1.2,db1.3……db1.n。數(shù)據(jù)的回收也是以庫為單位進行的横朋,數(shù)據(jù)的刪除將會造成數(shù)據(jù)的空洞或者碎片仑乌,碎片太多,會造成數(shù)據(jù)庫空間占用較大琴锭,加載到內(nèi)存時也會存在碎片的問題晰甚,內(nèi)存使用率不高,會造成數(shù)據(jù)頻繁地在內(nèi)存和磁盤之間Swap决帖,影響MongoDB集群性能厕九。因此將頻繁更新刪除的表放在一個獨立的數(shù)據(jù)庫下,將會減少碎片地回,從而提高性能扁远。第三:單庫單表絕對不是最好的選擇俊鱼。原因有三:表越多,映射文件越多畅买,從MongoDB的內(nèi)存管理方式來看并闲,浪費越多;同理谷羞,表越多帝火,回寫和讀取的時候,無法合并IO資源湃缎,大量的隨機IO對傳統(tǒng)硬盤是致命的犀填;單表數(shù)據(jù)量大,索引占用高雁歌,更新和讀取速度慢宏浩。第四:Local庫容量設(shè)置。我們知道Local庫主要存放oplog靠瞎,oplog用于數(shù)據(jù)的同步和復(fù)制比庄,oplog同樣要消耗內(nèi)存的,因此選擇一個合適的oplog值很重要乏盐,如果是高插入高更新佳窑,并帶有延時從庫的副本集需要一個較大的oplog值(比如50G);如果沒有延時從庫父能,并且數(shù)據(jù)更新速度不頻繁神凑,則可以適當(dāng)調(diào)小oplog值(比如5G)『瘟撸總之溉委,oplog值大小的設(shè)置取決于具體業(yè)務(wù)應(yīng)用場景,一切脫離業(yè)務(wù)使用場景來設(shè)置oplog的值大小都是耍流氓爱榕。
如何設(shè)計表(Collection)瓣喊?
MongoDB在數(shù)據(jù)邏輯結(jié)構(gòu)上和RDBMS比較類似,如圖4所示:MongoDB三要素:數(shù)據(jù)庫(DataBase)黔酥、集合(Collection)藻三、文檔(Document)分別對應(yīng)RDBMS(比如MySQL)三要素:數(shù)據(jù)庫(DataBase)、表(Table)跪者、行(Row)棵帽。
圖4 MongoDB和RDBMS數(shù)據(jù)邏輯結(jié)構(gòu)對比
MongoDB作為一支文檔型的數(shù)據(jù)庫允許文檔的嵌套結(jié)構(gòu),和RDBMS的三范式結(jié)構(gòu)不同渣玲,我們以“人”描述為例逗概,說明兩者之間設(shè)計上的區(qū)別⊥埽“人”有以下的屬性:姓名仗谆、性別指巡、年齡和住址;住址是一個復(fù)合結(jié)構(gòu)隶垮,包括:國家藻雪、城市、街道等狸吞。針對“人”的結(jié)構(gòu)勉耀,傳統(tǒng)的RDBMS的設(shè)計我們需要2張表:一張為People表[圖5],另外一張為Address表[圖6]蹋偏。這兩張表通過住址ID關(guān)聯(lián)起來(即Addess ID是People表的外鍵)便斥。在MongoDB表設(shè)計中,由于MongoDB支持文檔嵌套結(jié)構(gòu)威始,我可以把住址復(fù)合結(jié)構(gòu)嵌套起來枢纠,從而實現(xiàn)一個Collection結(jié)構(gòu)[圖7],可讀性會更強黎棠。
圖5 RDBMSPeople表設(shè)計
圖6 RDBMS Address表設(shè)計
圖7 MongoDB表設(shè)計
MongoDB作為一支NoSQL數(shù)據(jù)庫產(chǎn)品晋渺,除了可以支持嵌套結(jié)構(gòu)外,它又是最像RDBMS的產(chǎn)品脓斩,因此也可以支持“關(guān)系”的存儲木西。接下來會詳細講述下對應(yīng)RDBMS中的一對一、一對多随静、多對多關(guān)系在MongoDB中我們設(shè)計和實現(xiàn)八千。IM用戶信息表,包含用戶uid燎猛、用戶登錄名恋捆、用戶昵稱、用戶簽名等重绷,是一個典型的一對一關(guān)系沸停,在MongoDB可以采用類RDBMS的設(shè)計,我們設(shè)計一張IM用戶信息表user:{_id:88, loginname:musicml, nickname:musicml,sign:love}论寨,其中_id為主鍵星立,_id實際為uid爽茴。IM用戶消息表葬凳,一個用戶可以收到來自他人的多條消息,一個典型的一對多關(guān)系室奏。我們?nèi)绾卧O(shè)計火焰?一種方案,采用RDBMS的“多行”式設(shè)計胧沫,msg表結(jié)構(gòu)為:{uid昌简,msgid占业,msg_content},具體的記錄為:123, 1, 你好纯赎;123谦疾,2,在嗎犬金。另外一種設(shè)計方案念恍,我們可以使用MongoDB的嵌套結(jié)構(gòu):{uid:123, msg:{[{msgid:1,msg_content:你好},{msgid:2, msg_content:在嗎}]}}晚顷。采用MongoDB嵌套結(jié)構(gòu)峰伙,會更加直觀,但也存在一定的問題:更新復(fù)雜该默、MongoDB單文檔16MB的限制問題瞳氓。采用RDBMS的“多行”設(shè)計,它遵循了范式栓袖,一方面查詢條件更靈活匣摘,另外通過“多行式”擴展性也較高。在這個一對多的場景下叽赊,由于MongoDB單條文檔大小的限制恋沃,我們并沒采用MongoDB的嵌套結(jié)構(gòu),而是采用了更加靈活的類RDBMS的設(shè)計必指。在User和Team業(yè)務(wù)場景下囊咏,一個Team中有多個User,一個User也可能屬于多個Team塔橡,這種是典型的多對多關(guān)系梅割。在MongoDB中我們?nèi)绾卧O(shè)計?一種方案我們可以采用類RDBMS的設(shè)計葛家。一共三張表:Team表{teamid,teamname, ……}户辞,User表{userid,username,……},Relation表{refid, userid, teamid}癞谒。其中Team表存儲Team本身的元信息底燎,User表存儲User本身的元信息,Relation表存儲Team和User的所屬關(guān)系弹砚。在MongoDB中我們可以采用嵌套的設(shè)計方案:一種2張表:Team表{teamid,teamname,teammates:{[userid, userid, ……]}双仍,存儲了Team所有的User成員和User表{useid,usename,teams:{[teamid, teamid,……]}},存儲了User所有參加的Team桌吃。在MongoDB Collection上我們并沒有開啟Auto-Shariding的功能朱沃,那么當(dāng)單Collection數(shù)據(jù)量變大后,我們?nèi)绾蜸harding?對Collection Sharding 我們采用手動水平Sharding的方式逗物,單表我們保持在千萬級別文檔數(shù)量搬卒。當(dāng)Collection數(shù)據(jù)變大,我們進行水平拆分翎卓。比如IM用戶信息表:{uid, loginname, sign, ……}契邀,可用采用uid取模的方式水平擴展,比如:uid%64失暴,根據(jù)uid查詢可以直接定位特定的Collection蹂安,不用跨表查詢。通過手動Sharding的方式锐帜,一方面根據(jù)業(yè)務(wù)的特點田盈,我們可以很好滿足業(yè)務(wù)發(fā)展的情況,另外一方面我們可以做到可控缴阎、數(shù)據(jù)的可靠允瞧,從而避免了Auto-Sharding帶來的不穩(wěn)定因素。對于Collection上只有一個查詢維度(uid)蛮拔,通過水平切分可以很好滿足述暂。但是對于Collection上有2個查詢維度,我們?nèi)绾翁幚斫牛勘热缟唐繁恚簕uid, infoid, info,……}畦韭,存儲了商品發(fā)布者,商品ID肛跌,商品信息等艺配。我們需要即按照infoid查詢,又能支持按照uid查詢衍慎。為了支持這樣的查詢需求转唉,就要求infoid的設(shè)計上要特殊處理:infoid包含uid的信息(infoid最后8個bit是uid的最后8個bit),那么繼續(xù)采用infoid取模的方式稳捆,比如:infoid%64赠法,這樣我們既可以按照infoid查詢,又可以按照uid查詢乔夯,都不需要跨Collection查詢砖织。
數(shù)據(jù)量、并發(fā)量增大末荐,遇到問題及其解決方案
大量刪除數(shù)據(jù)問題及其解決方案
我們在IM離線消息中使用了MongoDB侧纯,IM離線消息是為了當(dāng)接收方不在線時,需要把發(fā)給接收者的消息存儲下來鞠评,當(dāng)接收者登錄IM后茂蚓,讀取存儲的離線消息后壕鹉,這些離線消息不再需要剃幌。已讀取離線消息的刪除聋涨,設(shè)計之初我們考慮物理刪除帶來的性能損耗,選擇了邏輯標(biāo)識刪除负乡。IM離線消息Collection包含如下字段:msgid, fromuid, touid, msgcontent, timestamp, flag牍白。其中touid為索引,flag表示離線消息是否已讀取抖棘,0未讀茂腥,1讀取。當(dāng)IM離線消息已讀條數(shù)積累到一定數(shù)量后切省,我們需要進行物理刪除最岗,以節(jié)省存儲空間,減少Collection文檔條數(shù)朝捆,提升集群性能般渡。既然我們通過flag==1做了已讀取消息的標(biāo)示,第一時間想到了通過flag標(biāo)示位來刪除:db.collection.remove({“flag” :1}};一條簡單的命令就可以搞定芙盘。表面上看很容易就搞定了驯用?!實際情況是IM離線消息表5kw條記錄儒老,近200GB的數(shù)據(jù)大小蝴乔。悲劇發(fā)生了:晚上10點后部署刪除直到早上7點還沒刪除完畢;MongoDB集群和業(yè)務(wù)監(jiān)控斷續(xù)有報警驮樊;從庫延遲大薇正;QPS/TPS很低;業(yè)務(wù)無法響應(yīng)囚衔。事后分析原因:雖然刪除命令db.collection.remove({“flag” : 1}};很簡單铝穷,但是flag字段并不是索引字段,刪除操作等價于全部掃描后進行佳魔,刪除速度很慢曙聂,需要刪除的消息基本都是冷數(shù)據(jù),大量的冷數(shù)據(jù)進入內(nèi)存中鞠鲜,由于內(nèi)存容量的限制宁脊,會把內(nèi)存中的熱數(shù)據(jù)swap到磁盤上,造成內(nèi)存中全是冷數(shù)據(jù)贤姆,服務(wù)能力急劇下降榆苞。遇到問題不可怕,我們?nèi)绾谓鉀Q呢霞捡?首先我們要保證線上提供穩(wěn)定的服務(wù)坐漏,采取緊急方案,找到還在執(zhí)行的opid,先把此命令殺掉(kill opid)赊琳,恢復(fù)服務(wù)街夭。長期方案,我們首先優(yōu)化了離線刪除程序[圖8]躏筏,把已讀IM離線消息的刪除操作板丽,每晚定時從庫導(dǎo)出要刪除的數(shù)據(jù),通過腳本按照objectid主鍵(_id)的方式進行刪除趁尼,并且刪除速度通過程序控制埃碱,從避免對線上服務(wù)影響。其次酥泞,我們通過用戶的離線消息的讀取行為來分析砚殿,用戶讀取離線消息時間分布相對比較均衡,不會出現(xiàn)比較密度讀取的情形芝囤,也就不會對MongoDB的更新帶來太大的影響瓮具,基于此我們把用戶IM離線消息的刪除由邏輯刪除優(yōu)化成物理刪除,從而從根本上解決了歷史數(shù)據(jù)的刪除問題凡人。
圖8離線刪除優(yōu)化腳本
大量數(shù)據(jù)空洞問題及其解決方案
MongoDB集群大量刪除數(shù)據(jù)后(比如上節(jié)中的IM用戶離線消息刪除)會存在大量的空洞名党,這些空洞一方面會造成MongoDB數(shù)據(jù)存儲空間較大,另外一方面這些空洞數(shù)據(jù)也會隨之加載到內(nèi)存中挠轴,導(dǎo)致內(nèi)存的有效利用率較低传睹,在機器內(nèi)存容量有限的前提下,會造成熱點數(shù)據(jù)頻繁的Swap岸晦,頻繁Swap數(shù)據(jù)欧啤,最終使得MongoDB集群服務(wù)能力下降,無法提供較高的性能启上。通過上文的描述邢隧,大家已經(jīng)了解MongoDB數(shù)據(jù)空間的分配是以DB為單位,而不是以Collection為單位的冈在,存在大量空洞造成MongoDB性能低下的原因倒慧,問題的關(guān)鍵是大量碎片無法利用,因此通過碎片整理包券、空洞合并收縮等方案纫谅,我們可以提高MongoDB集群的服務(wù)能力。那么我們?nèi)绾温涞啬亟蹋糠桨敢唬何覀兛梢允褂肕ongoDB提供的在線數(shù)據(jù)收縮的功能付秕,通過Compact命令(db.yourCollection.runCommand(“compact”);)進行Collection級別的數(shù)據(jù)收縮,去除Collectoin所在文件碎片侍郭。此命令是以O(shè)nline的方式提供收縮询吴,收縮的同時會影響到線上的服務(wù)掠河,其次從我們實際收縮的效果來看,數(shù)據(jù)空洞收縮的效果不夠顯著猛计。因此我們在實際數(shù)據(jù)碎片收縮時沒有采用這種方案唠摹,也不推薦大家使用這種空洞數(shù)據(jù)的收縮方案。既然這種數(shù)據(jù)方案不夠好有滑,我們可以采用Offline收縮的方案二:此方案收縮的原理是:把已有的空洞數(shù)據(jù),remove掉嵌削,重新生成一份無空洞數(shù)據(jù)毛好。那么具體如何落地?先預(yù)熱從庫苛秕;把預(yù)熱的從庫提升為主庫肌访;把之前主庫的數(shù)據(jù)全部刪除;重新同步艇劫;同步完成后吼驶,預(yù)熱此庫;把此庫提升為主庫店煞。具體的操作步驟如下:檢查服務(wù)器各節(jié)點是否正常運行 (ps -ef |grep mongod)蟹演;登入要處理的主節(jié)點 /mongodb/bin/mongo--port 88888;做降權(quán)處理rs.stepDown()顷蟀,并通過命令 rs.status()來查看是否降權(quán)酒请;切換成功之后,停掉該節(jié)點鸣个;檢查是否已經(jīng)降權(quán)羞反,可以通過web頁面查看status,我們建議最好登錄進去保證有數(shù)據(jù)進入囤萤,或者是mongostat 查看昼窗; kill 掉對應(yīng)mongo的進程: kill 進程號;刪除數(shù)據(jù)涛舍,進入對應(yīng)的分片刪除數(shù)據(jù)文件澄惊,比如: rm -fr /mongodb/shard11/*;重新啟動該節(jié)點富雅,執(zhí)行重啟命令缤削,比如:如:/mongodb/bin/mongod --config /mongodb/shard11.conf;通過日志查看進程吹榴;數(shù)據(jù)同步完成后亭敢,在修改后的主節(jié)點上執(zhí)行命令 rs.stepDown() ,做降權(quán)處理图筹。通過這種Offline的收縮方式帅刀,我們可以做到收縮率是100%让腹,數(shù)據(jù)完全無碎片。當(dāng)然做離線的數(shù)據(jù)收縮會帶來運維成本的增加扣溺,并且在Replic-Set集群只有2個副本的情況下骇窍,還會存在一段時間內(nèi)的單點風(fēng)險。通過Offline的數(shù)據(jù)收縮后锥余,收縮前后效果非常明顯腹纳,如[圖9,圖10]所示:收縮前85G存儲文件,收縮后34G存儲文件驱犹,節(jié)省了51G存儲空間嘲恍,大大提升了性能。
圖9收縮MongoDB數(shù)據(jù)庫前存儲數(shù)據(jù)大小
圖10收縮MongoDB數(shù)據(jù)庫后存儲數(shù)據(jù)大小
MongoDB集群監(jiān)控
MongoDB集群有多種方式可以監(jiān)控:mongosniff雄驹、mongostat佃牛、mongotop、db.xxoostatus医舆、web控制臺監(jiān)控俘侠、MMS、第三方監(jiān)控蔬将。我們使用了多種監(jiān)控相結(jié)合的方式爷速,從而做到對MongoDB整個集群完全Hold住。第一是mongostat[圖11]霞怀,mongostat是對MongoDB集群負載情況的一個快照遍希,可以查看每秒更新量、加鎖時間占操作時間百分比里烦、缺頁中斷數(shù)量凿蒜、索引miss的數(shù)量、客戶端查詢排隊長度(讀|寫)胁黑、當(dāng)前連接數(shù)废封、活躍客戶端數(shù)量(讀|寫)等。
圖11MongoDB mongostat監(jiān)控
mongstat可以查看的字段較多丧蘸,我們重點關(guān)注Locked漂洋、faults、miss力喷、qr|qw等刽漂,這些值越小越好,最好都為0弟孟;locked最好不要超過10%贝咙;造成faults、miss原因主要是內(nèi)存不夠或者內(nèi)冷數(shù)據(jù)頻繁Swap拂募,索引設(shè)置不合理庭猩;qr|qw堆積較多窟她,反應(yīng)了數(shù)據(jù)庫處理慢,這時候我們需要針對性的優(yōu)化蔼水。
第二是web控制臺震糖,和MongoDB服務(wù)一同開啟,它的監(jiān)聽端口是MongoDB服務(wù)監(jiān)聽端口加上1000趴腋,如果MongoDB的監(jiān)聽端口33333吊说,則Web控制臺端口為34333。我們可以通過http://ip:port(http://8.8.8.8:34333)訪問監(jiān)控了什么[圖12]:當(dāng)前MongoDB所有的連接數(shù)优炬、各個數(shù)據(jù)庫和Collection的訪問統(tǒng)計包括:Reads, Writes, Queries等颁井、寫鎖的狀態(tài)、最新的幾百行日志文件穿剖。
圖12 ?MongoDB Web控制臺監(jiān)控
第三是MMS(MongoDBMonitoring Service)蚤蔓,它是2011年官方發(fā)布的云監(jiān)控服務(wù)卦溢,提供可視化圖形監(jiān)控糊余。工作原理如下:在MMS服務(wù)器上配置需要監(jiān)控的MongoDB信息(ip/port/user/passwd等);在一臺能夠訪問你MongoDB服務(wù)的內(nèi)網(wǎng)機器上運行其提供的Agent腳本单寂;Agent腳本從MMS服務(wù)器獲取到你配置的MongoDB信息贬芥;Agent腳本連接到相應(yīng)的MongoDB獲取必要的監(jiān)控數(shù)據(jù);Agent腳本將監(jiān)控數(shù)據(jù)上傳到MMS的服務(wù)器宣决;登錄MMS網(wǎng)站查看整理過后的監(jiān)控數(shù)據(jù)圖表蘸劈。具體的安裝部署,可以參考:http://mms.10gen.com尊沸。
圖13 MongoDB MMS監(jiān)控
第四是第三方監(jiān)控威沫,MongoDB開源愛好者和團隊支持者較多,可以在常用監(jiān)控框架上擴展洼专,比如:zabbix棒掠,可以監(jiān)控CPU負荷、內(nèi)存使用屁商、磁盤使用烟很、網(wǎng)絡(luò)狀況、端口監(jiān)視蜡镶、日志監(jiān)視等雾袱;nagios,可以監(jiān)控監(jiān)控網(wǎng)絡(luò)服務(wù)(HTTP等)官还、監(jiān)控主機資源(處理器負荷芹橡、磁盤利用率等)、插件擴展望伦、報警發(fā)送給聯(lián)系人(EMail僻族、短信粘驰、用戶定義方式)投放、手機查看方式漠吻;cacti,可以基于PHP,MySQL,SNMP及RRDTool開發(fā)的網(wǎng)絡(luò)流量監(jiān)測圖形分析工具贷祈。
最后我要感謝公司和團隊度秘,在MongoDB集群的大規(guī)模實戰(zhàn)中積累了寶貴的經(jīng)驗顶伞,才能讓我有機會撰寫了此文,由于MongoDB社區(qū)不斷發(fā)展剑梳,特別是MongoDB 3.0唆貌,對性能、數(shù)據(jù)壓縮垢乙、運維成本锨咙、鎖級別、Sharding以及支持可插拔的存儲引擎等的改進追逮,MongoDB越來越強大酪刀。文中可能會存在一些不妥的地方,歡迎大家交流指正钮孵。