????????58 同城作為中國(guó)最大的生活服務(wù)平臺(tái)仓坞,涵蓋了房產(chǎn)、招聘腰吟、二手无埃、二手車、黃頁等核心業(yè)務(wù)毛雇。58 同城發(fā)展之初录语,大規(guī)模使用關(guān)系型數(shù)據(jù)庫(SQL Server、MySQL 等)禾乘,隨著業(yè)務(wù)擴(kuò)展速度增加,數(shù)據(jù)量和并發(fā)量演變的越來越有挑戰(zhàn)虽缕,此階段 58 的數(shù)據(jù)存儲(chǔ)架構(gòu)也需要相應(yīng)的調(diào)整以更好的滿足業(yè)務(wù)快速發(fā)展的需求始藕。
????????MongoDB 經(jīng)過幾個(gè)版本的迭代蒲稳,到 2.0.0 以后,變的越來越穩(wěn)定伍派,它具備的高性能江耀、高擴(kuò)展性、Auto-Sharding诉植、Free-Schema祥国、類 SQL 的豐富查詢和索引等特性,非常誘惑晾腔,同時(shí) 58 同城在一些典型業(yè)務(wù)場(chǎng)景下使用 MongoDB 也較合適舌稀,2011 年,我們開始使用 MongoDB灼擂,逐步擴(kuò)大了使用的業(yè)務(wù)線壁查,覆蓋了 58 幫幫、58 交友剔应、58 招聘睡腿、信息質(zhì)量等等多條業(yè)務(wù)線。
????????隨著 58 每天處理的海量數(shù)據(jù)越來越大峻贮,并呈現(xiàn)不斷增多的趨勢(shì)席怪,這為 MongoDB 在存儲(chǔ)與處理方面帶來了諸多的挑戰(zhàn)。面對(duì)百億量級(jí)的數(shù)據(jù)纤控,我們?cè)撊绾未鎯?chǔ)與處理挂捻,本文將詳細(xì)介紹 MongoDB 遇到的問題以及最終如何“完美”解決。
????????本文詳細(xì)講述 MongoDB 在 58 同城的應(yīng)用實(shí)踐:MongoDB 在 58 同城的使用情況嚼黔;為什么要使用 MongoDB细层;MongoDB 在 58 同城的架構(gòu)設(shè)計(jì)與實(shí)踐;針對(duì)業(yè)務(wù)場(chǎng)景我們?cè)?MongoDB 中如何設(shè)計(jì)庫和表唬涧;數(shù)據(jù)量增大和業(yè)務(wù)并發(fā)疫赎,我們遇到典型問題及其解決方案;MongoDB 如何監(jiān)控碎节。
1捧搞、MongoDB 在 58 同城的使用情況
????????MongoDB 在 58 同城的眾多業(yè)務(wù)線都有大規(guī)模使用:58 轉(zhuǎn)轉(zhuǎn)、58 幫幫狮荔、58 交友胎撇、58 招聘、58 信息質(zhì)量殖氏、58 測(cè)試應(yīng)用等晚树。
2、為什么要使用 MongoDB雅采?
????????MongoDB 這個(gè)來源英文單詞“humongous”爵憎,homongous 這個(gè)單詞的意思是“巨大的”慨亲、“奇大無比的”,從 MongoDB 單詞本身可以看出它的目標(biāo)是提供海量數(shù)據(jù)的存儲(chǔ)以及管理能力宝鼓。MongoDB 是一款面向文檔的 NoSQL 數(shù)據(jù)庫刑棵,MongoDB 具備較好的擴(kuò)展性以及高可用性,在數(shù)據(jù)復(fù)制方面愚铡,支持 Master-Slaver(主從)和 Replica-Set(副本集)等兩種方式蛉签。通過這兩種方式可以使得我們非常方便的擴(kuò)展數(shù)據(jù)。
????????MongoDB 較高的性能也是它強(qiáng)有力的賣點(diǎn)之一沥寥,存儲(chǔ)引擎使用的內(nèi)存映射文件(MMAP 的方式)碍舍,將內(nèi)存管理工作交給操作系統(tǒng)去處理。MMAP 的機(jī)制营曼,數(shù)據(jù)的操作寫內(nèi)存即是寫磁盤乒验,在保證數(shù)據(jù)一致性的前提下,提供了較高的性能蒂阱。
????????除此之外锻全,MongoDB 還具備了豐富的查詢支持、較多類型的索引支持以及 Auto-Sharding 的功能录煤。在所有的 NoSQL 產(chǎn)品中鳄厌,MongoDB 對(duì)查詢的支持是最類似于傳統(tǒng)的 RDBMS,這也使得應(yīng)用方可以較快的從 RDBMS 轉(zhuǎn)換到 MonogoDB妈踊。
????????在 58 同城了嚎,我們的業(yè)務(wù)特點(diǎn)是具有較高的訪問量,并可以按照業(yè)務(wù)進(jìn)行垂直的拆分廊营,在每個(gè)業(yè)務(wù)線內(nèi)部通過 MongoDB 提供兩種擴(kuò)展機(jī)制歪泳,當(dāng)業(yè)務(wù)存儲(chǔ)量和訪問量變大,我們可以較易擴(kuò)展露筒。同時(shí)我們的業(yè)務(wù)類型對(duì)事務(wù)性要求低呐伞,綜合業(yè)務(wù)這幾點(diǎn)特性,在 58 同城使用 MongoDB 是較合適的慎式。
3伶氢、如何使用 MongoDB?
????????MongoDB 作為一款 NoSQL 數(shù)據(jù)庫產(chǎn)品瘪吏,F(xiàn)ree Schema 是它的特性之一癣防,在設(shè)計(jì)我們的數(shù)據(jù)存儲(chǔ)時(shí),不需要我們固定 Schema掌眠,提供給業(yè)務(wù)應(yīng)用方較高的自由度蕾盯。那么問題來了,F(xiàn)ree Schema 真的 Free 嗎蓝丙?
? ??????第一:Free Schema 意味著重復(fù) Schema级遭。在 MongoDB 數(shù)據(jù)存儲(chǔ)的時(shí)候香嗓,不但要存儲(chǔ)數(shù)據(jù)本身,Schema(字段 key)本身也要重復(fù)的存儲(chǔ)(例如:{“name”:”zhuanzhuan”, “infoid”:1,“infocontent”:”這個(gè)是轉(zhuǎn)轉(zhuǎn)商品”})装畅,必然會(huì)造成存儲(chǔ)空間的增大。
? ??????第二:Free Schema 意味著 All Schema沧烈,任何一個(gè)需要調(diào)用 MongoDB 數(shù)據(jù)存儲(chǔ)的地方都需要記錄數(shù)據(jù)存儲(chǔ)的 Schema掠兄,這樣才能較好的解析和處理,必然會(huì)造成業(yè)務(wù)應(yīng)用方的復(fù)雜度锌雀。
????????那么我們?nèi)绾螒?yīng)對(duì)呢蚂夕?在字段名 Key 選取方面,我們盡可能減少字段名 Key 的長(zhǎng)度腋逆,比如:name 字段名使用 n 來代替婿牍,infoid 字段名使用 i 來代替,infocontent 字段名使用 c 來代替(例如:{“n”:”zhuanzhuan”, “i”:1, “c”:”這個(gè)是轉(zhuǎn)轉(zhuǎn)商品”})惩歉。使用較短的字段名會(huì)帶來較差的可讀性等脂,我們通過在使用做字段名映射的方式( #defineZZ_NAME? ("n")),解決了這個(gè)問題撑蚌;同時(shí)在數(shù)據(jù)存儲(chǔ)方面我們啟用了數(shù)據(jù)存儲(chǔ)的壓縮上遥,盡可能減少數(shù)據(jù)存儲(chǔ)的量。
????????MongoDB 提供了自動(dòng)分片(Auto-Sharding)的功能争涌,經(jīng)過我們的實(shí)際測(cè)試和線上驗(yàn)證粉楚,并沒有使用這個(gè)功能。我們啟用了 MongoDB 的庫級(jí) Sharding亮垫;在 CollectionSharding 方面模软,我們使用手動(dòng) Sharding 的方式,水平切分?jǐn)?shù)據(jù)量較大的文檔饮潦。
????????MongoDB 的存儲(chǔ)文檔必須要有一個(gè)“_id”字段燃异,可以認(rèn)為是“主鍵”。這個(gè)字段值可以是任何類型害晦,默認(rèn)一個(gè) ObjectId 對(duì)象特铝,這個(gè)對(duì)象使用了 12 個(gè)字節(jié)的存儲(chǔ)空間,每個(gè)字節(jié)存儲(chǔ)兩位 16 進(jìn)制數(shù)字壹瘟,是一個(gè) 24 位的字符串鲫剿。這個(gè)存儲(chǔ)空間消耗較大,我們實(shí)際使用情況是在應(yīng)用程序端稻轨,使用其他的類型(比如 int)替換掉到灵莲,一方面可以減少存儲(chǔ)空間,另外一方面可以較少 MongoDB 服務(wù)端生成“_id”字段的開銷殴俱。
????????在每一個(gè)集合中政冻,每個(gè)文檔都有唯一的“_id”標(biāo)示枚抵,來確保集中每個(gè)文檔的唯一性略贮。而在不同集合中欺嗤,不同集合中的文檔“_id”是可以相同的。比如有 2 個(gè)集合 Collection_A 和 Collection_B惭蟋,Collection_A 中有一個(gè)文檔的“_id”為 1024苦锨,在 Collection_B 中的一個(gè)文檔的“_id”也可以為 1024逼泣。
4、MongoDB 集群部署
????????MongoDB 集群部署我們采用了 Sharding+Replica-Set 的部署方式舟舒。整個(gè)集群有 Shard Server 節(jié)點(diǎn)(存儲(chǔ)節(jié)點(diǎn)拉庶,采用了 Replica-Set 的復(fù)制方式)、Config Server 節(jié)點(diǎn)(配置節(jié)點(diǎn))秃励、Router Server(路由節(jié)點(diǎn)氏仗、Arbiter Server(投票節(jié)點(diǎn))組成。每一類節(jié)點(diǎn)都有多個(gè)冗余構(gòu)成夺鲜。滿足 58 業(yè)務(wù)場(chǎng)景的一個(gè)典型 MongoDB 集群部署架構(gòu)如下所示 [圖 1]:
? ? ? ? 在部署架構(gòu)中皆尔,當(dāng)數(shù)據(jù)存儲(chǔ)量變大后,我們較易增加 Shard Server 分片谣旁。Replica-Set 的復(fù)制方式床佳,分片內(nèi)部可以自由增減數(shù)據(jù)存儲(chǔ)節(jié)點(diǎn)。在節(jié)點(diǎn)故障發(fā)生時(shí)候榄审,可以自動(dòng)切換砌们。同時(shí)我們采用了讀寫分離的方式,為整個(gè)集群提供更好的數(shù)據(jù)讀寫服務(wù)搁进。
5浪感、針對(duì)業(yè)務(wù)場(chǎng)景我們?cè)?MongoDB 中如何設(shè)計(jì)庫和表
????????MongoDB 本身提供了Auto-Sharding的功能,這個(gè)智能的功能作為 MongoDB 的最具賣點(diǎn)的特性之一饼问,真的非秤笆蓿靠譜嗎(圖 3)?也許理想是豐滿的莱革,現(xiàn)實(shí)是骨干滴峻堰。
? ??????首先,是在 Sharding Key 選擇上盅视,如果選擇了單一的 Sharding Key捐名,會(huì)造成分片不均衡,一些分片數(shù)據(jù)比較多闹击,一些分片數(shù)據(jù)較少镶蹋,無法充分利用每個(gè)分片集群的能力。為了彌補(bǔ)單一 Sharding Key 的缺點(diǎn),引入復(fù)合 Sharing Key贺归,然而復(fù)合 Sharding Key 會(huì)造成性能的消耗淆两;
? ??????第二,count 值計(jì)算不準(zhǔn)確的問題拂酣,數(shù)據(jù) Chunk 在分片之間遷移時(shí)秋冰,特定數(shù)據(jù)可能會(huì)被計(jì)算 2 次,造成 count 值計(jì)算偏大的問題婶熬;
? ??????第三丹莲,Balancer 的穩(wěn)定性 & 智能性問題,Sharing 的遷移發(fā)生時(shí)間不確定尸诽,一旦發(fā)生數(shù)據(jù)遷移會(huì)造成整個(gè)系統(tǒng)的吞吐量急劇下降。為了應(yīng)對(duì) Sharding 遷移的不確定性盯另,我們可以強(qiáng)制指定 Sharding 遷移的時(shí)間點(diǎn)性含,具體遷移時(shí)間點(diǎn)依據(jù)業(yè)務(wù)訪問的低峰期。比如 IM 系統(tǒng)鸳惯,我們的流量低峰期是在凌晨 1 點(diǎn)到 6 點(diǎn)商蕴,那么我們可以在這段時(shí)間內(nèi)開啟 Sharding 遷移功能,允許數(shù)據(jù)的遷移芝发,其他的時(shí)間不進(jìn)行數(shù)據(jù)的遷移绪商,從而做到對(duì) Sharding 遷移的完全掌控,避免掉未知時(shí)間 Sharding 遷移帶來的一些風(fēng)險(xiǎn)辅鲸。
6格郁、如何設(shè)計(jì)庫(DataBase)?
????????我們的 MongoDB 集群線上環(huán)境全部禁用了 Auto-Sharding 功能独悴。如上節(jié)所示例书,僅僅提供了指定時(shí)間段的數(shù)據(jù)遷移功能。線上的數(shù)據(jù)我們開啟了庫級(jí)的分片刻炒,通過 db.runCommand({“enablesharding”: “im”}); 命令指定决采。并且我們通過 db.runCommand({movePrimary:“im”, to: “sharding1”}); 命令指定特定庫到某一固定分片上。通過這樣的方式坟奥,我們保證了數(shù)據(jù)的無遷移性树瞭,避免了 Auto-Sharding 帶來的一系列問題,數(shù)據(jù)完全可控爱谁,從實(shí)際使用情況來看晒喷,效果也較好。
????????既然我們關(guān)閉了 Auto-Sharding 的功能管行,就要求對(duì)業(yè)務(wù)的數(shù)據(jù)增加情況提前做好預(yù)估厨埋,詳細(xì)了解業(yè)務(wù)半年甚至一年后的數(shù)據(jù)增長(zhǎng)情況,在設(shè)計(jì) MongoDB 庫時(shí)需要做好規(guī)劃:確定數(shù)據(jù)規(guī)模、確定數(shù)據(jù)庫分片數(shù)量等荡陷,避免數(shù)據(jù)庫頻繁的重構(gòu)和遷移情況發(fā)生雨效。
????????那么問題來了,針對(duì) MongoDB废赞,我們如何做好容量規(guī)劃徽龟?
????????第一:MongoDB 集群高性能本質(zhì)是 MMAP 機(jī)制,對(duì)機(jī)器內(nèi)存的依賴較重唉地,因此我們要求業(yè)務(wù)熱點(diǎn)數(shù)據(jù)和索引的總量要能全部放入內(nèi)存中据悔,即:Memory > Index + Hot Data。一旦數(shù)據(jù)頻繁地 Swap耘沼,必然會(huì)造成 MongoDB 集群性能的下降极颓。當(dāng)內(nèi)存成為瓶頸時(shí),我們可以通過 Scale Up 或者 Scale Out 的方式進(jìn)行擴(kuò)展群嗤。
? ??????第二:我們知道 MongoDB 的數(shù)據(jù)庫是按文件來存儲(chǔ)的:例如:db1 下的所有 collection 都放在一組文件內(nèi) db1.0,db1.1,db1.2,db1.3……db1.n菠隆。數(shù)據(jù)的回收也是以庫為單位進(jìn)行的,數(shù)據(jù)的刪除將會(huì)造成數(shù)據(jù)的空洞或者碎片狂秘,碎片太多骇径,會(huì)造成數(shù)據(jù)庫空間占用較大,加載到內(nèi)存時(shí)也會(huì)存在碎片的問題者春,內(nèi)存使用率不高破衔,會(huì)造成數(shù)據(jù)頻繁地在內(nèi)存和磁盤之間 Swap,影響 MongoDB 集群性能钱烟。因此將頻繁更新刪除的表放在一個(gè)獨(dú)立的數(shù)據(jù)庫下晰筛,將會(huì)減少碎片,從而提高性能拴袭。
? ??????第三:?jiǎn)螏靻伪斫^對(duì)不是最好的選擇传惠。原因有三:表越多,映射文件越多稻扬,從 MongoDB 的內(nèi)存管理方式來看卦方,浪費(fèi)越多;同理泰佳,表越多盼砍,回寫和讀取的時(shí)候,無法合并 IO 資源逝她,大量的隨機(jī) IO 對(duì)傳統(tǒng)硬盤是致命的浇坐;單表數(shù)據(jù)量大,索引占用高黔宛,更新和讀取速度慢近刘。
? ??????第四:Local 庫容量設(shè)置。我們知道 Local 庫主要存放 oplog,oplog 用于數(shù)據(jù)的同步和復(fù)制觉渴,oplog 同樣要消耗內(nèi)存的介劫,因此選擇一個(gè)合適的 oplog 值很重要,如果是高插入高更新案淋,并帶有延時(shí)從庫的副本集需要一個(gè)較大的 oplog 值(比如 50G)座韵;如果沒有延時(shí)從庫,并且數(shù)據(jù)更新速度不頻繁踢京,則可以適當(dāng)調(diào)小 oplog 值(比如 5G)誉碴。總之瓣距,oplog 值大小的設(shè)置取決于具體業(yè)務(wù)應(yīng)用場(chǎng)景黔帕,一切脫離業(yè)務(wù)使用場(chǎng)景來設(shè)置 oplog 的值大小都是耍流氓。
7蹈丸、如何設(shè)計(jì)表(Collection)蹬屹?
????????MongoDB 在數(shù)據(jù)邏輯結(jié)構(gòu)上和 RDBMS 比較類似,如圖 2 所示:MongoDB 三要素:數(shù)據(jù)庫(DataBase)白华、集合(Collection)、文檔(Document)分別對(duì)應(yīng) RDBMS(比如 MySQL)三要素:數(shù)據(jù)庫(DataBase)贩耐、表(Table)弧腥、行(Row)。
? ? ? ? MongoDB 作為一支文檔型的數(shù)據(jù)庫允許文檔的嵌套結(jié)構(gòu)潮太,和 RDBMS 的三范式結(jié)構(gòu)不同管搪,我們以“人”描述為例,說明兩者之間設(shè)計(jì)上的區(qū)別铡买「常“人”有以下的屬性:姓名、性別奇钞、年齡和住址澡为;住址是一個(gè)復(fù)合結(jié)構(gòu),包括:國(guó)家景埃、城市媒至、街道等。針對(duì)“人”的結(jié)構(gòu)谷徙,傳統(tǒng)的 RDBMS 的設(shè)計(jì)我們需要 2 張表:一張為 People 表 [圖 3]拒啰,另外一張為 Address 表 [圖 4]。這兩張表通過住址 ID 關(guān)聯(lián)起來(即 Addess ID 是 People 表的外鍵)完慧。在 MongoDB 表設(shè)計(jì)中谋旦,由于 MongoDB 支持文檔嵌套結(jié)構(gòu),我可以把住址復(fù)合結(jié)構(gòu)嵌套起來,從而實(shí)現(xiàn)一個(gè) Collection 結(jié)構(gòu) [圖 5]册着,可讀性會(huì)更強(qiáng)拴孤。
? ? ? ? MongoDB 作為一支 NoSQL 數(shù)據(jù)庫產(chǎn)品,除了可以支持嵌套結(jié)構(gòu)外指蚜,它又是最像 RDBMS 的產(chǎn)品乞巧,因此也可以支持“關(guān)系”的存儲(chǔ)。接下來會(huì)詳細(xì)講述下對(duì)應(yīng) RDBMS 中的一對(duì)一摊鸡、一對(duì)多绽媒、多對(duì)多關(guān)系在 MongoDB 中我們?cè)O(shè)計(jì)和實(shí)現(xiàn)。
????????IM 用戶信息表免猾,包含用戶 uid是辕、用戶登錄名、用戶昵稱猎提、用戶簽名等获三,是一個(gè)典型的一對(duì)一關(guān)系,在 MongoDB 可以采用類 RDBMS 的設(shè)計(jì)锨苏,我們?cè)O(shè)計(jì)一張 IM 用戶信息表 user:{_id:88, loginname:musicml, nickname:musicml,sign:love}疙教,其中 _id 為主鍵,_id 實(shí)際為 uid伞租。IM 用戶消息表贞谓,一個(gè)用戶可以收到來自他人的多條消息,一個(gè)典型的一對(duì)多關(guān)系葵诈。
我們?nèi)绾卧O(shè)計(jì)裸弦?
????????一種方案,采用 RDBMS 的“多行”式設(shè)計(jì)作喘,msg 表結(jié)構(gòu)為:{uid理疙,msgid,msg_content}泞坦,具體的記錄為:123, 1, 你好窖贤;123,2贰锁,在嗎主之。
????????另外一種設(shè)計(jì)方案,我們可以使用 MongoDB 的嵌套結(jié)構(gòu):{uid:123, msg:{[{msgid:1,msg_content: 你好}李根,{msgid:2, msg_content: 在嗎}]}}槽奕。
????????采用 MongoDB 嵌套結(jié)構(gòu),會(huì)更加直觀房轿,但也存在一定的問題:更新復(fù)雜粤攒、MongoDB 單文檔 16MB 的限制問題所森。采用 RDBMS 的“多行”設(shè)計(jì),它遵循了范式夯接,一方面查詢條件更靈活焕济,另外通過“多行式”擴(kuò)展性也較高。
????????在這個(gè)一對(duì)多的場(chǎng)景下盔几,由于 MongoDB 單條文檔大小的限制晴弃,我們并沒采用 MongoDB 的嵌套結(jié)構(gòu),而是采用了更加靈活的類 RDBMS 的設(shè)計(jì)逊拍。
????????在 User 和 Team 業(yè)務(wù)場(chǎng)景下上鞠,一個(gè) Team 中有多個(gè) User,一個(gè) User 也可能屬于多個(gè) Team芯丧,這種是典型的多對(duì)多關(guān)系芍阎。
????????在 MongoDB 中我們?nèi)绾卧O(shè)計(jì)?一種方案我們可以采用類 RDBMS 的設(shè)計(jì)缨恒。一共三張表:Team 表{teamid,teamname, ……}谴咸,User 表{userid,username,……},Relation 表{refid, userid, teamid}骗露。其中 Team 表存儲(chǔ) Team 本身的元信息岭佳,User 表存儲(chǔ) User 本身的元信息,Relation 表存儲(chǔ) Team 和 User 的所屬關(guān)系萧锉。
????????在 MongoDB 中我們可以采用嵌套的設(shè)計(jì)方案:一種 2 張表:Team 表{teamid,teamname,teammates:{[userid, userid, ……]}珊随,存儲(chǔ)了 Team 所有的 User 成員和 User 表{useid,usename,teams:{[teamid, teamid,……]}},存儲(chǔ)了 User 所有參加的 Team驹暑。
????????在 MongoDB Collection 上我們并沒有開啟 Auto-Shariding 的功能,那么當(dāng)單 Collection 數(shù)據(jù)量變大后辨赐,我們?nèi)绾?Sharding优俘?對(duì) Collection Sharding 我們采用手動(dòng)水平 Sharding 的方式,單表我們保持在千萬級(jí)別文檔數(shù)量掀序。當(dāng) Collection 數(shù)據(jù)變大帆焕,我們進(jìn)行水平拆分。比如 IM 用戶信息表:{uid, loginname, sign, ……}不恭,可用采用 uid 取模的方式水平擴(kuò)展叶雹,比如:uid%64,根據(jù) uid 查詢可以直接定位特定的 Collection换吧,不用跨表查詢折晦。
????????通過手動(dòng) Sharding 的方式,一方面根據(jù)業(yè)務(wù)的特點(diǎn)沾瓦,我們可以很好滿足業(yè)務(wù)發(fā)展的情況满着,另外一方面我們可以做到可控谦炒、數(shù)據(jù)的可靠,從而避免了 Auto-Sharding 帶來的不穩(wěn)定因素风喇。對(duì)于 Collection 上只有一個(gè)查詢維度(uid)宁改,通過水平切分可以很好滿足。
????????但是對(duì)于 Collection 上有 2 個(gè)查詢維度魂莫,我們?nèi)绾翁幚砘苟祝勘热缟唐繁恚簕uid, infoid, info,……},存儲(chǔ)了商品發(fā)布者耙考,商品 ID谜喊,商品信息等。我們需要即按照 infoid 查詢琳骡,又能支持按照 uid 查詢锅论。為了支持這樣的查詢需求,就要求 infoid 的設(shè)計(jì)上要特殊處理:infoid 包含 uid 的信息(infoid 最后 8 個(gè) bit 是 uid 的最后 8 個(gè) bit)楣号,那么繼續(xù)采用 infoid 取模的方式最易,比如:infoid%64,這樣我們既可以按照 infoid 查詢炫狱,又可以按照 uid 查詢藻懒,都不需要跨 Collection 查詢。
8视译、數(shù)據(jù)量嬉荆、并發(fā)量增大,遇到問題及其解決方案
8.1????大量刪除數(shù)據(jù)問題及其解決方案
????????我們?cè)?IM 離線消息中使用了 MongoDB酷含,IM 離線消息是為了當(dāng)接收方不在線時(shí)鄙早,需要把發(fā)給接收者的消息存儲(chǔ)下來,當(dāng)接收者登錄 IM 后椅亚,讀取存儲(chǔ)的離線消息后限番,這些離線消息不再需要。已讀取離線消息的刪除呀舔,設(shè)計(jì)之初我們考慮物理刪除帶來的性能損耗弥虐,選擇了邏輯標(biāo)識(shí)刪除。IM 離線消息 Collection 包含如下字段:msgid, fromuid, touid, msgcontent, timestamp, flag媚赖。其中 touid 為索引霜瘪,flag 表示離線消息是否已讀取,0 未讀惧磺,1 讀取颖对。
????????當(dāng) IM 離線消息已讀條數(shù)積累到一定數(shù)量后,我們需要進(jìn)行物理刪除磨隘,以節(jié)省存儲(chǔ)空間惜互,減少 Collection 文檔條數(shù)布讹,提升集群性能。既然我們通過 flag==1 做了已讀取消息的標(biāo)示训堆,第一時(shí)間想到了通過 flag 標(biāo)示位來刪除:db.collection.remove({“flag” :1}}; 一條簡(jiǎn)單的命令就可以搞定描验。表面上看很容易就搞定了?坑鱼!實(shí)際情況是 IM 離線消息表 5kw 條記錄膘流,近 200GB 的數(shù)據(jù)大小。
? ??????悲劇發(fā)生了:晚上 10 點(diǎn)后部署刪除直到早上 7 點(diǎn)還沒刪除完畢鲁沥;MongoDB 集群和業(yè)務(wù)監(jiān)控?cái)嗬m(xù)有報(bào)警呼股;從庫延遲大;QPS/TPS 很低画恰;業(yè)務(wù)無法響應(yīng)彭谁。事后分析原因:雖然刪除命令 db.collection.remove({“flag” : 1}}; 很簡(jiǎn)單,但是 flag 字段并不是索引字段允扇,刪除操作等價(jià)于全部掃描后進(jìn)行缠局,刪除速度很慢,需要?jiǎng)h除的消息基本都是冷數(shù)據(jù)考润,大量的冷數(shù)據(jù)進(jìn)入內(nèi)存中狭园,由于內(nèi)存容量的限制,會(huì)把內(nèi)存中的熱數(shù)據(jù) swap 到磁盤上糊治,造成內(nèi)存中全是冷數(shù)據(jù)唱矛,服務(wù)能力急劇下降。
????????遇到問題不可怕井辜,我們如何解決呢绎谦?首先我們要保證線上提供穩(wěn)定的服務(wù),采取緊急方案粥脚,找到還在執(zhí)行的 opid窃肠,先把此命令殺掉(kill opid),恢復(fù)服務(wù)阿逃。長(zhǎng)期方案铭拧,我們首先優(yōu)化了離線刪除程序 [圖 6]赃蛛,把已讀 IM 離線消息的刪除操作恃锉,每晚定時(shí)從庫導(dǎo)出要?jiǎng)h除的數(shù)據(jù),通過腳本按照 objectid 主鍵(_id)的方式進(jìn)行刪除呕臂,并且刪除速度通過程序控制破托,從避免對(duì)線上服務(wù)影響。其次歧蒋,我們通過用戶的離線消息的讀取行為來分析土砂,用戶讀取離線消息時(shí)間分布相對(duì)比較均衡州既,不會(huì)出現(xiàn)比較密度讀取的情形,也就不會(huì)對(duì) MongoDB 的更新帶來太大的影響萝映,基于此我們把用戶 IM 離線消息的刪除由邏輯刪除優(yōu)化成物理刪除吴叶,從而從根本上解決了歷史數(shù)據(jù)的刪除問題。
8.2????大量數(shù)據(jù)空洞問題及其解決方案
????????MongoDB 集群大量刪除數(shù)據(jù)后(比如上節(jié)中的 IM 用戶離線消息刪除)會(huì)存在大量的空洞序臂,這些空洞一方面會(huì)造成 MongoDB 數(shù)據(jù)存儲(chǔ)空間較大蚌卤,另外一方面這些空洞數(shù)據(jù)也會(huì)隨之加載到內(nèi)存中,導(dǎo)致內(nèi)存的有效利用率較低奥秆,在機(jī)器內(nèi)存容量有限的前提下逊彭,會(huì)造成熱點(diǎn)數(shù)據(jù)頻繁的 Swap,頻繁 Swap 數(shù)據(jù)构订,最終使得 MongoDB 集群服務(wù)能力下降侮叮,無法提供較高的性能。
????????通過上文的描述悼瘾,大家已經(jīng)了解 MongoDB 數(shù)據(jù)空間的分配是以 DB 為單位囊榜,而不是以 Collection 為單位的,存在大量空洞造成 MongoDB 性能低下的原因分尸,問題的關(guān)鍵是大量碎片無法利用锦聊,因此通過碎片整理、空洞合并收縮等方案箩绍,我們可以提高 MongoDB 集群的服務(wù)能力孔庭。
8.3????那么我們?nèi)绾温涞啬兀?/h4>
? ??????方案一:我們可以使用 MongoDB 提供的在線數(shù)據(jù)收縮的功能,通過 Compact 命令(db.yourCollection.runCommand(“compact”);)進(jìn)行 Collection 級(jí)別的數(shù)據(jù)收縮材蛛,去除 Collectoin 所在文件碎片圆到。此命令是以 Online 的方式提供收縮,收縮的同時(shí)會(huì)影響到線上的服務(wù)卑吭,其次從我們實(shí)際收縮的效果來看芽淡,數(shù)據(jù)空洞收縮的效果不夠顯著。因此我們?cè)趯?shí)際數(shù)據(jù)碎片收縮時(shí)沒有采用這種方案豆赏,也不推薦大家使用這種空洞數(shù)據(jù)的收縮方案挣菲。
????????既然這種數(shù)據(jù)方案不夠好,我們可以采用 Offline 收縮的方案二:此方案收縮的原理是:把已有的空洞數(shù)據(jù)掷邦,remove 掉白胀,重新生成一份無空洞數(shù)據(jù)。那么具體如何落地抚岗?先預(yù)熱從庫或杠;把預(yù)熱的從庫提升為主庫;把之前主庫的數(shù)據(jù)全部刪除宣蔚;重新同步向抢;同步完成后认境,預(yù)熱此庫;把此庫提升為主庫挟鸠。
? ??????具體的操作步驟如下:檢查服務(wù)器各節(jié)點(diǎn)是否正常運(yùn)行 (ps -ef |grep mongod)叉信;登入要處理的主節(jié)點(diǎn) /mongodb/bin/mongo--port 88888;做降權(quán)處理 rs.stepDown()艘希,并通過命令 rs.status() 來查看是否降權(quán)茉盏;切換成功之后,停掉該節(jié)點(diǎn)枢冤;檢查是否已經(jīng)降權(quán)鸠姨,可以通過 web 頁面查看 status,我們建議最好登錄進(jìn)去保證有數(shù)據(jù)進(jìn)入淹真,或者是 mongostat 查看讶迁; kill 掉對(duì)應(yīng) mongo 的進(jìn)程: kill 進(jìn)程號(hào);刪除數(shù)據(jù)核蘸,進(jìn)入對(duì)應(yīng)的分片刪除數(shù)據(jù)文件巍糯,比如: rm -fr /mongodb/shard11/*;重新啟動(dòng)該節(jié)點(diǎn)客扎,執(zhí)行重啟命令祟峦,比如:如:/mongodb/bin/mongod --config /mongodb/shard11.conf;通過日志查看進(jìn)程徙鱼;數(shù)據(jù)同步完成后宅楞,在修改后的主節(jié)點(diǎn)上執(zhí)行命令 rs.stepDown() ,做降權(quán)處理袱吆。
????????通過這種 Offline 的收縮方式厌衙,我們可以做到收縮率是 100%,數(shù)據(jù)完全無碎片绞绒。當(dāng)然做離線的數(shù)據(jù)收縮會(huì)帶來運(yùn)維成本的增加婶希,并且在 Replic-Set 集群只有 2 個(gè)副本的情況下,還會(huì)存在一段時(shí)間內(nèi)的單點(diǎn)風(fēng)險(xiǎn)蓬衡。通過 Offline 的數(shù)據(jù)收縮后喻杈,收縮前后效果非常明顯,如 [圖 7, 圖 8] 所示:收縮前 85G 存儲(chǔ)文件狰晚,收縮后 34G 存儲(chǔ)文件筒饰,節(jié)省了 51G 存儲(chǔ)空間,大大提升了性能家肯。
9龄砰、MongoDB 集群監(jiān)控
????????MongoDB 集群有多種方式可以監(jiān)控:mongosniff盟猖、mongostat讨衣、mongotop换棚、db.xxoostatus、web 控制臺(tái)監(jiān)控反镇、MMS固蚤、第三方監(jiān)控。我們使用了多種監(jiān)控相結(jié)合的方式歹茶,從而做到對(duì) MongoDB 整個(gè)集群完全 Hold 住夕玩。
????????第一是 mongostat[圖 9],mongostat 是對(duì) MongoDB 集群負(fù)載情況的一個(gè)快照惊豺,可以查看每秒更新量燎孟、加鎖時(shí)間占操作時(shí)間百分比、缺頁中斷數(shù)量尸昧、索引 miss 的數(shù)量揩页、客戶端查詢排隊(duì)長(zhǎng)度(讀|寫)、當(dāng)前連接數(shù)烹俗、活躍客戶端數(shù)量 (讀|寫) 等爆侣。
? ? ? ? mongstat 可以查看的字段較多,我們重點(diǎn)關(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ù)庫處理慢,這時(shí)候我們需要針對(duì)性的優(yōu)化戴质。
? ??????第二是 web 控制臺(tái)度宦,和 MongoDB 服務(wù)一同開啟,它的監(jiān)聽端口是 MongoDB 服務(wù)監(jiān)聽端口加上 1000告匠,如果 MongoDB 的監(jiān)聽端口 33333戈抄,則 Web 控制臺(tái)端口為 34333。我們可以通過 http://ip:port(http://8.8.8.8:34333)訪問監(jiān)控了什么 [圖 10]:當(dāng)前 MongoDB 所有的連接數(shù)后专、各個(gè)數(shù)據(jù)庫和 Collection 的訪問統(tǒng)計(jì)包括:Reads, Writes, Queries 等划鸽、寫鎖的狀態(tài)、最新的幾百行日志文件。
? ? ? ??第三是 MMS(MongoDBMonitoring Service)裸诽,它是 2011 年官方發(fā)布的云監(jiān)控服務(wù)嫂用,提供可視化圖形監(jiān)控。工作原理如下:在 MMS 服務(wù)器上配置需要監(jiān)控的 MongoDB 信息(ip/port/user/passwd 等)丈冬;在一臺(tái)能夠訪問你 MongoDB 服務(wù)的內(nèi)網(wǎng)機(jī)器上運(yùn)行其提供的 Agent 腳本嘱函;Agent 腳本從 MMS 服務(wù)器獲取到你配置的 MongoDB 信息;Agent 腳本連接到相應(yīng)的 MongoDB 獲取必要的監(jiān)控?cái)?shù)據(jù)埂蕊;Agent 腳本將監(jiān)控?cái)?shù)據(jù)上傳到 MMS 的服務(wù)器往弓;登錄 MMS 網(wǎng)站查看整理過后的監(jiān)控?cái)?shù)據(jù)圖表。具體的安裝部署蓄氧,可以參考:http://mms.10gen.com函似。
? ? ? ??第四是第三方監(jiān)控,MongoDB 開源愛好者和團(tuán)隊(duì)支持者較多喉童,可以在常用監(jiān)控框架上擴(kuò)展缴淋,比如:zabbix,可以監(jiān)控 CPU 負(fù)荷泄朴、內(nèi)存使用重抖、磁盤使用、網(wǎng)絡(luò)狀況祖灰、端口監(jiān)視钟沛、日志監(jiān)視等;nagios局扶,可以監(jiān)控監(jiān)控網(wǎng)絡(luò)服務(wù)(HTTP 等)恨统、監(jiān)控主機(jī)資源(處理器負(fù)荷、磁盤利用率等)三妈、插件擴(kuò)展畜埋、報(bào)警發(fā)送給聯(lián)系人(EMail、短信畴蒲、用戶定義方式)悠鞍、手機(jī)查看方式;cacti模燥,可以基于 PHP,MySQL,SNMP 及 RRDTool 開發(fā)的網(wǎng)絡(luò)流量監(jiān)測(cè)圖形分析工具咖祭。
轉(zhuǎn)載自:https://www.infoq.cn/article/app-practice-of-mongodb-in-58-ten-billion-scale-data/