海納百川——人人網海量存儲系統Nuclear開發手記
此文為《程序員》雜志約稿,發表在2010年9月刊。懷念過去美好的時光和所有的UGC兄弟真摯友誼,謹以此文為個人職業發展階段作一個美好的終結。以下是全文原稿。
2009年8月左右,由于業務擴展的需要,我們的團隊開始了一個新項目的研發,其中需要完成一個存儲系統,把評論數據聚合到一起同時還要提供線上的讀寫服務。這些評論來自不同的業務產品,數據量非常之巨大;同時對穩定性的要求非常高,因為如果出現宕機,將影響到大量的業務線。于是,我們開始了對此類系統的探索。
Nuclear的由來
經過需求分析階段,擺在我們面前的是五點要求:高可用、高可擴展、高性能、Key-Value存儲、支持關系化查詢。經過一段痛苦的系統選型分析,我們最終決定開發屬于人人網的海量存儲系統。Nuclear,正如其名,nuclear的未來將要肩負起整個評論系統存儲的核反應般的壓力爆發的重任。由于當時并沒有這方面的經驗,一切都是摸著石頭過河,我們設計了好幾期的雛形,一開始明顯就是有問題的構架設計,慢慢地在學習和進步的過程中,團隊的成員也在慢慢地成長,離我們的目標也越來越近。又因為業務模型的需要和方便分布到集群,這個系統慢慢演變,最后成為了一個可靠的分布式key-value存儲系統。以下特將在研發過程中遇到的問題做一個總結。
Key-Value系統的優缺點
NoSQL系統在過去的一兩年里,飽受了爭議和技術界的目光。從原理上講,基本上這類系統都會有一些相同的優點和缺點:
優點:
1. 只有高效的查詢可用,性能是可想像的。
2. 容易分布到集群。
3. 可以很容易增加緩存層用來加速讀操作。
4. 邏輯和存儲清晰分離(出于性能考慮,關系型數據庫鼓勵將商業邏輯和存儲操作混在一起)。
缺點:
1. 沒有復雜的查詢過濾器。
2. 所有的聯合查詢必須在代碼實現。
3. 沒有外鍵的結構。
4. 沒有觸發器和視圖。
Nuclear系統的一大特點是,我們改進了普通的key-value系統在存儲模式上的固定形態,設計為上層的存儲協議和底層的存儲引擎完全分離,以便在不同的應用場景選擇更合適的存儲引擎。例如,當底層存儲使用MySQL時,可以支持key→list結構的弱結構化讀取;當底層存儲只使用內存時,那無疑便是一個分布式緩存系統了。
高可用性
任意一個分布式的存儲系統,都會遇到一個棘手的問題,那就是當一個數據節點出現故障的時候怎么辦?是不是整個系統都跟著崩潰了?當然不行。是不是可以丟掉一部分數據呢?這也是不可接受的。這也是有不少網友經常反饋的一個問題。答案是唯一的,那就是不要把所有的雞蛋都放在一個籃子里。但是如果一份數據在多個節點上有備份的話,那么這份數據的一致性也是一個致命的問題。
在參考了一些國內外分布式系統的處理方法后,我們歸納典型的做法有兩種,一種是以亞馬遜的dynamo系統為代表的NRW的方法;另一種是簡單的主從備份使用心跳檢測時刻檢查節點是否故障的一種做法。
(1) 亞馬遜Dynamo的NRW
在Dynamo系統中,第一次提出來了NRW的方法。Dynamo系統是將數據復制多份的系統,靠以下的機制來保障節點故障時服務的正常提供:
N –復制的次數
R –讀數據的最小節點數
W –寫成功的最小分區數
這三個數的具體作用呢,是用來靈活地調整Dynamo系統的可用性與復制數據一致性的。
舉例來說,如果R=1的話,表示最少只需要去一個節點讀數據即可,讀到即返回,這時是可用性是很高的,但并不能保證數據的一致性,如果W同時為1的話,那可用性更新是最高的一種情況,但這時完全不能保障數據的一致性,因為在可供復制的N個節點里,只需要寫成功一次的話就返回了,也就意味著,有可能在讀的這一次并沒有真正讀到需要的數據(一致性相當的不好)。理論上我們可以做到N個節點中只要有一個節點正常,那么這次操作就不會失敗。如果 W=R=N=3的話,也就是說,每次寫的時候,都保證所有要復制的點都寫成功,讀的時候也是都讀到,這樣子一定讀出來的數據是正確的,但是這中間的性能大打折扣,也就是說,數據的一致性非常的高,但系統的可用性卻非常低了,有一個節點出故障了,這次操作就失敗了。如果R + W > N能夠保證要讀的數據肯定都是寫成功的,Dynamo推薦使用322的組合使用可。
(2) 常見的主從備份和心跳檢測
主從備份最常見的要算是MySQL數據庫的備份了,而如果做了主從備份的MySQL出現了故障的話,常規的做法也是即時檢測與手機短信通知到人,然后再由工程師去手動處理,在工程師手動處理主從備份數據的過程中,MySQL靠log模式來保證數據的一致性。在諸如kata(apache下的一個分布式搜索)之類的系統中,由于在源碼中使用了ZooKeeper這樣的開發套件,在遇到分布式系統單點故障時,使得可以做到依靠系統本身也能全自動地進行節點的切換取舍,而這時數據的一致性,往往又需要另外的策略來保證。
以上兩種方案,我們選擇了第一種,原因主要是亞馬遜的方法實現非常的簡單,但是從理論上講卻非常的實用,真正有一種四兩撥千斤的感覺,所以很多時候,好用的東西往往不是最難的,實用才是硬道理。
數據分布與遷移時遇到的壓力沖擊
Nuclear是一個分布式的key-value存儲系統,key對應的數據分布在什么節點上,需要遵守一定的規則。在Nuclear中,數據分布在0~2^64的哈希環上,Nuclear集群初始化的時候,根據節點數均分整個哈希環。假如有A、B、C三個節點,key的分布如圖1所示:
圖1 key分布在三個節點的示意圖
圖1中,箭頭方向表示復制的方向,假設N=3,表示復制三份,如圖上的情況也就意味著每個節點都有三份數據,以此類推。
因為數據有多份,所以也存在著數據的自動遷移和恢復。這就會遇到一個問題:如果一個節點宕機后恢復,遷移程序勢必需要以最快的速度將原來需要的數據通過網絡從其他節點復制過來。這樣的數據導出導入必然會對對應的節點造成一定的沖擊,如果這時此節點開始提供服務,極有可能達到系統的臨界點,反而將剛剛恢復的節點沖擊宕機。
為了極力避開這樣的情況發生,而且能快速地完成遷移的過程,在nuclear中,所有的數據遷移過程,都會提前判斷操作系統當前的負載情況,根據系統負載來暫停和重啟遷移數據的線程。我們使用的開發語言是java,在JDK提供的java.lang.management包中,有許多監控系統負載的方法可以直接使用,非常方便。
系統架構和瓶頸分析
整個Nuclear系統構建于java之上,其系統架構如圖2所示:
圖2 Nuclear系統構架圖
處在最上層的是對外的存儲API,提供put、get等操作,接下來一層分成了兩個部分,一部分是正常的節點部分,一部分是后臺運行的定時任務,下面都是組件化的模塊,共同搭建了整個系統的穩定服務。
整個系統中最大的瓶頸出現在并發任務處理和數據傳輸上,為此我們大量使用了NIO、并發編程,充分發揮了多核CPU多線程處理的優勢,盡量將網絡傳輸導致延遲縮小到最小。這樣做的結果是,在高并發的情況下系統中也不存在阻塞點,甚至可以說,Ncluear系統的設計哲學是:萬事皆異步。
另一個更為直接的瓶頸,就是磁盤的IO瓶頸,在我們的實際線上環境中,最后遇到的問題也就是磁盤的瓶頸,在讀寫比為8比1且高并發壓力的情況下,如果底層的存儲引擎表現不佳,多半的原因都是,過多依賴了硬盤的讀寫,很容易就達到了硬盤IO的瓶頸,此時,我們的cache層的作用就舉足輕重,將大多數的數據留在內存中,這是一個不錯的做法。
結束語
人人網UGC團隊(http://ugc.renren.com)利用Nuclear,將炙手可熱的NoSQL系統真正用于生產環境中,提升了整體系統的穩定性和自動維護性,在中國眾多的互聯網企業中樹立了自己的技術風格,同時也為技術行業提供了不少可供參考的寶貴資料,熱忱歡迎更多的朋友加入到我們的行列中來。
冷昊:人人網UGC團隊成員,現和UGC技術團隊一起為人人網用戶在信息共享和傳播的更加便捷做不懈努力,關注系統架構、分布式技術、數據挖掘。個人Blog: http://www.lenghao.com。可以通過電子郵件 jerome.leng@gmail.com 聯系到他。
陳臻(54chen):人人網UGC團隊成員,技術組織哥學社創始人,PHPChina內核版主,業余時間混跡于各技術組織且樂此不疲。個人Blog:http://www.54chen.com/。可以通過電子郵件 czhttp@gmail.com 聯系到他。