【轉載】視覺中國的NoSQL之路:從MySQL到MongoDB

文 /  潘凡

起因

視覺中國網站(www.chinavisual.com)是國內最大的創意人羣的專業網站。2009年以前,同很多公司一樣,我們的CMS和社區產品都構建於PHP+Nginx+MySQL之上;MySQL使用了Master+Master的部署方案;前端使用自己的PHP框架進行開發;Memcached作爲緩存;Nginx進行Web服務和負載均衡;Gearman進行異步任務處理。在傳統的基於靜態內容(如文章,資訊,帖子)的產品,這個體系運行良好。通過分級的緩存,數據庫端實際負載很輕。2009年初,我們進行了新產品的開發。此時,我們遇到了如下一些問題。

用戶數據激增:我們的MySQL某個信息表上線1個月的數據就達到千萬。我們之前忽略的很多數據,在新形勢下需要跟蹤記錄,這也導致了數據量的激增;

用戶對於信息的實時性要求更高:對信息的響應速度和更新頻度就要求更高。簡單通過緩存解決的靈丹妙藥不復存在;

對於Scale-out的要求更高:有些創新產品的增長速度是驚人的。因此要求能夠無痛的升級擴展,否則一旦停機,那麼用戶流失的速度也是驚人的;

大量文件的備份工作:我們面向的是創意人羣,產生的內容是以圖片爲主。需要能夠對這些圖片及不同尺寸的縮略圖進行有效的備份管理。我們之前使用的Linux inotify+rsync的增量備份方案效果不佳;

需求變化頻繁:開發要更加敏捷,開發成本和維護成本要更低,要能夠快速地更新進化,新功能要在最短的週期內上線。

最初,我們試圖完全通過優化現有的技術架構來解決以上問題:對數據時效性進一步分級分層緩存,減小緩存粒度;改進緩存更新機制(線上實時和線下異步更新)提高緩存命中率;嘗試對業務數據的特點按照水平和垂直進行分表;使用MogileFS進行分佈存儲;進一步優化Mysql的性能,同時增加MySQL節點等。但很快發現,即便實施了上述方案,也很難完全解決存在的問題:過度依賴Memcached導致數據表面一致性的維護過於複雜,應用程序開發需要很小心,很多時候出現Memcached的失效會瞬間導致後端數據庫壓力過大;不同類型數據的特點不同,數據量差別也很大;分表的機制和方式在效率平衡上很難取捨;MogileFS對我們而言是腳小鞋大,維護成本遠遠超過了實際的效益;引入更多的MySQL數據庫節點增大了我們的維護量,如何有效監控和管理這些節點又成了新的問題。雖然虛擬化可以解決部分問題,但還是不能令人滿意;

除了MySQL,能否找到一個更爲簡單、輕便的瑞士軍刀呢?我們的目光投向了NoSQL的方案。

候選方案

最初,對於NoSQL的候選方案,我依據關注和熟悉程度,並且在甄別和選擇合適的方案時特別制定了一些原則:是否節省系統資源,對於CPU等資源是否消耗過大;客戶端/API支持,這直接影響應用開發的效率;文檔是否齊全,社區是否活躍;部署是否簡單;未來擴展能力。按以上幾點經過一段測試後,我們候選名單中剩下Redis、MongoDB和Flare。

Redis對豐富數據類型的操作很吸引人,可以輕鬆解決一些應用場景,其讀寫性能也相當高,唯一缺點就是存儲能力和內存掛鉤,這樣如果存儲大量的數據需要消耗太多的內存(最新的版本已經不存在這個問題)。

Flare的集羣管理能力令人印象深刻,它可以支持節點的動態部署,支持節點的基於權重的負載均衡,支持數據分區。同時允許存儲大的數據,其key的長度也不受Memcached的限制。而這些對於客戶端是透明的,客戶端使用Memcached協議鏈接到Flare的proxy節點就可以了。由於使用集羣,Flare支持fail-over,當某個數據節點宕掉,對於這個節點的訪問都會自動被proxy節點forward到對應的後備節點,恢復後還可以自動同步。Flare的缺點是實際應用案例較少,文檔較爲簡單,目前只在Geek使用。

以上方案都打算作爲一個優化方案,我從未想過完全放棄MySQL。然而,用MongoDB做產品的設計原型後,我徹底被征服了,決定全面從MySQL遷移到MongoDB。

爲什麼MongoDB可以替代MySQL?

MongoDB是一個面向文檔的數據庫,目前由10gen開發並維護,它的功能豐富,齊全,完全可以替代MySQL。在使用MongoDB做產品原型的過程中,我們總結了MonogDB的一些亮點:

使用JSON風格語法,易於掌握和理解:MongoDB使用JSON的變種BSON作爲內部存儲的格式和語法。針對MongoDB的操作都使用JSON風格語法,客戶端提交或接收的數據都使用JSON形式來展現。相對於SQL來說,更加直觀,容易理解和掌握。

