閱讀793 返回首頁    go 阿裏雲 go 技術社區[雲棲]


MongoDB使用實踐:媽媽幫平台技術架構

在2017年3月12日下午於阿裏巴巴西溪園區舉行的MongoDB杭州用戶交流會上,來自媽媽幫平台的開發總監胡興邦給我們帶來了《媽媽幫平台技術架構及MongoDB使用實踐 》的分享,在演講中他對比傳統的關係型數據庫,分析並總結出了MongoDB的優勢和不足,以及實際使用中應注意的問題。

此次演講的內容主要分為四個方麵:

  1. 選擇並使用Mongo的經曆 
  2. MongoDB與關係數據型數據庫的對比 
  3. MongoDB對開發和架構帶來的影響 
  4. MongoDB的數據模型設計。

以下是本次演講的整理內容:

一.早期對MySQL的使用

7ee7ddb8a4a9492c55d18d5dfd927ae51c4e87f0

早在2012年聖誕節上線的新係統中我們就開始全線使用Mongodb的數據庫,而在那之前我們使用的數據庫是傳統的MySQL,因為它比較開源,性能穩定。在使用MySQL的幾年中,我們針對使用過程中出現問題提出了一些解決方案:如利用分庫分表解決數據量問題,利用“一主多從”的模式緩解訪問壓力等等。同時我們也研究了amoeba和mysql-proxy等中間件產品。盡管如此,使用MySQL運維的成本仍相對較大,因為在12年之前雲產品還不是很豐富,很多中間件和MySQL的運維工作都需要自己去做,並且運行在自己的IDC上,無形之中增加了不少的成本。

二.現在的MongoDB集群架構

4d91beb9a7df409bf2e8aa59454df2c741541fba

從2012年使用MongoDB以來,目前部署的MongogDB集群的架構圖如上:

  • 左側可用區B區:目前B區用來為MongoDB、整個IDC機房以及其他資源上雲這個過程服務,其中最重要的一個環節就是MongoDB的上雲。因為不像其他的應用服務器,如Python,Java等,這些資源都是對等、無狀態的,所以比較容易上雲,而在MongoDB上雲的過程中我們則必須保障服務不被中斷,否則後果嚴重。
  • 右側可用區A區:A區是現在主要的架構,為了緩解讀寫壓力而采用了“一主四從”的模式。另一種“一主兩從”的模式則主要用於線上業務,其中“一從”是供大數據分析部門做數據抽取使用, “另一從”則充當延時的節點,以防止數據被誤刪的情況。同時一些數據節點可以采用多台機器共用一個物理機方式以節約成本。

三.使用MongoDB的曆史

443080084fa9d2c02c03ec2d99129d1ff84a5835

使用MongoDB的曆史大致可分為如下四個階段:

  1. 早期使用MongoDB的過程比較簡單,所有應用程序和數據庫都運行在同一台機器上麵。
  2. 緊接著隨著應用量不斷增加,出於成本的考慮我們做了主從。因為如果做複製集的話至少需要2N+1個節點,這在早期對資源和成本會帶來不小的壓力。
  3. 後麵我們做了多組的主從和多組的複製集。這樣做是迫於在早期版本的MongoDB中鎖結構的性能相當不盡人意,它因為鎖庫會對數據庫中表,比如回帖和發帖的表,進行頻繁的數據讀寫。為了緩解這個庫的讀寫壓力以及鎖的壓力,我們按照業務邏輯對數據進行切分,例如我們會將某個話題庫裏麵的回帖單獨地抽取出來作為一個獨立的組,然後再對組進行相應的操作。
  4. 之後在15年底我們做了Sharding。Sharding適用於處理數據量巨大的表,利用Sharding我們能夠處理數據庫中的回帖表,這個表中包含了接近3億條回帖。目前我們一共部署了五個複製集群,其中隻有回帖部分做了Sharding,其他部分都沒有做Sharding。

四.青睞MongoDB理由

