海納百川——人人網海量存儲系統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所示:
key分佈在三個節點的示意圖
圖1 key分佈在三個節點的示意圖
圖1中,箭頭方向表示複製的方向,假設N=3,表示複製三份,如圖上的情況也就意味着每個節點都有三份數據,以此類推。
因爲數據有多份,所以也存在着數據的自動遷移和恢復。這就會遇到一個問題:如果一個節點宕機後恢復,遷移程序勢必需要以最快的速度將原來需要的數據通過網 絡從其他節點複製過來。這樣的數據導出導入必然會對對應的節點造成一定的衝擊,如果這時此節點開始提供服務,極有可能達到系統的臨界點,反而將剛剛恢復的 節點衝擊宕機。
爲了極力避開這樣的情況發生,而且能快速地完成遷移的過程,在nuclear中,所有的數據遷移過程,都會提前判斷操作系統當前的負載情況,根據系統負載 來暫停和重啓遷移數據的線程。我們使用的開發語言是java,在JDK提供的java.lang.management包中,有許多監控系統負載的方法可 以直接使用,非常方便。

系統架構和瓶頸分析
整個Nuclear系統構建於java之上,其系統架構如圖2所示:
Nuclear系統構架圖
圖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。可以通過電子郵件 [email protected] 聯繫到他。
陳臻(54chen)
陳臻(54chen):人人網UGC團隊成員,技術組織哥學社創始人,PHPChina內核版主,業餘時間混跡於各技術組織且樂此不疲。個人Blog:http://www.54chen.com/ 。可以通過電子郵件 [email protected] 聯繫到他。

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