“記憶體是新的硬碟,硬碟是新的磁帶”此話出自圖靈獎得主Jim Gray。
一、前言
我理解這句話的意思是,我們應該把隨機IO都放到記憶體中去,而把像磁帶一樣的順序IO留給硬碟這裡不包括SSD)。
如果應用沒有達到一定的層級,可能我們看上面兩句話都會覺得太geek,然而在應用資料量日益龐大,動態內容比例日益增大的今天,再忽視這個基本準則將會是一個災難。
今天我們談一下這一理論在NoSQL產品中的展現。
二、實現
問題一:宕機資料丟失
我們先看一下幾個傑出的NoSQL代表,Cassandra,MongoDB,Redis。他們幾乎都使用了同一種儲存模式,就是將寫操作在記憶體中進行,定時或按某一條件將記憶體中的資料直接寫到磁碟上。這樣做的好處是我們可以充分利用記憶體在隨機IO上的優勢,而避免了直接寫磁碟帶來的隨機IO瓶頸:磁碟尋道時間。當然,壞處就是如果遭遇宕機等問題時,可能會丟失一些資料。
解決宕機丟資料的問題有兩個方法:
1.即時記錄動作記錄
這時通常的做法是當一個寫操作到達,系統首先會往記錄檔裡追加一條寫記錄,成功後再操作記憶體進行寫資料操作。而由於記錄檔是不斷追加的,因此也就保證了不會有大量的隨機IO產生。
2.Quorum NRW
這一理論是基於叢集式儲存的,其原理是如果叢集有N個結點,那麼如果我們每次寫操作需要至少同步到W個結點才算成功,而每次讀操作只要從R個結點讀資料就一定能保證其得到正確結果如果某一結點有此資料,既成功,如果所有R個結點都無資料,則說明無此資料)。而NRW之間的關係必須滿足N < R + W 。其實這一理論並不難理解,我們可以將這個不等式做一下移項:R > N – W ,我們有N個結點,寫的時候最少寫W個才算成功,也就是W個結點有這份資料,那麼N-W就是說可能沒有某一份資料的最大結點數。最多可能有N-W個結點沒有某一資料,那如果我們進行資料讀取操作時,讀到大於N-W個結點,那麼必然有一個以上的結點是有這份資料的。所以要求R > N-W。
所以可能你已經想明白了,為了防止資料丟失,我們採用的實際是簡單的冗餘備份的方法。資料寫到多台機器會比寫單台機器的磁碟快嗎?對。相對於直接的磁碟操作,跨網路進行記憶體操作可以更快。其最簡單的例子就是改進的一致性hash:
摘自Amazon的Dynamo文檔,key的hash值位於A,B結點間的資料,並不是只存在B結點上,而是順著環的方向分別在C和D結點進行備份。當然這樣做的好處並不完全在於上面說的冗餘備份。
當然,很多時候是上面兩種解決方案同時使用以保證資料的高可用性。
問題二:記憶體容量的限制
當我們將記憶體當作硬碟來用的時候,我們必然會面臨容量問題。這也是我們上面說到的資料會定時flush到磁碟的原因,當記憶體中的資料已經超出可用記憶體的大小,那麼我們就需要將其進行落地操作,對swap的過度使用是不符合我們初衷的,也是達不到高效隨機IO的效果的。這裡也有兩種解決方案:
1.應用程式層swap
採用這種方法的有TokyoCabinet和Redis兩個產品。TokyoCabinet主要是通過mmap提高IO效率,而其mmap到的只有資料檔案頭部的一部分內容。一旦資料檔案大於其設定的最大mmap長度由參數xmsize控制),那剩下的部分就是純粹的低效磁碟操作了。於是它提供了一種類似於Memcached的緩衝機制,通過參數rcnum配置,將一些通過LRU機制篩選出來的熱資料進行key—value式的緩衝,這一部分記憶體是和mmap佔用的記憶體完全獨立的。同樣的,Redis在2.0版本之後增加了對磁碟儲存的支援,其機制與TokyoCabinet類似,也是通過資料操作來判斷資料的熱度,並將熱資料盡量放到記憶體中。
2.多版本的資料合併
什麼叫多版本的資料合併呢?我們上面講Bigtable,或其開源版本Cassandra,都是通過定時將記憶體中的資料區塊flush到磁碟中,那麼我們會想,如果這次是一個update操作,比如keyA的值從ValueA變成了ValueB,那麼我們在flush到磁碟的時候就得執行對老資料ValueA的清除工作了。而這樣,是否就達不到我們希望進行順序的磁碟IO的目的呢?沒錯,這樣是達不到的,所以Bigtable類型的系統確實也並不是這樣做的,在flush磁碟的時候,並不會執行合併作業,而是直接將記憶體資料寫入磁碟。這樣寫是方便很多,那讀的時候可能會存在一個值有多個版本的情況,這時就需要我們來進行多版本合并了。所以第二種方法就是將一段時間的寫操作寫成一個塊可能並非一個檔案),保證記憶體的使用不會無限膨脹。在讀取時通過讀多個檔案塊進行資料版本合并來完成。
那如果儲存在磁碟的資料量是記憶體容量的很多倍,我們可能會產生許多個資料區塊,那麼我們在擷取資料版本時,是否需要全部遍曆所有資料區塊呢?當然不用,如果你看過BigTable論文,相信你還記得它其中用到了bloom-filter演算法。bloom-filter演算法最廣泛的應用是在搜尋引擎爬蟲中,它用於判斷一個URL是否存在於已抓取集合中,這一演算法並不百分之百精準可能將不在集合中的資料誤判為在集合中,但不會出現相反的誤差),但其在時間複雜度上僅是幾次hash計算,而空間複雜度也非常低。Bigtable實現中也用到了bloom-filter演算法,用它來判斷一個值是否在某一個集合中。而由於bloom-filter演算法的特點,我們只會多讀幾率很小),不會少讀資料區塊。於是我們就實現對遠遠大於實體記憶體容量的資料的儲存。
三、結尾
好了,就寫到這裡,關於NoSQL中對此原理的應用還有更多理解和認識的同學,歡迎交流。