Schema-less,支持嵌入子文檔:MongoDB是一個Schema-free的文檔數據庫。一個數據庫可以有多個Collection,每個Collection是Documents的集合。Collection和Document和傳統數據庫的Table和Row並不對等。無需事先定義Collection,隨時可以創建。

Collection中可以包含具有不同schema的文檔記錄。 這意味着,你上一條記錄中的文檔有3個屬性,而下一條記錄的文檔可以有10個屬性,屬性的類型既可以是基本的數據類型(如數字、字符串、日期等),也可以是數組或者散列,甚至還可以是一個子文檔(embed document)。這樣,可以實現逆規範化(denormalizing)的數據模型,提高查詢的速度。

圖1 MongoDB是一個Schema-free的文檔數據庫

圖1 MongoDB是一個Schema-free的文檔數據庫

圖2是一個例子,作品和評論可以設計爲一個collection,評論作爲子文檔內嵌在art的comments屬性中,評論的回覆則作爲comment子文檔的子文檔內嵌於replies屬性。按照這種設計模式,只需要按照作品id檢索一次,即可獲得所有相關的信息了。在MongoDB中,不強調一定對數據進行Normalize ,很多場合都建議De-normalize,開發人員可以扔掉傳統關係數據庫各種範式的限制,不需要把所有的實體都映射爲一個Collection,只需定義最頂級的class。MongoDB的文檔模型可以讓我們很輕鬆就能將自己的Object映射到collection中實現存儲。

圖2 MongoDB支持嵌入子文檔

圖2 MongoDB支持嵌入子文檔

簡單易用的查詢方式:MongoDB中的查詢讓人很舒適,沒有SQL難記的語法,直接使用JSON,相當的直觀。對不同的開發語言,你可以使用它最基本的數組或散列格式進行查詢。配合附加的operator,MongoDB支持範圍查詢,正則表達式查詢,對子文檔內屬性的查詢,可以取代原來大多數任務的SQL查詢。

CRUD更加簡單,支持in-place update:只要定義一個數組,然後傳遞給MongoDB的insert/update方法就可自動插入或更新;對於更新模式,MongoDB支持一個upsert選項,即:“如果記錄存在那麼更新,否則插入”。MongoDB的update方法還支持Modifier,通過Modifier可實現在服務端即時更新,省去客戶端和服務端的通訊。這些modifer可以讓MongoDB具有和Redis、Memcached等KV類似的功能:較之MySQL,MonoDB更加簡單快速。Modifier也是MongoDB可以作爲對用戶行爲跟蹤的容器。在實際中使用Modifier來將用戶的交互行爲快速保存到MongoDB中以便後期進行統計分析和個性化定製。

所有的屬性類型都支持索引,甚至數組:這可以讓某些任務實現起來非常的輕鬆。在MongoDB中,“_id”屬性是主鍵,默認MongoDB會對_id創建一個唯一索引。

服務端腳本和Map/Reduce:MongoDB允許在服務端執行腳本,可以用Javascript編寫某個函數,直接在服務端執行,也可以把函數的定義存儲在服務端,下次直接調用即可。MongoDB不支持事務級別的鎖定,對於某些需要自定義的“原子性”操作,可以使用Server side腳本來實現,此時整個MongoDB處於鎖定狀態。Map/Reduce也是MongoDB中比較吸引人的特性。Map/Reduce可以對大數據量的表進行統計、分類、合併的工作,完成原先SQL的GroupBy等聚合函數的功能。並且Mapper和Reducer的定義都是用Javascript來定義服務端腳本。

性能高效,速度快: MongoDB使用c++/boost編寫,在多數場合,其查詢速度對比MySQL要快的多,對於CPU佔用非常小。部署也很簡單,對大多數系統,只需下載後二進制包解壓就可以直接運行,幾乎是零配置。

支持多種複製模式: MongoDB支持不同的服務器間進行復制,包括雙機互備的容錯方案。

Master-Slave是最常見的。通過Master-Slave可以實現數據的備份。在我們的實踐中,我們使用的是Master-Slave模式,Slave只用於後備,實際的讀寫都是從Master節點執行。

Replica Pairs/Replica Sets允許2個MongoDB相互監聽,實現雙機互備的容錯。

MongoDB只能支持有限的雙主模式(Master-Master),實際可用性不強,可忽略。