229e40cf478e6f92d7f36e5dc8a90f3093246eff

選擇MongoDB的理由很簡單,而且也不完全是技術原因:

  1. 跳出關係型數據庫很酷:這個想法由來已久,我們最早接觸的非關係型數據庫日本一個Key/Value數據庫。那時它的數據結構相當簡單,類型上沒有Redis這麼豐富,雖然運行效率還不錯,但是對開發來說還有點變扭。不過它至少讓我們第一次接觸到了非關係型數據庫。
  2. 很互聯網:MongoDB的互聯網基因很強,適合互聯網公司。
  3. 設計基因裏就考慮到分布式架構:不同於傳統的關係型數據庫如MySQL,MongoDB在設計的時候就考慮到了分布式,對於創業型公司來說這樣的考慮可能可以降低一點後期的運維成本。

五.MongoDB與關係數據的優缺點對比

c131ff304bb08d937c78c43a4343fe0e13b2795f

MongoDB和關係數據庫的對比主要集中在MongoDB和MySQL的對比上,並且可以從schema、事務、穩定性、分布式和運維五大方麵進行對比:

  1. schema:眾所周知,MongoDB是非關係型數據庫,裏麵基本不存在schema;而關係型數據庫MySQL在這一方麵則非常強。
  2. 事務:MongoDB的事務性相對比較弱,盡管有final-modify可以保證某些事務的一致性,但很難保證其他複雜事務的一致性;相比之下,MySQL比較完善一點。
  3. 穩定性:早期版本的MongoDB相當的不穩定,這一點MongoDB不如成熟的MySQL。
  4. 分布式:Mongo“天生驕傲”,從設計之初就考慮了分布式,而MySQL則折騰比較多。
  5. 運維:相對於MySQL,MongoDB的優勢體現在它的便利性。在IDC上搭建MySQL往往費時費力。

六.無schema是一把雙刃劍

bb41398b25adf92fe4337c3fea2997298348cf90

無Schema對開發的影響主要集中在以下四點:

  1. 對數據庫、表、字段的添加極為方便:雖然它對於字段的添加、數據庫或者數據表的創建非常開放,但與此同時,如果任何一個開發者不能合理地控製這一個過程的話,那麼表的增長在後期將變得不可控。這個問題在開發的過程中顯得尤為棘手,特別是出現當一個開發人員無意中添加了一個字段,而另外一個開發人員發現這個字段很好用卻沒有加索引這種嚴重的情況。這會對後期的運維造成一定的壓力,這也是在程序設計中經常會麵對的挑戰。
  2. 數據模型設計隨意性大:因為MongoDB沒有schema。
  3. 給db運維帶來了風險:基於前麵兩個原因,MongoDB有時會給運維帶來一定的風險。
  4. 數據結構使用選擇糾結:對一個剛剛接觸MongoDB,對MongoDB不太熟悉的程序員來說,對數據的選擇往往令人迷茫。例如,在MongoDb裏麵,bjson可以嵌套多層,可以嵌套數組,也可以嵌套對象,這樣無疑使數據的選擇變得更加寬泛,這對後期的整體架構會帶來很多的壞處。

七.事務問題

e9b5cec1492fa67b5caaf736c202d57f9b78b315

以我們在實際使用中遇到的問題為例,社區雖然對事務性方麵要求不是太高,但在某些場景下,確實需要關注對事務的處理:比如一個用戶在某個話題下發了一個回帖,由於這個發帖的操作在後台可能涉及數據庫,也可能跨複製集,所以不能完全保證事務一定成功。並且即使用戶成功地回帖了,但也不能保證其個人中心頁麵下顯示回帖總量被正確地更新。這裏牽涉到事務處理的問題。

上述事務問題可以采用 1.後台定時修正 2.隊列 3.二階段提交 這三種解決方案。而我們大體上是基於後台定時修正這種方案來保障計數這個功能或是其他的事務。雖然後麵兩種技術方案目前都比較成熟,但從開發人員的對事務的理解以及對代碼的維護性等角度來說過於複雜。因為有些事務和業務高度耦合,後兩種方案往往不利於維護這些代碼;而在後台定時修正這個方案中,它通過將所有代碼都集中在這一塊來簡化修正這個過程,同時也允許選擇性地修正對事務要求較高的那一部分,以盡量地避免事情發生。

八.事務問題中仍存在的不足之處

d4297d72380d1fbf7ecec820014aacfbda8862c1

上述解決方案雖然在一定程度上能解決一些問題,但仍不夠“完美”,這主要體現在:

  1. 與業務的耦合性高,很多業務點的保證是基於業務方的需求來做的。
  2. 代碼重用和維護性都差。
  3. 對開發的要求高且增加複雜度和bug出現的幾率。
九.MongoDB的穩定性問題
33b97dff5010419ec99fb2988fec08306cf2b0c0

MongoDB的穩定性問題主要集中在內存性能和係統升級這兩方麵:

  1. 內存不足時性能極其不穩定:在3.0版本之前MongoDB的性能都十分不穩定,特別是在開啟內存文件映射引擎之後,一旦數據量和內存容量差不多的時候,這時係統中的數據會變得十分不穩定,產生的false非常多,隊列讀寫操作也會變得異常,並且同時也會讓排查錯誤這個過程在一定程度上變得更加困難。
  2. 一路升級:以我們在實際使用中升級MongoDB為例,因為使用得比較早,所以一路要升級。在2月底的時候,我們剛把MongoDB從2.6全線升級到了3.2.12,這次升級最大的變化就是config-server的升級,它的數據結構發生了改變,並且在運行的時候要對其進行數據的初始化。這個server以前是單鏡像的,而現在官方希望實現複製集以保證多個鏡像,但這麼做缺點是如果有一個節點出現問題,那麼config-server所擁有的寫功能將不被允許,這就意味這用戶隻擁有讀的功能,卻不能做chunk的遷移,以及相關的balance的操作,所以說這個升級是相當的痛苦。2.2到2.6的版本升級對客戶端的升級也是比較大的,其中2.6主要涉及一些在客戶端命令方麵的改動。而2.6到3.2的版本升級中最重要的升級是對WiredTiger引擎的升級,一般來說升級普通的組件還是比較容易,因為隻需要升級相應的bin文件就可以了,但如果要升級它的WiredTiger引擎的話,那麼所有數據都必須重新錄入一遍的。在這個升級中我們一共花了兩個周末的時間,前一個周末升級了一部分的集群,後一個周末升級了另外一個集群。

十.關於MongoDB分布式的總結

9219b9d444548b58e6f507847dbe50119b08809f

關於MongoDB在分布式環境下的總結:

  1. 利用複製集解決單點的問題:這樣無論任何一台機器宕機,也不會影響係統的整體性能。它可以支持MySQL所不具備的自動選主的功能。
  2. 使用Sharding解決數據容量的問題:當我們數據庫表中保存的帖子總量超過兩億之後 ,一般的方法在這個查詢方麵顯得尤為吃力,在實際使用中我們認為Sharding的性能還是比較令人滿意的。

十一.片鍵選擇很關鍵

e0a07759bc045cf717d7500ca00750d253d42c67