內置GridFS,支持大容量的存儲:這個特點是最吸引我眼球的,也是讓我放棄其他NoSQL的一個原因。GridFS具體實現其實很簡單,本質仍然是將文件分塊後存儲到files.file和files.chunk 2個collection中,在各個主流的driver實現中,都封裝了對於GridFS的操作。由於GridFS自身也是一個Collection,你可以直接對文件的屬性進行定義和管理,通過這些屬性就可以快速找到所需要的文件,輕鬆管理海量的文件,無需費神如何hash才能避免文件系統檢索性能問題, 結合下面的Auto-sharding,GridFS的擴展能力是足夠我們使用了。在實踐中,我們用MongoDB的GridFs存儲圖片和各種尺寸的縮略圖。

圖3 MongoDB的Auto-sharding結構

圖3 MongoDB的Auto-sharding結構

內置Sharding,提供基於Range的Auto Sharding機制:一個collection可按照記錄的範圍,分成若干個段,切分到不同的Shard上。Shards可以和複製結合,配合Replica sets能夠實現Sharding+fail-over,不同的Shard之間可以負載均衡。查詢是對客戶端是透明的。客戶端執行查詢,統計,MapReduce等操作,這些會被MongoDB自動路由到後端的數據節點。這讓我們關注於自己的業務,適當的時候可以無痛的升級。MongoDB的Sharding設計能力最大可支持約20 petabytes,足以支撐一般應用。

第三方支持豐富: MongoDB社區非常活躍,很多開發框架都迅速提供了對MongDB的支持。不少知名大公司和網站也在生產環境中使用MongoDB,越來越多的創新型企業轉而使用MongoDB作爲和Django,RoR來搭配的技術方案。

實施結果

實施MonoDB的過程是令人愉快的。我們對自己的PHP開發框架進行了修改以適應MongoDB。在PHP中,對MongoDB的查詢、更新都是圍繞Array進行的,實現代碼變得很簡潔。由於無需建表,MonoDB運行測試單元所需要的時間大大縮短,對於TDD敏捷開發的效率也提高了。當然,由於MongoDB的文檔模型和關係數據庫有很大不同,在實踐中也有很多的困惑,幸運的是,MongoDB開源社區給了我們很大幫助。最終,我們使用了2周就完成了從MySQL到MongoDB的代碼移植比預期的開發時間大大縮短。從我們的測試結果看也是非常驚人,數據量約2千萬,數據庫300G的情況下,讀寫2000rps,CPU等系統消耗是相當的低(我們的數據量還偏小,目前陸續有些公司也展示了他們的經典案例:MongoDB存儲的數據量已超過 50億,>1.5TB)。目前,我們將MongoDB和其他服務共同部署在一起,大大節約了資源。

一些小提示

切實領會MongoDB的Document模型,從實際出發,扔掉關係數據庫的範式思維定義,重新設計類;在服務端運行的JavaScript代碼避免使用遍歷記錄這種耗時的操作,相反要用Map/Reduce來完成這種表數據的處理;屬性的類型插入和查詢時應該保持一致。若插入時是字符串“1”,則查詢時用數字1是不匹配的;優化MongoDB的性能可以從磁盤速度和內存着手;MongoDB對每個Document的限制是最大不超過4MB;在符合上述條件下多啓用Embed Document, 避免使用DatabaseReference;內部緩存可以避免N+1次查詢問題(MongoDB不支持joins)。

用Capped Collection解決需要高速寫入的場合,如實時日誌;大數據量情況下,新建同步時要調高oplogSize的大小,並且自己預先生成數據文件,避免出現客戶端超時;Collection+Index合計數量默認不能超過24000;當前版本(<v1.6)刪除數據的空間不能被回收,如果你頻繁刪除數據,那麼需要定期執行repairDatabase,釋放這些空間。

結束語

MongoDB的里程碑是1.6版本,預計今年7月份發佈,屆時,MongoDB的Sharding將首次具備在生產環境中使用的條件。作爲MongoDB的受益者,我們目前也在積極參與MongoDB社區活動,改進Perl/PHP對於MongoDB的技術方案。在1.6版本後也將年內推出基於MongoDB的一些開源項目。

對於那些剛剛起步,或者正在開發創新型互聯網應用的公司來說,MongoDB的快速、靈活、輕量和強大擴展性,正適合我們快速開發產品,快速迭代,適應用戶迅速變化和更新的種種需求。

總而言之,MongoDB是一個最適合替代MySQL的全功能的NoSQL產品,使用MongoDB+Perl/PHP/Django/RoR的組合將很快成爲開發Web2.0、3.0的產品的最佳組合,就像當年MySQL替代Oracle/DB2/Informix一樣,歷史總是驚人的相似,讓我們拭目以待吧!

作者簡介:

潘凡(nightsailer,N.S.), 視覺中國網站技術總監,聯合創始人,家有1狗2貓。目前負責網站平臺設計和底層產品研發工作。當前關注:Apps平臺設計、分佈式文件存儲、NoSQL、高性能後現代的Perl編程。Twitter:@nightsailer  Blog:http://nightsailer.com/

 

 

原載地址:http://www.programmer.com.cn/4199/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章