從開發的角度來看選片鍵十分重要,如果片鍵選得不好,那麼這對之後的數據重新切分是很被動的,同時也會耗用大量運維的時間。片鍵選擇的重要性體現在如下四個方麵:

  1. 滿足業務場景:在一般業務場景下,大部分的query都應當有對應的query-key。缺少query-key會導致的一個問題就是對mongos造成極大的壓力。舉個例子:我們當時就是因為上線時沒有用好share-key而被迫放棄了一個業務,這個業務在上線時造成mongos出現流量異常的情況,流量非常大,甚至超過一個G,這導致了整個內網的癱瘓。最後追查下來發現是因為有個開發人員在查詢的時候沒有借助share-key,導致mongos要在各個sharding進行數據查詢並匯總,最終導致對mongos產生巨大的壓力。所以建議在業務場景盡量保證集中使用share-key。
  2. 避免負載不均勻:分片有兩種,一種基於hash的,另一種基於range。有些場景不太適合range分片,比如在一條帖子後麵追加發貼,這會給後麵的sharding帶來持續增長的壓力。而如果基於hash分片的話,這樣所有的壓力都會被均勻地分攤到多個sharding上麵去,從而減小了係統的負擔。目前我們的發帖采用的正是hash這種方式,因為基於用戶的角度來看,發帖不需要太多連續性的屬性。
  3. 避免hash的稀疏導致chunk過大:比如說如果按照性別來進行hash,這是不太合理的。
  4. 官方推薦的做法:可以根據官方的文檔考慮share-key的選擇,並且結合自身的業務邏輯來判斷是否負責自己的場景,並選擇出最合理的方案。

十二.關於MongoDB運維的思考

4336a2fad18dce922f8a870c22c74bfcadfb8562

針對運維自動化方麵的不足,我們主要在以下五個方麵進行優化:

  1. 基於rockmongo優化了權限控製:因為之前沒有驗證這一部分,所以給開發造成一定的不便,而盡管後麵增加了驗證部分,但權限控製的力度仍然不夠,這使得我們有時在運維開發中不得不去線上query這些數據。針對這一問題我們基於rockmongo做了二次開發,集中優化了權限控製這一部分,使其基本上能控製collection這一級別。
  2. 監控數據庫和表的增長:由於開發環境中不存在schema,所以有可能導致數據庫表或是connection的增長不可控,為此我們做了實現了一個監控服務,這個服務能時刻監視client下麵數據的增長。舉個例子:一個業務的數據量原本被設計為每天增長十萬,但突然間這個業務發展迅勐,數據量勐增到了一千萬,這時就要快速發現並確定采用是采用sharding的技術還是通過應用層分庫分表來緩解數據庫壓力,避免因為數據的增長而導致內存耗盡,給運維帶來不穩定性。因此通過監控這些數據,我們能迅速發現異常的數據表或是connection,並且會與業務方及時溝通,然後進行優化。
  3. 大表加索引:不同於background這種做法, 當超過100萬時我們會采用官方的滾動加索引的方式,隻對在線用戶需要用到的表加索引,並且白天時間是不加索引的。采用Background這種方式加索引固然有它的好處,它可以不影響業務,並且保持這台機器不下線,但缺點是在加索引的過程中不能高效地利用內存資源。相反,如果使用滾動加索引,我們可以在複製集中暫停一台機器,讓它全心全意地加索引,這樣處理效率比較高,能很快地保證索引加完,並且時間比較可控,比如可以在晚上加索引,然後立馬進行上線,最後掛載到複製集下麵去。因此在超過100萬數據時,我們不會業務線上是對其加索引。
  4. 兼容性的總結:包括對客戶端的總結,其中有很多對業務影響比較大的改動。
  5. 費機器:以複製集為例,一個複製集往往需要2N+1台機器來支撐,並且這些機器最好是對等的,以支持業務的發展。我們每次增加機器至少都是三台,因為這些業務對內存的需求特別大。所以我們在創業的時候,架構也在相應地不斷變遷,從主從到複製集,到複製集,然後到sharding。這也是處於成本的考慮。 

十三.數據模型設計

467fd20e1d4cb2fb491c6d027c157df06d591098

One-to-one

關係型數據庫總結起來主要包括一對一、一對多和多對多等關係型數據模型。在one to one 關係中如果有id、name以及其他的附屬信息需要處理時,一般關係型數據庫的做法是設計成左邊的兩張表,而非關係型數據庫則設計成右邊的一個Document。如果設計成左邊的兩張,那麼當更新數據時必須要更新這兩張表,此時如果發生一個表更新操作成功而另一表可失敗的情況,這就會造成數據的不一致,如果發生了數據不一致,是否采取回滾還涉及到事務的問題。遇到這種情況相對來講可以比較容易地解決,例如讓用戶進行重試,但對於某些複雜的操作來說,實現回滾是比較困難的。所以如果按照右邊的方案設計一個document,並且在裏麵建索引,這種情況能到到有效緩解。

5714cf1532ca3a7afd16361aa5225d300fbc7012

One-to-many

在one to many關係中,關係型數據庫可以設計成利用主鍵來關聯兩個表,而右邊的非關係數據庫則是采用數組來進行存放,但當數據中元素比較多時,還是不建議這樣做,因為它在後期容易使得對數據庫的各種操作變得複雜。

十四.數據模型設計

45bfe456cef3bb43b8e2c539fcc3ed97ba4b38fb

個人關於數據模型的三條總結:

  1. 優先使用單一的Document來進行存儲:盡量保證數據是在一個Document裏麵的存儲,這樣一來能減輕了很多事務方麵的問題。
  2. 靈活選擇常規的collectoin或是滾動的collection:因為在有些業務場景中確實是不需要長時間地保存業務數據,比如記錄管理員的操作或是用戶的操作行為,這些並不需要被一直保留,我們可以把一百萬或是一千萬條數據臨時保留三個月就可以了,在這之後我們對些數據也不會追查這麼久。
  3. 借助數組、字典等數據類型:這些數據結構往往能夠解決許多業務場景中出現的比較特殊的情況,這些都是MongoDB中用到的比較優秀的特性。

十五.最後的建議

cd0cbf224bec948d4ef7e42907e94fd1367dcc6d

用了這麼多年的mongo,我想給那些準備或是將來打算使用MongoDB的程序員提一些建議:

  1. 如果對事務要求確實比較嚴格的話,建議慎用MongoDB:比如如果通過一個zone-keeper搭建一個分布事務,使用MongoDB在運維方麵可能會讓人感覺起來比較費力,同時開發起來也比較費勁。因此在事務要求比較多的情況下還是建議使用傳統的數據庫,當然也可以選擇現有的雲存儲或是阿裏RDS,目前這些產品都比較成熟了。
  2. 後期難以和傳統的數據庫互相轉換:我們在開發的後期階段有一段時間是想把MongoDB轉化為MySQL的,之後再轉換到阿裏雲的RDS上麵去,但後來這個想法直接被放棄了。原因很簡單:因為數據庫轉換十分複雜麻煩,它可能會涉及到嵌套的問題,可能包括是一對多的關係,這樣分離起來比較麻煩。
  3. 提前考慮,避免無schema帶來的混亂問題:如果你想使用Mongodb或是想避免schema的話,最好在數據的前期對你的model進行嚴格的限製。比如在前期有一個數據bjson對象,或是一個數組,那麼在前期一定要仔細驗證這個數據結構,避免後期出現結構數據方麵的異常,因為在對數據類型比較嚴格環境中,對於這些數據訪問的可能會產生一些異常。因此如果前期考慮不足,這會使後麵開發和數據校驗以及修複的過程複雜化。
  4. 多看官方文檔,積極升級:相對於其他來源的文檔,官方文檔寫得還是十分詳實的,包括一些數據升級、數據加索引以及各個方麵的內容官方都提供了詳細的操作文檔,所以目前我們基本上所有的操作也都是基於官方的文檔。另外積極升級也很重要,舉個例子,通過升級2.6到3.2wired-Tiger引擎,我們內存的使用情況和數據壓縮情況都大大得到了緩解,而在升級之前我們是用的內存映射引擎,這比較消耗內存,所以當時我們的機器都配置了196G的內存。

最後更新:2017-04-20 00:31:12

  上一篇:go test ceshi
  下一篇:go 多方位拓展之路:監控平台MongoDB實踐