[轉]海量存儲檢索原理系列文章

轉自:http://blog.csdn.net/heiyeshuwu/article/details/9722443

海量存儲檢索原理系列文章

作者:WhisperXD

來源:http://qing.blog.sina.com.cn/whisperxd




Nov20

海量存儲之序言

​今天玩微薄的時候有人問我有沒有數據存儲的相關資料,我想了想。。雖然在這個領域內也算有點積累,以前講課的ppt有200多頁,但畢竟ppt的信息量有限。所以在這裏將這個系列的部分內容在這裏進行重新編排


主要將涉及到:
1. 數據庫原理   http://qing.weibo.com/1765738567/693f0847330005sm.html
   關係代數   http://qing.weibo.com/1765738567/693f0847330005v7.html
   事務  http://qing.weibo.com/1765738567/693f084733000672.html
   數據存儲模型
   數據寫入模式性能和安全性分析
   
2. 倒排索引
3. 分佈式kv系統
   數據的切分
   數據的管理和擴容
   數據安全性
   讀寫可用性
4. 硬件存儲在淘寶的測試數據和分析
5. 淘寶在線數據存儲檢索經驗介紹

http://qing.weibo.com/1765738567/693f0847330005v7.html 下一篇


Nov20

海量存儲系列之一

http://qing.weibo.com/1765738567/693f0847330005sk.html 海量存儲系列之序言

那麼 在走進海量存儲與檢索的世界之前,我們先看一看目前似乎覺得最落伍的數據庫系統。醜話先說。。我也沒在這個領域沉浸幾年,所以其實裏面很多的概念也是有可能模糊的,所以在這裏寫出來,一是希望我能把以前的積累再次重新的梳理一次,查缺補漏。二也是在這世界留下點記錄。。表明我曾經來到過這世界,學到過這些東西。。

之所以從這裏開始,一部分的原因是我本身是從這裏開始接觸存儲這個領域的,另外一部分原因是因爲,從我的私心來說,我認爲數據庫是生產力工具的開始,也將是生產力工具最終的歸宿。

一個數據庫,我們可以抽象的認爲由下面的一個邏輯結構組成,刨除意義不大的視圖,存儲過程,外鍵限制等之後,我們就剩下了下面的這張圖:

海量存儲系列之一


從API來說,也就是SQL,結構化查詢語言,這個東東我們後面再去細說,先來看看這個關係代數模型。
之所以要從這裏開始,主要的原因是因爲,這是最受到關注的一個部分,自大從一開始做分佈式數據層開始,被人問得最多的問題就是:1. 切分以後如何做join。2.如何進行分佈式事務。。
可惜,現在我也沒有一個方法能做到100%讓您滿意。。因爲,沒有銀彈,只有取捨。
取捨的原則,也就是要根據,1) 你能做什麼。2)你需要做什麼。3)你能放棄什麼。來決定上層系統的整體架構。傳統的ACID的關係代數模型,在新的環境中,很難複用。但,原理沒有變,做法上可以做微調

不知道各位在想起關係代數的時候,會想到什麼?
http://www.bitscn.com/os/windows/200604/4364.html 我給大家一些解釋(當然我沒仔細看過:)

不愛看上面解釋的童鞋們呢,也不用糾結,給大家一些簡單的例子。
1. select * from tab where user = ?
這就是最簡單的關係代數的例子,我們對他進行抽象來說,其實就是將大量數據中的一小批數據,按照某個要求查出來的過程。
2. select ename,dname from emp,deptwhere emp.deptno=dept.deptno and emp.deptno=30
這是另外一個例子,本質來說就是個∩的操作。
3. select count(*) from tab.
這是關係代數的第二個主要的用處,就是進行統計和計算,這類的函數有個專用的名字,叫做aggregate function.(感謝@KR明

恩,基本上有這個概念就可以了。
那麼這裏請聯想,你所接觸到的什麼地方會有碰到有這樣的一些計算呢?
數據庫?對。不過不是廢話麼。。
還有就是hadoop的平臺也會用到,hive pig.

那麼,我們對這類關係代數進行一下簡單的概念上的小結。
也就是用於處理數據的一類方法的抽象,最主要的作用是,按照某個條件選出一批數據,然後再進行一些簡單的統計計算的功能。如是而已:)

那麼,下面就有個疑問了,下層的數據庫或存儲,是如何進行這類的計算的呢?
那麼請看下一個章節。
我們一起探尋一下,處理這類關係代數計算的方法有哪些




Nov22

海量存儲系列之二

​http://qing.weibo.com/1765738567/693f0847330005sm.html 上一篇

在上一篇裏面,我們對數據庫的抽象的組成原理進行了簡單的描述。在這一篇裏面,我們一起來看看,如何能夠使用kv這樣的工具。來完成關係代數運算。
那麼,讓我們先來熱熱身:
海量存儲系列之二
這是一組數據,以pk作爲主鍵,user_id和Name是外key.
那麼,如果我要運行查詢:Select * from tab where id = ?
應該如何進行呢?
這裏需要一些額外的知識,在數據結構中,有那麼一種結構,可以用於處理按照某個key找到value的過程,抽象來看,一種方法是二分查找法,一種方法是hash.
如果各位是java用戶,那麼二分查找的實現可以認爲是個TreeMap的實現,而Hash的方法則可以認爲是hashMap的實現。如果是個c/cpp的用戶,那麼就二分查找就對應map實現。而hash實現則對應stl裏面的hash_map。
那麼,這裏的這個問題,我們就很容易可以解決了
以id作爲map的key,以其他數據作爲value,把所有數據都放入到map裏面,然後再使用id=1作爲key,從map中找到對應的value返回即可。(這一個部分,我們在後面的章節裏面還會介紹,現在大家只需要有個大概的印象即可)
怎麼樣?是不是很簡單?那麼,我們來討論更進一步的問題:
如果我想找到符合Select * from tab where user_id = 0的所有結果,應該如何去作?
仔細想想。那麼第一種做法一定是這樣。
把整個集合內的所有數據,都拿出來,然後找到user_id的數字,如果user_id=0,那麼就認爲是符合要求的記錄,直接返回。
如果不是user_id=0,那麼不匹配,丟棄這條記錄即可。
這樣一定可以找到所有符合要求的記錄。
然而,這樣作,帶來的問題是,我有多少條記錄,就需要進行多少次這樣的匹配,那麼,假設有100000000000000000條記錄,就需要匹配這樣多次,才能找到符合要求的記錄。這是個悲劇。。
那麼,怎麼解決這個悲劇呢?
於是有些聰明人就又想起了map結構,hash或tree,不都可以按照k找到value麼。那我們這裏也可以利用這個map結構嘛。。
海量存儲系列之二

也就是說,以user_id作爲key,id作爲value,構建一個Map.不就又能進行快速查詢了麼。
於是,就有了數據庫最重要的一個結構“索引” 這種以外鍵作爲key,主鍵作爲value的東西,有個專有的名字,叫做二級索引。
有了二級索引,我們的所有查詢,都可以以接近O(LogN)(有序數據),或O(1)的效率找到我們需要的數據。是不是很爽?
但這不是銀彈,你付出了空間成本,本質來說就是空間換時間的過程。同時,也會降低寫入的效率。
怎麼樣?理解了沒?如果自認爲對這些都瞭解了,那麼我們再來看一個問題:
海量存儲系列之二
如果我要找的是:Select ...where user_id = ? And name = '襪子'
應該怎麼做呢?
估計很多人都立刻又會想起那個Map,對的,但在這裏,我想給出以下的幾種查詢的模式:
1. 遍歷所有數據,取出一條以後,查看user_id = 0 and name='襪子'是否符合要求,如果符合,則返回數據。
這是個合理的策略,空間最爲節省,但帶來的損耗是要遍歷所有的數據。
2. 如果有個user_id -> pk的索引
海量存儲系列之二

,那麼我們可以先按照user_id,找到一組符合要求的pk list.然後再根據pk list,再回到
海量存儲系列之二
取出符合要求的數據後,判斷name=‘襪子’這個條件,如果符合,就返回,不符合,就丟棄。
這是個折衷策略,在空間和性能中,儘可能的找到個合理的區間的策略。
題外話,這個“根據pk list,再回到pk=>整個數據的kv表中,找出符合要求的數據後,判斷name=‘襪子’這個條件,如果符合,就返回,不符合,就丟棄”的策略,在數據庫有個專有名詞,叫回表。
3. 組合索引
這是個新名詞兒,但其實也是個很簡單的概念。
直接上圖:
海量存儲系列之二
:-),其實就是個很簡單的策略,先比較user_id進行排序,如果user_id相同,那麼比較name排序。
這樣,假定我們有100000條記錄,屬於100個用戶,那麼平均來看,每個用戶就只有1000條記錄了。
原來要回表1000條記錄才能找到符合要求的數據,而如果使用組合索引,這1000條,也可以使用O(log2N)或者O(1)的策略進行檢索啦。
在很多場景中,都能夠提升效率和速度。但付出的是更多的存儲空間。
好啦,這篇就介紹到這裏,留個題目給大家:
海量存儲系列之二
假設有這麼一組數據,性別有4種,user_id是一對多的關係,如果我想查詢
select * from tab where user_id in (?,?,?,?) and 性別='不明'
如何進行索引構建能夠獲得比較好的效果呢?



海量存儲系列之三

上一篇 http://qing.weibo.com/1765738567/693f0847330005v7.html

首先是回答上次的問題。

海量存儲系列之三

假設有這麼一組數據,性別有4種,user_id是一對多的關係,如果我想查詢

select * from tabwhere user_id in (?,?,?,?) and 性別='不明'

如何進行索引構建能夠獲得比較好的效果呢?

我個人認爲,應該建立的是以user_id作爲前導列,性別作爲輔助列的索引,在大量單值查詢時會有優勢。

理由如下

1. 假定總數據量爲N,user_id的區分度爲N/10000 而性別的區分度爲N/4

那麼如果以user_id作爲前導列,性別作爲後列,那麼查詢的複雜度爲O(logN+log(N/10000))。也就是說,第一次二分查找之後,下一次是在第一次的二分查找的基礎上再去查找。而如果以性別作爲前導,user_id作爲後列,那麼複雜度爲

O(logN+log(N/4));

效率略差。

然後進入本次正題。上次介紹了關係模型,那麼這次我們來介紹一下事務。

在一切之前,我想先給自己解嘲一下。。事務我自己也沒有辦法完全融匯貫通,因爲每一個小的選擇,都會導致效果的完全不同,所以有錯請在後面一起探討。

那麼我們在這裏,主要以單機事務作爲入手點,然後闡述一下多機事務相關的知識點。我在這裏只是想做一個引導,讓大家能夠對整個的知識體系有一個基本的認識,對於細節問題,會給出一些資料,而不會直接去進行講解,因爲篇幅所限.

一般來說,我們一提起事務,就會想到數據庫,確實,事務是數據庫的最重要的一個屬性。但這似乎不是事務的本源,那麼,讓我們從更深層次去對事務進行一次思考吧:

事務,本質來說就是一組由一個人(或機器)發起的連續的邏輯操作,共同的完成一件事情,在完成整個事情之前,其所有的改動,都不應該對其他人可見和影響。而在事務結束之後,其一切的改動,都必須“全部”“立刻”對其他的人(或機器)可見。

然後,人們爲了描述這一運作,使用了四個詞彙,這也是很多面試的同學們折戟沉沙之處。J 不過這個以前我也不會,後來發現,理解了以後,確實有點用,所以這裏也費一些筆墨吧。

原子性(Atomicity):也就是說,一組操作,要不就都成功,要不就都失敗。不存在中間狀態。

一致性(Consistency):一致性,也就是說,這個事務在提交或回滾的時候,對其他人(或機器)來說,數據的狀態是同一的,不會出現中間的狀態。最理想的狀態下,就是說,數據提交後,所有的更改立刻同時生效,可惜,在計算機領域,這個做不到。。因爲cpu運算,磁盤寫入,內存寫入,都是要時間的,內部一定是個順序化的過程,所以不可能做到絕對的立刻同時生效。

所以一致性一般來說指代的是邏輯上的同時生效,比如,我要改A,B兩行數據,那麼,最簡單的一致性保證就是,對A,B加鎖,改A,B,然後對A,B解鎖。

這樣下一個人讀到的一定是A,B的最新值啦。

(但這塊有很多種解釋,一般來說這是個最不明確的詞彙)。

 

隔離性(Isolation):隔離,這是面試最容易掛的一個問題,其實我認爲不怪我們,而是因爲本身這個隔離性,是依託鎖來進行設計的。

我們所知道的鎖,主要有以下幾種,1.讀寫鎖,2. 排他鎖

那麼這四種級別其實就和這兩種鎖的實現有關,之所以要定義四個級別,其實原因也是因爲,鎖的範圍越大,並行效率越低。而範圍越小,那麼並行的效率就越高。

讀未提交: 其實就是什麼鎖也沒有,所以數據的中間狀態,是可能被其他人讀到的。

讀已提交:就是讀寫鎖實現,讀鎖在查詢之後會被釋放掉,所以這樣其他人可能會更改那些被釋放了讀鎖的數據,這樣當前事務再去讀取的時候,就可能讀取到被別人修改過的數據了,所以一個人在事務中讀取到的某個數據,可能下次讀取就變成別的數據啦。這就是不可重複讀的意思。。

可重複讀:也是個讀寫鎖實現,讀鎖會阻塞其他人(或機器)的寫,於是,只要是事務中讀取到得數據,都被加了鎖,其他人沒辦法改他們,於是就實現了可重複讀咯。

最後是序列化,就是所有都順序,一個大鎖全部鎖住J

 

持久性(Durability):持久性就是,事務執行後,就丟不了了,就算是整個中國被淹了,機器都沒了,數據也不應該丟掉(不過基本做不到這個,也就是一個機器掛了不會丟數據而已。。)所有機房沒了那數據也就沒了。。

對於這塊,給大家一些參考資料:

http://zh.wikipedia.org/wiki/%E4%BA%8B%E5%8B%99%E9%9A%94%E9%9B%A2

http://www.cnblogs.com/wangiqngpei557/archive/2011/11/19/2255132.html

 

這些講的不錯,淺顯易懂是我的最愛.


好啦,爲了保證我寫的東西不會被"qing"這個大怪獸再次吃掉。。我先發這些。

在下一個章節,我們繼續在事務這個領域徜徉,給大家介紹一下,在單機上面,事務是如何進行的。


http://qing.weibo.com/1765738567/693f08473300067j.html 下一篇 單機事務



Nov26

海量存儲系列之四


單機事務:

其實在上面介紹ACID的時候

我們已經提到了一種最簡單的實現方式,就是鎖的實現方式。

從原理來看,事務是個變態而複雜的事情。其實如果是序列化的話呢,那麼實現起來一定是非常簡單的。

但問題就在於,這樣性能實在比較低,於是,就有了非常多的方案,爲了能哪怕減少一個地方的鎖,或者降低一個地方的鎖的級別,就付出大量的時間和代碼加以實現。

那麼,讓我們以崇敬的心情,去拜讀一下他們的勞動成果吧~

 --------------------------------------------------------------------------------

 

在上一篇中,我們談了事務管理的四個核心要素,其中有兩個要素是和性能緊密相關的,其實也就是需要涉及到鎖的,一個是隔離性,一個是一致性。

一致性問題和隔離性問題,我們都可以歸結爲一個問題,他們都用於定義,什麼時候數據可被共享,什麼時候數據必須被獨佔。而這些決策,就最終決定了整個數據庫系統的並行度,也就直接的決定了多線程併發時的性能指標。

 

如果要改一大批數據,又必須保證這些數據要麼都出現,要麼都不出現,這時候就有個難題了:因爲這些數據不可能在同一個時間被選出,更不可能在同一個時間被更改。

於是就必須想個辦法來假裝達到這個狀態,於是我們就需要一種方法,使得針對不同數據的更改,不同人(或機器)不打架。而如果出現對相同數據的更改,則要將更新進行排隊。

這個排隊可供選擇的方法,就我知道的有:1,排他鎖。2. 讀寫鎖。3. Copy on write(MVCC) .4. 隊列。5. 內存事務。這些方式。

 

從性能來說,排他鎖最慢,而讀寫因爲讀可以併發,所以效率稍高,但寫和讀不能同時進行。3. Copy on write(MVCC) 則讀取和寫入之間可以互相不影響,所以效率更高。隊列這種方式,內存時效果很好,省去中斷上下文切換的時間。內存事務,目前還在研究階段,具備很大潛力的東西。

 

排他鎖,隊列和內存事務,在目前的數據庫中用的相對較少,我們就不在這裏說了。

這裏主要說兩種實現,一種是讀寫鎖,一種是MVCC.

 

先說讀寫鎖,也是隔離性中“讀已提交,可重複讀”兩種實現中最重要的底層實現方式。

簡單來說,就是如果一個人在事務中,那麼他所有寫過的數據,所有讀過的數據,都給他來個鎖,讓其他小樣兒都只能等在外面,直到數據庫能確定所有更改已經全部完成了,沒有剩下什麼半拉子狀態的時候,就解開所有的鎖,讓其他人可以讀取和寫入。Hoho,就是這個了。

 

那麼MVCC呢,其實是對讀寫鎖的一個改進,有一批大牛們,說你們這讀寫鎖,寫的時候不能讀,讀的時候不能寫,並行度太低了,我要做個更牛B的,寫不阻塞讀,讀不阻塞寫的東西來超越你們。

於是他們想起了copy-on-write.鼓搗了個MVCC數據庫出來。。。

題外話,現在的甲骨文,之所以能在數據庫領域保持優勢地位,有個很重要的原因也是因爲他們是很早就在商業數據庫系統中實現了MVCC的數據寫入引擎。

所以他們的Thomas Kyte 技術副總裁也就有了在他們的最牛逼的oracle專家編程裏面有了吹噓的資本 XD .

 

這裏我們要着重的介紹一下MVCC,因爲這東西看起來非常的精妙而美麗。。。現在大量的分佈式類存儲中,也都在借鑑這套模式中的很多部分來增加自己的並行度,以提升性能。比如megaStore.比如percolator。

我們在讀寫鎖的實現中,提到了寫讀的相互阻塞問題,MVCC則使用copy-on-write來解決這個問題。

如果一個人在事務中,會先申請一個事務ID,這個ID是自增的,每個事務都有他自己的唯一的ID,那麼他寫過的數據,都會被轉變爲一次帶有當前事務ID的新數據,在讀取的時候,則只會讀取小於等於自己事務ID的數據。這樣實現的東東,語義上來說,與可重複讀就一樣了。而如果讀小於等於全局ID的數據,那麼這樣的實現,就是讀已提交了。

 

一般來說,MVCC只實現了四個級別中的第二級和第三級,其他的就沒有啦,不過這兩個是我們最常見的級別。所以也就大家同樂,同樂了~

 

有了這個東西,我們的一致性也就很容易保證了,因爲一個事物和他對應的版本號對應,又有更改後的數據和更改前的數據,如果要提交,那麼就只需要很簡單的讓更改後的數據生效可見即可,這樣我們可以將大量的更新中要做的事情,都在事務過程中進行,這樣,比原有的基於讀寫鎖的必須在commit時候一起做掉來說,commit這個操作就輕量化了很多,於是,就可以支持更多的人(或機器)持有事務狀態了。

 

很美妙吧?

我一致認爲這是oracle當年的核心競爭力,不過現在基本上是個數據庫就用了這一套,我們就不在多嘴啦~

 

解決了一致性和隔離性,剩下的是原子性和持久性,原子性麼,一般來說就是要麼都成功,也就是新版本數據都讓他生效,要麼就都失敗,也就是讓和自己事務ID對應的所有修改都無效即可。也很好就解決掉了。持久性。這個就是後面我們要在寫入模型裏面介紹的東西了,基本上來說就是寫磁盤策略的事情。

 

到這裏,我們單機ACID的實現大概思路,就給大家介紹過了。下一個章節,我們還要用很多的文字,來向大家介紹在分佈式場景中我們面臨的事務的難題,以及“我所知道的”百花齊放的解決方法。

http://qing.weibo.com/1765738567/693f0847330006ao.html?rnd=0.6134993201121688




Nov27

海量存儲系列之五

http://qing.weibo.com/1765738567/693f08473300067j.html 上一篇


在上一章節,我們一起瀏覽瞭如何進行單機事務操作。下面我們來看一下分佈式場景中我們碰到的問題吧。

 

需要說明的一點是,這裏涉及到的權衡點非常的多。就我短短的工作經驗裏面,也只是能夠簡單的涉獵一部分,因爲在事務這個領域,目前大家都在嘗試提出各種各樣的不同的方法,而在taobao,我們目前也沒有完美的解決這個問題,更多的是在權衡,在金錢和開發成本之間,做出選擇。

 

那麼,我們就先從問題開始,來看一下原來的事務出了什麼問題。

在事務中,有ACID四種屬性。(見上篇文章)

 

在分佈式場景中,我們看引入了什麼因素,導致了什麼樣的新問題:

1.      延遲因素:光是我們所知最快的信息載體了,各位可能都會從潛意識裏面認爲光傳輸信息不就是一眨眼的事情而已。那我們做個簡單的計算吧(感謝@淘寶叔度,第一次在分享中讓我對這個問題有了個數值化的印象。):

北京到杭州,往返距離2600km ,光在真空中的傳輸速度是30wkm/s。在玻璃中的速度是真空的2/3。算下來,最小的請求和響應,之間的延遲就有13ms。並且,因爲光在管子裏走的不是直線,又有信號干擾等問題,一般來說要乘以2~3倍的因子值。

所以一次最小的請求和響應,時間就差不多有30ms左右了。

再想想TCP的時間窗口的移動策略,相信大家都能意識到,實際上延遲是不可忽略的,尤其在傳輸較多數據的時候,延遲是個重要的因素,不能不加以考慮。

並且,延遲 不是 帶寬,帶寬可以隨便增加,千兆網卡換成萬兆,但延遲卻很難降低。而我們最需要的,是帶寬,更是延遲的降低。因爲他直接決定了我們的可用性。

2.      災備因素:單機的情況下,人們一般不會去追求說一個機器物理上被水沖走了的時候,我的數據要保證不丟(因爲沒辦法的嘛。。)。但在分佈式場景下,這種追求就成爲了可能,而互聯網行業,對這類需求更是非常看重,恨不能所有的機器都必須是冗餘的,可隨意替換的。這樣才能保證7*24小時的正常服務。這無疑增加了複雜度的因素。

3.      Scale out的問題: 單機總是有瓶頸的,於是,人們的追求就一定是:不管任何一種角色的機器,都應該可以通過簡單的增加新機器的方式來提升整個集羣中任何一個角色的性能,容量等指標。這也是互聯網行業的不懈追求。

4.      性能:更快的響應速度,更低的延遲,就是更好的用戶體驗。(所以google用了個“可憐”到家的簡單input框來提升用戶體驗,笑)。

 

說道這裏,大概大家都應該對在分佈式場景下的廣大人民羣衆的目標有了一個粗略的認識了。

那麼我們來看一下原有ACID的問題吧。

 

在上次的章節中,我們也提到了ACID中,A和D相對的,比較容易達到。但C和I都涉及到鎖實現,也就和性能緊密的相關了。

然後,人們就開始了糾結,發掘這個C和I,似乎不是那麼容易了。

上次,我們談到,目前主流的實現一次更新大量數據的時候,不同人(或機器)修改數據相互之間不會打架的方法有以下幾種:

1.      排他鎖

2.      讀寫鎖

3.      Copy-on-write

4.      隊列

5.      內存事務

 

 

排他鎖和讀寫鎖,本身都是鎖的實現,單機的鎖實現,相對而言是非常簡單的事情,但如果涉及到分佈式鎖,那麼消耗就很高了,原因是,鎖要在兩邊都達到一致,需要多次機器之間的交互過程,這個交互的過程,再考慮到延遲的因素,基本上一次加鎖請求就要100~200+毫秒的時間了,那麼去鎖又要這樣的時間。而要知道,我們在單機做內存鎖操作,最慢也不過10毫秒。。

於是,有一批人就說了,既然這麼難,我們不做了!~來個理論證明他很難就行了~。於是就有了CAP和BASE.

所謂CAP,我個人的理解是描述了一種: 在數據存了多份的前提下,一致性和響應時間,讀寫可用性不可兼得的“現象”而已。

在我這裏來看CAP的證明過程就是個扯淡的玩意兒,他只是描述了一種現象而已。原因還是網絡延遲,因爲延遲,所以如果要做到數據同時出現或消失,那麼按照鎖的方式原來可能只需要10ms以內完成的操作,現在要200~400ms才能完成,那自然不能接受了。所謂CAP就是這個現象的英文簡稱,笑。

 

BASE呢,這個理論似乎更老,其實也是個現象,就是基本可用,軟狀態,最終一致的簡稱,也沒個證明,其實就是告訴咱:要權衡一下,原來的ACID不太容易實現啦,我們得適當放棄一些啦。但請各位注意,ACID實際上是能夠指導我們在什麼情況下做什麼樣的事情能夠獲取什麼樣的結果的。而BASE則不行,這也說明BASE不是個經典的理論。

 

好啦。廢話了這麼多,其實就是想說,分佈式場景沒有銀彈啦,你們自己權衡去吧。我們大牛們救不了你們啦的意思。。

 

既然大牛救不了咱,咱就只能自救了。。。

 

好,好的文章就要在關鍵的地方恰然而止,留下懸念,我們也就在這裏留下點懸念吧。

在這篇中,主要是想給大家介紹一下,目前在分佈式場景中,事務碰到了什麼問題,出現這些問題的原因是什麼。

 

在下一篇中,我將嘗試從原理的角度,去分析目前的幾類常見的在分佈式場景中完成原有事務需求的方法。敬請期待 : ) 


http://qing.weibo.com/1765738567/693f0847330007ay.html下一篇



Dec07

海量存儲系列之六


抱歉大家,間隔有點久,因爲這一章要比較細緻的總結,所以有些時間耽誤。上次我們講到,單機事務個我們面臨的問題,下面我們來說一些我所知的解決的方法。

 

在我開始做淘寶數據層的時候,被問得最多的無非也就是:如何做事務,如何做join.至今仍然如此,我一般都會簡單而明確的跟對方說:沒有高效的實現方法。

 

雖然沒有高效的實現,但實現還是有的。作爲引子,我們先來介紹一下這種實現的方式。

 

我們仍然以上一次講到的bob和smith爲例子來說明好了。

開始的時候。Bob要給smith100塊,那麼實際上事務中要做的事情是

事務開始時查詢bob有多少錢。如果有足夠多的錢讓bob的賬戶 -100 ,然後給smith 的賬戶+100 。最後事務結束。

 

如果這個事情在單機,那麼事情可以使用鎖的方式加以解決。

但如果bob在一臺機器,smith在另外一臺機器,我們應該怎麼做呢?

 

第一種最常被人想起的方法,就是兩段提交協議。

兩段提交協議從原理上來說是非常簡單的一套協議。

Prepare(bob-100) at 機器A->prepare (smith+100) at 機器b ->commit(bob) ->commit(smith)

事務結束。

 

兩段提交的核心,是在prepare的階段,會對所有該操作所影響的數據加鎖,這樣就可以阻止其他人(或機器)對他的訪問。題外話,問個問題: )如果這時有其他節點,用相反的方向,進行更新,也就是先更新smith,然後更新bob.會有可能發生什麼事情呢?

兩段提交協議是被我們在大部分場景下放棄的一個模型,原因主要是因爲

1.      Tm本身需要記錄事務進行的過程,log要保證安全和可信,性能非常低。

2.      鎖的利用率和並行性較低。

3.      網絡開銷較大

4.      可見性要求實際上就等於讓快的操作等慢的。

 

所以從性能角度來說,這類需求不多也不常見。

既然這樣的模型不行,有沒有其他模型可以使用呢?

 

有的。

 

在事務的過程中,細心的讀者不難發現,實際上事務中並不需要這麼強的一致可見性。

Bob是需要強一致的,因爲他的操作仰賴於他有多少錢,如果他的錢不夠100,那麼是不能讓他的賬戶變爲負數的。但smith卻不需要,smith不需要判斷他的賬戶有多少錢,只需要把錢加到他的賬戶裏,不少給他,到賬時間儘可能短就可以。

Smith不需要chech賬戶的錢數,這個前提非常重要,這也是我們能使用最終一致性的關鍵因素。

 

下面,我們來看一下另外的選擇吧。

Bob的賬號在機器A上,smith的賬號在機器b上。

首先,我們在機器A上做以下操作:

1.      本地事務開始

2.      讀取bob的賬戶

3.      判斷是否有充足餘額

4.      更新bob的賬號,將bob的錢減少100

5.      將需要給smith加100塊這個操作,以事務的形式插入到同機(A)的一張log表中,並自動生成一個唯一的transactionID。

6.      事務關閉

 

然後,異步的發送一個通知,給一個消費者。

消費者接到通知後,從bob的機器上讀取到需要給smith+100這個操作,以及該操作所對應的transactionID。

然後,按照如下方法進行運作

1.      查看在去重表內是否有對應的transactionID.如果沒有,則

2.      開啓本地事務

3.      將smith的賬戶+100

4.      將transactionID 插入去重表

5.      事務結束

 

這樣,我們也可以完成一個交易的核心流程了。在交易類過程中的大量事務操作,都是以這樣的方式完成的。

 

下面,我們針對上面的這個流程的一些抉擇的點進行一些探討。

 

首先,是bob這個機器,這裏涉及第一個抉擇點。

如果bob是個消費大戶,短時間內進行了大量購買,那麼可能會造成的問題是,bob所在的那個機器會成爲熱點,如果在某個突發的情況下,某個賬戶突然成爲熱點,那麼這些有狀態的數據很難快速的反應並加以處理,會造成事務數在某個單節點大量堆積。造成掛掉。

 

可能的解決方法是:

1.      利用兩段提交協議來讓原來的” 將需要給smith加100塊這個操作,以事務的形式插入到同機(A)的一張log表中,並自動生成一個唯一的transactionID”這步操作放在另外的一臺機器上進行。

這樣做的的好處是,無論bob怎麼是熱點,都可以通過水平的加log機器的方式來防止這種熱點的產生。

壞處則有:

         1方案複雜度高

         2額外的網絡開銷

         3消息基於網絡發送後,會可能得到三個可能的反饋:1. 成功 2. 失敗 3. 無反饋。最麻煩的就是這個無反饋,他可能成功,也可能失敗。所以是不確定的狀態,需要進行事務的兩邊進行第二次確認,來確保這個事務的參與方是否都做了該做的事情,如果有一方做了類似commit的操作,那麼另外的一方應該commit.如果兩方都沒做commit操作,那麼應該回滾。

2.      讓bob的庫餘量更高,並按照訪問壓力進行數據的切分,按照熱度進行數據劃分,放棄原有的簡單取mod的策略。來兼容這種不均勻特性。

 

其次,如果有80個系統都關注着smith加了100這個操作的log,要做對應的處理(比如一些人要針對這個加錢操作做個打款短信推送,有些要做個數據分析等等),那麼這裏就有另外一個問題,這些系統對bob所在的庫的讀取就會讓該機器成爲悲劇的存在。

所以,可以考慮的方式是,增加一個隊列,使用,推,拉,或推拉結合的方式將smith加100這個操作加以分發。這樣就可以減輕主機的壓力。

         壞處則是:

         1方案進一步複雜

         2如何保證log到數據分發服務器之間的數據同步是安全的和準確的?

         3如何保證分發服務器的可靠和冗餘?

         4如何保證寫入分發服務器的數據的安全和可靠?

 

再次,smith這邊也有問題,爲什麼要使用一張去重表呢?其實是因爲,在發送端,也就是隊列將數據發送到目標機器後,也可能從目標機獲取到三種不同的反饋,一類是成功(這個佔了大多數)。一類是失敗。還有一類是。。。沒反饋。

 

當然,最麻煩的還是這個沒反饋的情況,沒人知道這時候到底對方是做成功了呢?還是沒做成功,爲了保證最大的吞吐量,又不能其他人都不做事兒了,就等對方的反饋。所以這裏就有另外的權衡了。

 

一般的模型有兩類,一類是用分佈式事務來完成。

一類是使用努力送達的模型,說叫努力送達,顧名思義,就是隻有得到成功的反饋,才停止投遞,而其他時候則重複投遞消息,直到對方反饋成功爲止。

 

兩種模型比較,顯然應該追求速度而放棄方便性,於是我們主要來說說這個努力送達以後所帶來的影響。

影響一 : 會有重複的投遞,也就是說,這個消息可能會投多次,這對於update set version=version+1 這類的操作來說,是個比較毀滅性的打擊。

影響二:如果需要重複投遞的消息過多,會導致log分發的機器消耗大量資源來進行重複投遞。這會影響server的穩定性

影響三:如果大量堆積消息,那麼會造成消息的嚴重delay。smith發現自己在1個月後收到了bob的錢,你說他會不會去K咱一頓: ) .

 

最後,額外記的這兩次log其實在某些場景下也是可以省去的。

 

以上,就是我在嘗試還原淘寶的消息和事務系統時所能大概想到的一些非常需要權衡和注意的問題點。

 

小小總結一下,整個問題的核心其實是冪等,說白了就是要能夠理解數據基於網絡的同步過程中,無反饋是一個經常發生的現象,在這種現象中,重複投遞比傻傻等待要有效率的多。所以,重複作爲一個side affect也就被默認的存在於系統中,所有的工程師都需要認識到這個問題的客觀存在,並採取方法去解決之。

 

在基於網絡的數據同步過程中,如果需要最大化性能,那麼,一致性是第一個被放棄的。然後數據和消息不會出現重複,是第二個被放棄指標。

 

 

使用這種模型,我們可以放棄原來快得等慢的的模式,讓整體的吞吐量和性能不會受制於鎖的限制,所以淘寶和支付寶才能夠支持如此大的交易量。完成大量交易訂單。

 

http://qing.weibo.com/1765738567/693f0847330007ki.html 下一篇



Dec10

海量存儲系列之七

 http://qing.weibo.com/1765738567/693f0847330007ay.html 上一篇

在上一個章節,我們闡述了分佈式場景下,事務的問題和一些可能的處理方式後,我們來到了下一章節

 

Key-value存儲

 

這一章,我們將進入k-v場景,其實,在大部分場景下,如果某個產品宣稱自己的寫讀tps超過其他存儲n倍,一般來說都是從k-v這個角度入手進行優化的,主要入手的點是樹的數據結構優化和鎖的細化,一般都能在一些特定的場景獲得5-10倍的性能提升。由此可見key-value存儲對於整個數據存儲模型是多麼的重要。

 

好吧,那麼我們來進入這個章節,用最簡單和淺顯的話語,闡述這些看起來很高深的理論吧 : )

 

在未來的幾篇中,我們將大概的介紹和分析如下幾種比較有特點的數據結構,並探討其優勢劣勢以及適用的場景。

 

 

海量存儲系列之七

讓我們先從映射入手吧,所謂映射,就是按照key找到value的過程,這個過程幾乎就是我們處理數據的最核心數據結構了。

 

如何能夠根據一個key找到對應的value呢?

一類是hash map.最簡單的實現就是算一個key數據的hashCode.然後按照桶的大小取mod.塞到其中的一個桶裏面去。如果出現衝突怎麼辦呢?append到這個桶內鏈表的尾部就行了。

 

還有一類呢,我們可以抽象的認爲是一個有序結構。之所以把它歸類到有序結構原因也很簡單,因爲只有有序才能做二分查找。。。舉些有序結構的例子吧: 1. 數組 2. 各類平衡二叉樹 3. B-樹類族 4. 鏈表

 

這些數據結構如果想進行快速查找,都需要先讓他們有序。然後再去做log2N的二分查找找到對應的key。

 

從原教旨上來說,這就是我們要用的key-value的主要結構了。

那麼,hash和有序結構,他們之間有什麼樣的差別呢?我們來進行一下簡單的比較

 

海量存儲系列之七
 

基本上來說,核心區別就是上面的這點,hash單次查詢效率較高,但爲了保證O(1)效率,對空間也有一定要求。而有序結構,查詢效率基本是O(log2N)這個級別。但有序結構可以支持範圍查找,而hash則很難支持。

 

所以,一般來說我們主要在使用的是有序結構來進行索引構建,因爲經常需要查詢範圍。

不過,所有數據庫幾乎都支持hash索引,如果你的查詢基本都是單值的,那麼可以找一找穩定的hash索引,他們能從一定程度上提升查詢的效率。

 

在這裏,我們主要討論有序結構,對於數據庫或nosql來說,有序結構主要就是指b-tree或b-tree變種。那麼我們先來介紹一下什麼叫b-tree作爲討論磁盤結構的入門吧。

 

先上圖(copy的,這是個b-tree。版權方請找我)

 

 

海量存儲系列之七

 

首先進行詞彙科普:b-tree只有兩類,一類叫b-tree,就是btree,還有一類是b+tree,但b-tree不是b”減”樹的意思。這個大家不要再跟當年的我犯同樣的錯誤喲 :__0

 

那麼b樹的核心是幾個關鍵詞

1.      樹高:一般來說,樹的高度比較低。三到五層

2.      數組:每一個node,都是一個“數組”,數組是很關鍵的決定性因素,我們後面寫入和讀取分析的時候會講到。

沒了呵呵

 

然後我們進行一下讀取和寫入的模擬。

讀取來說:如果我要查找28這個數據對應的value是多少,路徑大概是:首先走root節點,取出root node後,對該數組進行二分查找,發現35>28>17,所以進入branch節點中的第二個節點,取出該節點後再進行二分查找。發現30>28>26,所以進入branch節點的p2 value,取出該節點,對該三個值的數組進行二分查找,從而定位到28這個數據的對應value。

 

而寫入刪除則涉及到分裂和合並這兩個btree最重要的操作,比如,要寫入37,那麼會先找到36所應該被插入的數組[36,60]這個數組,然後判斷其是否有空,如果有空,則對該數組進行重新排序。而如果沒有空,則必須要進行分裂。分裂的緣由是因爲組成b-tree的每一個node,都是一個數組,數組最大的特性是,數組內元素個數是固定的。因此必須要把原有已經滿掉的數組裏面的一半的數據拿出來,放到新的一個新建立的空數組中,然後把要寫入的數據寫入到老或新的這兩個數組裏面的一個裏面去。

【這裏要留個問題給大家了,我想問一下,爲什麼b-tree要使用數組來存儲數據呢?爲什麼不選擇鏈表等結構呢?】

對於上面的這個小的b-tree sample裏面呢,因爲數組[35,60],數組已經滿了,所以要進行分裂。於是數組在插入了新值以後,變成了兩個[35,36] 和[60] ,然後再改變父節點的指針並依次傳導上去即可。

 

當出現刪除的時候,會可能需要進行合併的工作,也就是寫入這個操作的反向過程。在一些場景中,因爲不斷地插入新的id,刪除老的id,會造成b-tree的右傾,這時候需要有後臺進程對這種傾向進行不斷地調整。

 

基本上,這就是b-tree的運轉過程了。

 

B+tree

海量存儲系列之七

B+tree 其實就是在原有b-tree的基礎上。增加兩條新的規則

1.      Branch節點不能直接查到數據後返回,所有數據必須讀穿或寫穿到leaf節點後才能返回成功

2.      子葉節點的最後一個元素是到下一個leaf節點的指針。

這樣做的原因是,更方便做範圍查詢,在b+樹種,如果要查詢20~56.只需要找到20這個起始節點,然後順序遍歷,不再需要不斷重複的訪問branch和root節點了。

 

 

發現每一種數據結構都需要去進行簡介才能夠比較方便的瞭解到他們的特性,所以在後續的章節還會介紹幾種有代表性的樹的結構都會針對性的加以介紹。


http://qing.weibo.com/1765738567/693f0847330008ii.html next





Dec18

海量存儲系列之八

首先來回答一個問題:爲什麼在磁盤中要使用b+樹來進行文件存儲呢?

原因還是因爲樹的高度低得緣故,磁盤本身是一個順序讀寫快,隨機讀寫慢的系統,那麼如果想高效的從磁盤中找到數據,勢必需要滿足一個最重要的條件:減少尋道次數。

我們以平衡樹爲例進行對比,就會發現問題所在了:


先上個圖

海量存儲系列之八


 

這是個平衡樹,可以看到基本上一個元素下只有兩個子葉節點

 

 

抽象的來看,樹想要達成有效查找,勢必需要維持如下一種結構:

樹的子葉節點中,左子樹一定小於等於當前節點,而當前節點的右子樹則一定大於當前節點。只有這樣,才能夠維持全局有序,才能夠進行查詢。

 

這也就決定了只有取得某一個子葉節點後,才能夠根據這個節點知道他的子樹的具體的值情況。這點非常之重要,因爲二叉平衡樹,只有兩個子葉節點,所以如果想找到某個數據,他必須重複更多次“拿到一個節點的兩個子節點,判斷大小,再從其中一個子節點取出他的兩個子節點,判斷大小。”這一過程。

 

這個過程重複的次數,就是樹的高度。那麼既然每個子樹只有兩個節點,那麼N個數據的樹的高度也就很容易可以算出了。

 

平衡二叉樹這種結構的好處是,沒有空間浪費,不會存在空餘的空間,但壞處是需要取出多個節點,且無法預測下一個節點的位置。這種取出的操作,在內存內進行的時候,速度很快,但如果到磁盤,那麼就意味着大量隨機尋道。基本磁盤就被查死了。

 

而b樹,因爲其構建過程中引入了有序數組,從而有效的降低了樹的高度,一次取出一個連續的數組,這個操作在磁盤上比取出與數組相同數量的離散數據,要便宜的多。因此磁盤上基本都是b樹結構。

 

不過,b樹結構也不是完美的,與二叉樹相比,他會耗費更多的空間。在最惡劣的情況下,要有幾乎是元數據兩倍的格子才能裝得下整個數據集(當樹的所有節點都進行了分裂後)。

 

 

以上,我們就對二叉樹和b樹進行了簡要的分析,當然裏面還有非常多的知識我這裏沒有提到,我希望我的這個系列能夠成爲讓大家入門的材料,如果感興趣可以知道從哪裏着手即可。如果您通過我的文章發現對這些原來枯燥的數據結構有了興趣,那麼我的目標就達到了: )

 

在這章中,我們還將對b數的問題進行一下剖析,然後給出幾個解決的方向


其實toku DB的網站上有個非常不錯的對b樹問題的說明,我在這裏就再次侵權一下,將他們的圖作爲說明b樹問題的圖譜吧,因爲真的非常清晰。

http://tokutek.com/downloads/mysqluc-2010-fractal-trees.pdf

海量存儲系列之八

 

B樹在插入的時候,如果是最後一個node,那麼速度非常快,因爲是順序寫。

海量存儲系列之八

 

但如果有更新插入刪除等綜合寫入,最後因爲需要循環利用磁盤塊,所以會出現較多的隨機io.大量時間消耗在磁盤尋道時間上。

 

海量存儲系列之八

 

如果是一個運行時間很長的b樹,那麼幾乎所有的請求,都是隨機io。因爲磁盤塊本身已經不再連續,很難保證可以順序讀取。

 

以上就是b樹在磁盤結構中最大的問題了。

 

那麼如何能夠解決這個問題呢?

目前主流的思路有以下幾種

1.      放棄部分讀性能,使用更加面向順序寫的樹的結構來提升寫性能。

這個類別裏面,從數據結構來說,就我所知並比較流行的是兩類,

一類是COLA(Cache-Oblivious Look ahead Array)(代表應用自然是tokuDB)。

一類是LSM tree(Log-structured merge Tree)或SSTABLE

(代表的數據集是cassandra,hbase,bdb java editon,levelDB etc.).

2.      使用ssd,讓尋道成爲往事。

 

我們在這個系列裏,主要還是講LSM tree吧,因爲這個東西幾乎要一桶漿糊了。幾乎所有的nosql都在使用,然後利用這個宣稱自己比MySQL的innodb快多少多少倍。。我對此表示比較無語。因爲nosql本身似乎應該是以省去解析和事務鎖的方式來提升效能。怎麼最後卻改了底層數據結構,然後宣稱這是nosql比mysql快的原因呢?

畢竟Mysql又不是不能掛接LSM tree的引擎。。。

 

好吧,牢騷我不多說,畢竟還是要感謝nosql運動,讓數據庫團隊都重新審視了一下數據庫這個產品的本身。

 

那麼下面,我們就來介紹一下LSM Tree的核心思想吧。

 

首先來分析一下爲什麼b+樹會慢。

 

從原理來說,b+樹在查詢過程中應該是不會慢的,但如果數據插入比較無序的時候,比如先插入5 然後10000然後3然後800 這樣跨度很大的數據的時候,就需要先“找到這個數據應該被插入的位置”,然後插入數據。這個查找到位置的過程,如果非常離散,那麼就意味着每次查找的時候,他的子葉節點都不在內存中,這時候就必須使用磁盤尋道時間來進行查找了。更新基本與插入是相同的

 

海量存儲系列之八

 

那麼,LSM Tree採取了什麼樣的方式來優化這個問題呢?

簡單來說,就是放棄磁盤讀性能來換取寫的順序性。

乍一看,似乎會認爲讀應該是大部分系統最應該保證的特性,所以用讀換寫似乎不是個好買賣。但別急,聽我分析之。

1.      內存的速度遠超磁盤,1000倍以上。而讀取的性能提升,主要還是依靠內存命中率而非磁盤讀的次數

2.      寫入不佔用磁盤的io,讀取就能獲取更長時間的磁盤io使用權,從而也可以提升讀取效率。

因此,雖然SSTable降低了了讀的性能,但如果數據的讀取命中率有保障的前提下,因爲讀取能夠獲得更多的磁盤io機會,因此讀取性能基本沒有降低,甚至還會有提升。

而寫入的性能則會獲得較大幅度的提升,基本上是5~10倍左右。

 

下面來看一下細節

其實從本質來說,k-v存儲要解決的問題就是這麼一個:儘可能快得寫入,以及儘可能快的讀取。

讓我從寫入最快的極端開始說起,闡述一下k-v存儲的核心之一—樹這個組件吧。

 

我們假設要寫入一個1000個節點的key是隨機數的數據。

 

對磁盤來說,最快的寫入方式一定是順序的將每一次寫入都直接寫入到磁盤中即可。

但這樣帶來的問題是,我沒辦法查詢,因爲每次查詢一個值都需要遍歷整個數據才能找到,這個讀性能就太悲劇了。。

 

那麼如果我想獲取磁盤讀性能最高,應該怎麼做呢?把數據全部排序就行了,b樹就是這樣的結構。

 

那麼,b樹的寫太爛了,我需要提升寫,可以放棄部分磁盤讀性能,怎麼辦呢?

 

簡單,那就弄很多個小的有序結構,比如每m個數據,在內存裏排序一次,下面100個數據,再排序一次……這樣依次做下去,我就可以獲得N/m個有序的小的有序結構。

 

在查詢的時候,因爲不知道這個數據到底是在哪裏,所以就從最新的一個小的有序結構裏做二分查找,找得到就返回,找不到就繼續找下一個小有序結構,一直到找到爲止。

 

很容易可以看出,這樣的模式,讀取的時間複雜度是(N/m)*log2N 。讀取效率是會下降的。

這就是最本來意義上的LSM tree的思路。

那麼這樣做,性能還是比較慢的,於是需要再做些事情來提升,怎麼做纔好呢?

 

於是引入了以下的幾個東西來改進它

1.      Bloom filter : 就是個帶隨即概率的bitmap,可以快速的告訴你,某一個小的有序結構裏有沒有指定的那個數據的。於是我就可以不用二分查找,而只需簡單的計算幾次就能知道數據是否在某個小集合裏啦。效率得到了提升,但付出的是空間代價。

2.      小樹合併爲大樹: 也就是大家經常看到的compact的過程,因爲小樹他性能有問題,所以要有個進程不斷地將小樹合併到大樹上,這樣大部分的老數據查詢也可以直接使用log2N的方式找到,不需要再進行(N/m)*log2n的查詢了。

 

這就是LSMTree的核心思路和優化方式。

不過,LSMTree也有個隱含的條件,就是他實現數據庫的insert語義時性能不會很高,原因是,insert的含義是: 事務中,先查找該插入的數據,如果存在,則拋出異常,如果不存在則寫入。這個“查找”的過程,會拖慢整個寫入。

 

 

這樣,我們就又介紹了一種k-v寫入的模型啦。在下一次,我們將再去看看另外一種使用了類似思路,但方法完全不同的b樹優化方式 COLA樹系。敬請期待 ~

 

 

 

 http://qing.weibo.com/1765738567/693f0847330008x6.html 下一篇




Dec22

海量存儲系列之九

http://qing.weibo.com/1765738567/693f0847330008ii.html  上一篇

終於來到了COLA樹系,這套東西目前來看呢,確實不如LSM火,不過作爲可選方案,也是個值得了解的嘗試,不過這塊因爲只有一組MIT的人搞了個東西出來,所以其實真正的方案也語焉不詳的。

 

從性能來說,tokuDB的寫入性能很高,但更新似乎不是很給力,查詢較好,佔用較少的內存。

 

http://www.mysqlperformanceblog.com/2009/04/28/detailed-review-of-tokutek-storage-engine/

這裏有一些性能上的指標和分析性文字。確實看起來很心動,不過這東西只適合磁盤結構,到了SSD似乎就掛了。原因不詳,因爲沒有實際的看過他們的代碼,所以一切都是推測,如果有問題,請告知我。

 

 

先說原理,上ppt http://tokutek.com/presentations/bender-Scalperf-9-09.pdf,簡單來說,就是一幫MIT的小子們,分析了一下爲什麼磁盤寫性能這麼慢,讀的性能也這麼慢,然後一拍腦袋,說:“哎呀,我知道了,對於兩級的存儲(比如磁盤對應內存,或內存對於緩存,有兩個屬性是會對整個查詢和寫入造成影響的,一個是容量空間小但速度更快的存儲的size,另外一個則是一次傳輸的block的size.而我們要做的事情,就是儘可能讓每次的操作傳輸儘可能少的數據塊。

 

傳輸的越少,那麼查詢的性能就越好。

 

 

進而,有人提出了更多種的解決方案。

•B-tree [Bayer, McCreight 72]

• cache-oblivious B-tree [Bender, Demaine, Farach-Colton 00]

• buffer tree [Arge 95]

• buffered-repositorytree[Buchsbaum,Goldwasser,Venkatasubramanian,Westbrook 00]

• Bε

 tree[Brodal, Fagerberg 03]

• log-structured merge tree [O'Neil, Cheng, Gawlick, O'Neil 96]

• string B-tree [Ferragina, Grossi 99]

 

這些結構都是用於解決這樣一個問題,在磁盤上能夠創建動態的有序查詢結構。

 

在今天,主要想介紹的就是COLA,所謂cache-oblivious 就是說,他不需要知道具體的內存大小和一個塊的大小,或者說,無論內存多大,塊有多大,都可以使用同一套邏輯進行處理,這無疑是具有優勢的,因爲內存大小雖然可以知道,但內存是隨時可能被臨時的佔用去做其他事情的,這時候,CO就非常有用了。

 

其他我就不多說了,看一下細節吧~再說這個我自己都快繞進去了。

 

衆所周知的,磁盤需要的是順序寫入,下一個問題就是,怎麼能夠保證數據的順序寫。

我們假定有這樣一個空的數據集合

海量存儲系列之九

 

 

可以認爲樹的高度是log2N。

每行要麼就是空的,要麼就是滿的,每行數據都是排序後的數據。

 

如果再寫一個值的時候,會寫在第一行,比如寫了3。

再寫一個值11的時候,因爲第一行已經寫滿了,所以將3取出來,和11做排序,嘗試寫第二行。又因爲第二行也滿了,所以將第二行的5和10也取出,對3,11,5,10 進行排序。寫入第四行

海量存儲系列之九

 

這就是COLA的寫入過程。

可以很清楚的看出,COLA的核心其實和LSM類似,每次“將數據從上一層取出,與外部數據進行歸併排序後寫入新的array”的這個操作,對sas磁盤非常友好。因此,寫入性能就會有非常大的提升。

 

並且因爲數據結構簡單,沒有維持太多額外的指針,所以相對的比較節省空間。

 

但這樣查詢會需要針對每個array都進行一次二分查找。

性能似乎還不是很高,所以,他們想到了下面這種方式,把它的命名爲fractal tree,分形樹。

用更簡單的方法來說的話呢,就是在merge的時候,上層持有下層數據的一個額外的指針。

來協助進行二分查找。

海量存儲系列之九

 

這樣,利用空間換時間,他的查詢速度就又回到了log2N這個級別了。

 

到此,又一個有序結構被我囫圇吞棗了。


嘿嘿


在下一篇,我們將進入大家期待的分佈式k-V場景,也就是noSQL的範疇了,讓我們撥開nosql的神祕面紗,看看這東西到底意味着什麼。

 

 

http://qing.weibo.com/1765738567/693f084733000963.html下一篇




Dec26

海量存儲系列之十

http://qing.weibo.com/1765738567/693f0847330008x6.html  上一篇

上一次,我們介紹了幾種常見的kv存儲模型,下面我們就正式進入到分佈式存儲的場景裏去看看這套東西在分佈式場景下的運作方式吧。

 

在分佈式key-value中,很多原來的知識是可以繼續複用的。因爲k-v解決的問題實在是非常的簡單,只不過是根據一個key找到value的過程,所以原來的知識,現在也繼續的可以用。

但有兩個額外的因素需要考慮

網絡延遲

         TCP/IP –公用網絡,ip跳轉慢,tcp包頭大

可能出現不可達問題

         這其實是狀態機同步中最難的一個問題,也就是,A給B消息,B可能給A的反饋可能是:1. 成功 2. 失敗 3. 無響應。最難處理的是這個無響應的問題。以前的文章中我們討論過這個問題,以後還會碰到。這裏暫且hold住。

 

先上圖一張,在未來的幾周內,我們都會依託這張圖來解釋分佈式K-V系統

海量存儲系列之十

 

可以看到,在客戶機到服務器端,有這麼幾個東西

一,規則引擎

二,數據節點間的同步

 

抽象的來看,分佈式K-V系統和傳統的單機k-v系統的差別,也就只在於上面的兩個地方的選擇。

 

 

今天先來談規則引擎

 

抽象的來看,規則引擎面向的場景應該被這樣的描述:對於有狀態的數據,需要一套機制以保證其針對同一個數據的多次請求,應該物理上被髮送到同一個邏輯區塊內的同一個數據上。

 

舉例子來說,一個人進行了三筆交易,每筆交易都是這個人給其他人100元。那麼,這三比交易的更新(update set money = money – 100 where userid = ?) 必須被髮到同一臺機器上執行,才能拿到正確的結果。【不考慮讀性能的gossip模型除外】

 

這種根據一個userid 找到其對應的機器的過程,就是規則引擎所要處理的事情。

 

我們對於規則引擎的需求,一般來說也就是要查的快,第二個是要能儘可能的將數據平均的分配到所有的節點中去。第三個,如果新的節點加入進去,希望能夠只移動那些需要移動的數據,不需要移動的數據則不要去移動他。

 

那麼一想到“根據xxx找到xxx”相信大家第一個想到的一定又是以前說過的K-V了。所以我們就再複習一遍: )

 

Hash 

O(1)效率

不支持範圍查詢(時間這樣的查詢條件不要考慮了)

不需要頻繁調整數據分佈

順序

主要是B-Tree

O(logN)效率

支持範圍查詢

需要頻繁調整節點指針以適應數據分佈

 

這也就是我們最常用的兩種分佈式k-value所使用的數據結構了

 

首先來看HASH的方法。Hash其實很容易理解,但我跟不少人交流,發現大家可能對一致性hash的理解有一定的誤解。下面請允許我給大家做個簡單的介紹。

 

簡單取模:

最簡單的HASH 就是取mod,user_id % 3 。這樣,會將數據平均的分配到0,1,2 這三臺機器中。

這也是我們目前最常用的,最好用的方案。但這套方案也會有一個問題,就是如果id % 3 -> id % 4 總共會有75%的數據需要變動hash桶,想一想,只增加了一臺機器,但80的數據需要從一個機器移動到另外一個機器,這無疑是不夠經濟的,也是對遷移不友好的方案。

不過,雖然增加一臺機器,會發生無謂的數據移動,但取模的方案在一些特殊的場景下,也能很好的滿足實際的需要,如id % 3 -> id % 6,這種情況下,只需要有50%的數據移動到新的機器上就可以了。這也就是正常的hash取模最合適的擴容方式-----> 倍分擴容。我們一般把這種擴容的方式叫做”N到2N”的擴容方案。

 

取模Hash還有個無法解決的問題,就是無法處理熱點的問題,假設有一個賣家有N個商品。如果按照賣家ID進行切分,那麼就有可能會造成數據不均勻的問題。有些賣家可能有10000000個商品,而有些賣家只會有10個。這種情況下如果有大量商品的賣家針對他的商品做了某種操作,那這樣無疑會產生數據熱點。如何解決這類問題,也是分佈式場景中面臨的一個重要的問題。

 

既然簡單取模有這麼多的問題,那有沒有辦法解決這些問題呢?

 

首先,我們來介紹第一種解決這個問題的嘗試。

 

一致性Hash.

先來個圖,這套圖估計幾乎所有對Nosql稍有了解的人都應該看過,在這裏我會用另外的方式讓大家更容易理解

 

 

海量存儲系列之十

上面這個圖,用代碼來表示,可以認爲是這樣一套僞碼

 

Def idmod = id % 1000 ;

If(id >= 0 and id < 250)

         returndb1;

Else if (id >= 250 and id < 500)

         returndb2;

Else if (id >= 500 and id < 750)

         returndb3;

Else

         returndb4;

 

這個return db1 db2 db3 db4 就對應上面圖中的四個淺藍色的點兒。

而如果要加一個node5 ,那麼僞碼會轉變爲

 

Def idmod = id % 1000 ;

If(id >= 0 and id < 250)

         returndb1;

Else if (id >= 250 and id < 500)

         returndb2;

Else if (id >= 500 and id < 625)

         returndb5;

else if(id >= 625 and id <750)

         returndb3

Else

         returndb4;

 

從這種結構的變化中,其實就可以解決我們在普通hash時候的面臨的兩個問題了。

1.      可以解決熱點問題,只需要對熱點的數據,單獨的給他更多的計算和存儲資源,就能部分的解決問題(但不是全部,因爲遷移數據不是無成本的,相反,成本往往比較高昂)

2.      部分的能夠解決擴容的問題,如果某個點需要加機器,他只會影響一個節點內的數據,只需要將那個節點的數據移動到新節點就可以了。

 

但一致性hash也會帶來問題,如果數據原本分佈就非常均勻,那麼加一臺機器,只能解決臨近的一個節點上的熱點問題,不會影響其他節點,這樣,熱點擴容在數據分佈均勻的情況下基本等於n->2n方案。因爲要在每個環上都加一臺機器,才能保證所有節點的數據的一部分遷移到新加入的機器上。

 

這無疑對也會浪費機器。

 

於是,我們又引入了第三套機制:


虛擬節點hash

Def hashid = Id % 65536

 

 

海量存儲系列之十

可以很容易的看出,上面這套虛擬節點的方案,其實與id % 4的結果等價。

可以認爲一致性hash和普通節點hash,都是虛擬節點hash的特例而已。

 

使用虛擬節點hash,我們就可以很容易的解決幾乎所有在擴容上的問題了。

碰到熱點?只需要調整虛擬節點map中的映射關係就行了

碰到擴容?只需要移動一部分節點的映射關係,讓其進入新的機器即可

 

可以說是一套非常靈活的方案,但帶來的問題是方案有點複雜了。

所以,我們一般在使用的方式是,首先使用簡單的取模方案,如id % 4。在擴容的時候也是用N->2N的方案進行擴容。但如果碰到需求複雜的場景,我們會“無縫”的將業務方原來的簡單取模方案,直接變爲使用虛擬節點hash的方案,這樣就可以支持更復雜的擴容和切分規則,又不會對業務造成任何影響了。

 

好,到這,我基本上就給大家介紹瞭如何使用Hash來完成分佈式k-value系統的規則引擎構建了。

下一期我們來看一下使用樹的方案,當然,主要也就是hbase這個東西了,可能會再介紹一下mongodb的自動擴容方案。睡覺睡覺: )


http://qing.weibo.com/1765738567/693f084733000a5w.html




Jan06

海量存儲系列之十一

http://qing.weibo.com/1765738567/693f084733000963.html 上一篇


ps : 最近霸神推了一把,粉絲增加不少,頓時亞歷山大。。還是希望大家用輕鬆一點的心態來看待我的這些科普文。
如果想精細推敲,歡迎在後面留言,我一定會與您討論與分享。

上一期我們主要在介紹hash相關的切分方式,那麼這次我們來看一下有序結構的切分

 

有序結構的拆分,目前主要就是使用樹或類似樹的結構進行拆分,這裏主要就是指HBase和MongoDB.

 

使用樹結構切分,帶來的好處就如hbase和mongoDB的宣傳標語一樣,可以無縫的實現自由擴展。但反過來,帶來的問題其實也不少,下面我們一起來看一看吧。

 

首先複習B樹知識http://qing.weibo.com/1765738567/693f0847330008ii.html

 

在B樹中,最關鍵的處理邏輯是如果單個節點數據滿的時候,應該進行節點分裂和節點合併。

 

那麼,其實在HBase中也有類似這樣的過程。

 

對於巨大量的數據來說,整個樹的Branch節點都有可能超過單機的內存大小上限,甚至超過單機的硬盤大小上限。

這時候就需要把BTree進行拆分,這種拆分的最標準實現映射,就是HBase.

(圖片版權方在:http://blog.csdn.net/HEYUTAO007/article/details/5766951)

 

 

海量存儲系列之十一

看這個圖可能會比較暈,沒關係,聽我分析之。

首先,整個Hbase就是爲了解決一個B樹非常巨大,以至於單機無法承載其branch and root節點之後,使用分佈式存儲的方式來提升整個樹的容災量的一種嘗試。

 

抽象的來看,每一個HRegion都是一個Btree的Node,這個Node會掛在在某個Region server上面,RangeServer內可以存放多個Hregion ,其實就是Btree的branch節點了,但因爲Branch也很多,以至於單機無法存放所有branch節點,因此就還需要一層結構來處理這個問題。這就是HMaster 。

上圖

海量存儲系列之十一

 

雖然可能有點抽象,不過本質來說就是這樣一個東西。

當然,細節有點變化:

HMaster ,在上面的圖中是單個點,實際的實現是一個btree,三層結構的。

 

因爲HMaster的數據不經常發生變化,同時,每次請求都去訪問HMaster,那麼HMaster所承擔的讀寫壓力就過大了。所以,HBase增加了一個客戶端的Cache.來存HMaster中的這幾層BTree.

 

於是,可憐的Hbase又得考慮如何能夠將HClient和HMaster中的數據進行同步的問題。

 

針對這個問題,Hbase提出的解決思路是,既然變動不大,那就允許他錯吧,只要咱知道出錯了,改正了就行了。

也即,允許HClient根據錯誤的Btree選擇到錯誤的Region Server,但一旦發現自己所選的數據在那臺Region server上無法找到,則立刻重新更新自己的HMaster表。已達到同步。

 

 

這基本上就是BTree的分佈式實踐中做的最好的HBase的一些過程了。

 

然後然後,私貨時間開始: )

 

藉助HDFS,Hbase幾乎實現了無限的擴展性,但整體結構過於複雜和龐大了,最終,他只解決了一個K-V寫入的問題,同時又希望對所有用戶屏蔽底層的所有數據節點的具體位置。

這套思路有其優勢之處(也就是Btree的優勢):

1.      純粹log場景,btree管理起來非常方便

2.      支持範圍查詢

 

但可能的劣勢其實也很多

1.      結構繁雜,在各種角色中進行數據同步,這件事本身聽起來就已經很嚇人了。然而,最終,他只是解決了一個按照K找到V的過程。。Hash一樣可以做到

2.      Region server ,維護難度較高,核心數據結構點,雖然該機器可以認爲是個接近無狀態的機器,但如果想拿一臺空機器恢復到可以承擔某個Region server的指責,這個過程需要的時間會很長,導致的問題就是,系統的一部分數據不可用,甚至發生雪崩。

3.      BTree 在不斷追加append的時候,其實是有熱點的,目前沒有很好地辦法能在按照時間序或按照自增id序列的時候保證所有的數據存儲機都能夠比較均衡的寫入數據。會存在熱點問題,這個問題的源頭在BTree需要有序並連續,這意味着連續的數據只會被寫在一個region塊內,這個問題在單機btree其實也是存在的,但有raid技術,以及有二級索引,所以問題沒有那麼明顯。(感謝@bluedavy)

 

綜上,HBase其實從一開始是一個面向後端處理的數據引擎,在數據一致性上是可以期待的,但對於線上系統來說,他違背了重要的一個原則:簡單。所以我“個人”對這一點持保留態度。

 

不過,這麼多大牛在努力的經營HBase這個產品,那麼我也樂觀其成,畢竟能把這麼複雜的東西整的能在這麼多臺機器上用,也是個巨大成就了。

 

MongoDB其實也是在學Hbase的這種有序的BTree結構,不過它的實現就簡單的多了。

就是把數據拆分成一段一段的數據,用一個公用的配置角色存儲這段數據所在的分片。查詢時進行二分查找找到。

思路類似。

 

從角色來看

他的規則引擎實現就是個有序數據的實現,可以認爲是個兩層有序結構查找.第一層決定數據的具體機器(Mongos+config server),第二層決定數據在該機的具體位置MongoServer。

 

 

好了,畫個圖用了20分鐘,今天的介紹就到這裏,下期我們來探討分佈式場景下一個必要的過程。數據的遷移方式討論。

http://qing.weibo.com/1765738567/693f084733000bxj.html 下一篇





海量存儲系列之十二

http://qing.weibo.com/1765738567/693f084733000a5w.html 上一篇

時間隔了比較久了,因爲最近在過年臨近,所以都在準備這方面的事情。這裏提前祝大家新年快樂。

然後還是回到我們的正題兒吧:)


本章,我們主要來討論數據的管理和擴容中最重要的一個部分,數據遷移。


數據遷移是數據運維中最爲重要的一個部分,在前面的文章中已經提到過,作爲有狀態的數據節點,在互聯網行業的主要追求就是,無限的水平擴展能力,這種水平擴展,主要用於解決兩類問題,一類是磁盤空間不足的問題,一類是性能不足的問題。



爲了達到這種能力,一般來說主要也就是這樣一個思路,儘可能的讓數據不動,只通過規則變動的方式來完成擴容,如果這種方式無法滿足要求,那麼再通過移動數據的方式,來滿足其他的一些需求。


下面來進行下分析。

只通過變動規則的方式擴容,舉個最簡單的例子,就是一組按照時間或自增id的數據。那麼最簡單的切分方式,就是按照時間或id的範圍,將一組數據直接映射到某個具體的機器上,類似

if(gmt> = 2010 and gmt < 2011)

returndataNode1;

elseif( gmt >= 2011 and gmt < 2012)

returndataNode2;

elseif(gmt >= 2012 and gmt < 20121223)

returndataNode3;


使用這種方式的好處,顯而易見,就是不用動數據,方法簡單。

但帶來的壞處也明顯,就是不移動數據,那麼如果一組數據已經成爲熱點,那麼永遠也沒有機會將熱點數據分開到不同的機器裏用以減輕熱點的損耗了。而,這種情況是非常有可能的,對於一對多的模型,如果按照一去存儲數據,那麼因爲多的數據量的不斷擴展,會最終導致單個機器的數據量和io超限。


爲了解決上述矛盾,就需要引入數據的遷移的方法了,簡單來說,就是按照規則將數據從原來的一組機器上,遷移到新的一組機器上去,這樣規則和數據一起變動,就可以有效的解決上面所說的熱點問題,儘可能讓所有的機器均勻的發揮效用。


思路很簡單,但工程實踐就複雜多了,下面來描述幾種擴容的模式,希望大家能針對這幾種場景以及我的分析,對如何解決這個問題有個更深入的認識。


所有有狀態的數據,其實都需要有擴容的策略,在這些擴容的模式中,最簡單的莫過於對cache節點的擴容了。因爲cache本身其實只是一個一致的數據的一個快照,快照的意義就在於:如果你對快照的數據是否正確有異議,可以直接去從數據的源頭再查一次寫回快照中,即可保證數據的最新。


那麼對於緩存數據,一般來說緩存的更新邏輯有兩種,一種是寫的時候同步更新緩存。一種是先讀緩存,緩存沒有的時候讀數據庫讀出最新值後更新緩存,一般來說是兩種緩存模式組合使用,因爲沒有副作用。對於這種情況的緩存節點擴容,最簡單的做法是,只需要直接改變規則即可。


如,假設原來的數據是按照id% 4進行切分的,那麼如果規則換爲id% 8.那麼有一半的數據就無法被訪問到。但沒關係,因爲業務的實際邏輯是,如果讀不到,就讀穿緩存去數據庫裏面取數據再更新回緩存,所以很快,數據會按照新的id% 8 進行填充,擴容就完成了。


當然,實際的擴容比這個要複雜一點,因爲,要考慮規則變動後,讀穿的次數增多,導致數據庫壓力上升的問題,所以要儘可能的避免過多的數據讀穿緩存,這時候會使用我們在以前的文章中討論過的一致性hash或虛擬節點hash,使用緩慢更新映射關係的方式,來降低擴容對數據庫帶來的壓力。


以上是最簡單的規則和數據一起移動的例子,從上述例子可以分析出,其實規則遷移的最主要問題在於如何保證規則變更時,數據能夠在規則發生變動的時候對外部保證數據是最新的讀取,在緩存擴容的case中,這個數據保證最新的任務,是由數據庫這個組件來完成的。所以緩存擴容是相對最爲簡單的。


那麼,自然的就會產生另外一個疑問:對於數據庫,怎麼保證這個一致性的讀取呢?這也是我們這一章要闡明的最重要的問題。


數據的一致性讀,一般來說就只有兩種做法。第一種是共享內存指針,說白了就是數據只有一份,但指向該數據的指針可能是多個。還有一種就是數據複製,數據的複製,保證一致性的難度會很大。更多的情況是按照實際的需求,取兩種模式的折衷。


對數據節點的擴容而言,其實核心就是數據的複製,既然複製,那麼一致性就非常難以保證,於是我們也就只能儘可能巧妙地利用手頭的工具,取折衷,用以儘可能的減少不一致的影響。


爲了解決這個一致性的問題,我們需要在規則上引入版本,這個概念,主要是用於規定什麼時候數據應該以什麼規則進行訪問。這樣,就可以避免數據複製過程中所帶來的不一致的問題了。


假設,我們原來的規則,版本號爲0,新的規則,版本號爲1.那麼,開始的時候,客戶端所持有的數據的切分規則是版本0,所有數據在老的一組機器上進行讀取和寫入,不會出現問題。當我給定v0和v1兩個版本同時存在時,從客戶端就可以意識到,目前的規則是兩份並存,數據可能是不一致的,這時候最簡單的處理策略是,阻止一切讀取和寫入,這樣數據的不一致就不會發生了(哈哈,因爲本身不允許讀寫了嘛。。),而當規則變爲只有v1的時候,那麼客戶端就可以知道,目前只有一個規則了,按照這個規則,進行數據訪問就可以了。


使用版本號,就可以讓客戶端能夠有機會意識到數據在某個時間段可能存在着不一致,應該加以針對性的處理,這樣就可以規避數據讀寫的不一致的問題了。


解決了不一致的問題,下面緊接着要解決的問題有兩個:

我如何知道應該讓哪些數據移動到哪臺機器上?

我如何儘可能的減小規則並存時的停寫的數據範圍?


針對這個問題,外面開源的社區,最常用的解決方法是一致性hash。


海量存儲系列之十二


在一致性hash中,在某個地方加一組機器,可以很容易的預測應該將哪個節點的數據移動到新的節點上。同時,又可以預測,哪些節點不會受到影響,哪些不受到影響的節點,完全可以開放讀取,而受到影響的節點,則阻止訪問即可。


如上圖中,

node4和node2中間,加了一個node5,那麼很容易的可以知道,只需要將node4中的一部分數據,寫入新的node5即可。而node2,node1,node3的數據不受到影響,可以繼續允許訪問。


這樣就可以比較成功的解決上面提到的兩個問題了。


但從http://qing.weibo.com/1765738567/693f084733000963.html這篇文章的討論中,我們也很容易可以看到,一致性hash也有他自己的問題。


於是,自然就有人要問,有沒有其他的做法呢?


自然是有啦,下面來介紹一下淘寶TDDL在這方面的工程實踐吧。以下是純粹乾貨,目前暫時沒見過業內使用類似方式,這種模式在淘寶也經歷了較多次的自動擴容考驗,能夠滿足我們的需求,相信也一定能滿足您的需求,因爲它什麼都沒做,也什麼都做了:).


首先是需求描述:分析淘寶的需求,簡單概括就是一句話,業務方的規則需求,複雜到無以復加,絕非簡單一致性hash或簡單btree可以滿足,爲了不同的業務需求,會有種類很多的切分規則。


需求分析:

需求分析其實就是挖掘需求的含義,找到哪些是真實的需求,哪些不是,將不是的砍掉,看看剩下的能不能滿足的過程:)


擴容系統的技術特點:

規則系統要自定義,因爲這是業務核心,只有業務知道他們的數據怎麼分配會獲得比較均勻的訪問模型。

擴容“不是”常態,一般來說擴容的週期是3個月~6個月,甚至更長。如果一個業務,每6天要擴容一次,那採購人員絕對會抄傢伙找他們team幹架去了

擴容本身不是不能做,但難度較大,一般來說需要幾個人一起參與,最少有數據運維人員,系統運維人員以及開發人員參與,一幫苦13程序員夜裏3點多鬧鐘叫起來,睡眼朦朧的進行機械的操作。難度可想而知。


基於這些技術特點,可以作如下分析

業務的變化要求數據擴容的規則要儘可能的自定義,可以有些預先定義好的規則模型,但不能強制要求業務必須走定義好的模型。

自動擴容,意義不大,如果只是讓業務人員根據數據點個確定,是最容易被接受的擴容模式

要儘可能的避免擴容本身對業務本身帶來的影響,同時要儘可能減輕開發人員的熬夜次數。


所以我們設計瞭如下的系統,他滿足以下特性

規則完全自定義,你可以隨便寫任何的ifelse等腳本代碼。

只對擴容需求提供決策支持和方案生成,但決策由人進行。

除了決策,其餘全部自動化。



這套系統就是我們的自動擴容系統。


因爲我們將要在Q1~Q2完全開源目前淘寶在300~400個系統中所使用的所有中間件,包括rpc調用框架,消息系統以及數據庫切分中間件,所以我的介紹本身將是對實現思路與細節的描述,無保留。


不過在這裏,讓我懸念留給下一篇(喂喂喂,難道不是文章太長導致的麼?嘿嘿),在下一篇,我們將仔細介紹一下TDDL的規則引擎系統。


http://qing.weibo.com/1765738567/693f084733000dby.html 下一篇




海量存儲之十三

http://qing.weibo.com/1765738567/693f084733000bxj.html 上一篇


在上一章中,我們主要介紹了規則引擎中最重要的一個部分,自動擴容,在今天的章節,我們主要還是介紹一下我們在淘寶TDDL中的工程實踐吧。

首先從原理開始吧。

先來一張圖

海量存儲之十三


這張圖以前也出現過,我們在裏面着重介紹了規則引擎

規則引擎是什麼呢?
對應在上述例子裏面,其實就是DBNum = pk % 3 這個規則。

他的變化可能很多,比如對於一致性hash則變爲一個if - else 的表達式(見前面)
也可能有其他的變化。
所以,我們要回歸本源,問一個問題,什麼是規則引擎?

抽象來看,規則引擎在做的事情是,根據一組輸入條件(例如主鍵id,或者用戶id+時間,或者一個rowKey),進行了一種計算,然後返回在某個機器某個表上執行的結果。這種計算要保證,在規則本身不發生變動的情況下,同一組輸入條件,返回的永遠是相同的結果。

想想這種描述像什麼?:-) 我個人認爲很像函數的定義,那麼讓我們換一下表述方式吧:
假設輸入數據爲x(主鍵id,用戶id_時間,或者rowKey) ,經過運算F,返回了該數據在某臺機器上這個結果y.那麼表達式就是
y = F(x)

這是第一層抽象,爲了方便表述,我們後面都以這種方式進行表述。

這種規則引擎,在幾乎所有“有狀態”的數據存儲中都會用到,在我們的工程實踐中,我們發現這套引擎需要非常靈活的表現能力,才能適應不同用戶的不同需求,比如有些場景中,業務方會給出一批經過數據分析以後的大賣家,他們固定的就擁有大量數據,會對其他人造成影響,這時候規則引擎必須能夠對各種不同的場景進行適應。

因爲規則能夠決定數據的分佈是否均勻,因此規則是整套系統中最重要的核心組件。

有了規則引擎,我們要追尋的下一個目標就是,如何能夠在儘量少的影響業務的正常使用的前提下,改變規則,以達到均衡訪問或擴容的目標。

要達到這個規則,第一個需要做的事情就是要能夠分辨,哪些數據應該被移動,以及從哪個源頭移動到哪個目標去。
要解決這個問題,在當時能夠想到的方法有兩個,一個是定死的規則,比如一致性hash,一致性hash,因爲規則本身的入參是定死的,輸出也是定死的,所以可以知道從哪裏移動到哪裏。但這也會帶來問題,因爲有些業務根本不是使用一致性hash來完成的,他們可能有自定義的函數(如:如果賣家id=2000,那麼走特殊的機器) 。
一旦有這樣的自定義函數,那麼就很難通過分析規則來獲取需要遷移的數據是哪些以及應該從哪裏移動到哪裏這些屬性了。



於是,就必須有另外的方法。


我們採取的方案,是完全放開F,採取多版本的方式來獲得“哪些數據應該被移動,以及從哪個源頭移動到哪個目標去”,這兩個信息。

原理如下:

我們假設有老規則 F0 ,以及新規則F1.
對於相同的輸入X,我們能得到兩個y,也即
y0 = F0(x) 以及y1 = F1(x)

對兩個y進行比較(compare) ,能夠獲取兩種結果: 結果1 : y0 == y1. 結果2 : y0 != y1.

思考這兩種結果的含義,不難明白其中的含義:
如果y0 == y1,那麼意味着,對於相同的數據x,在老規則和新規則中,數據都在同一個庫的同一張表上(y相同),這條數據在老規則換爲新規則的時候是不需要移動的。
而,如果y0 != y1,那麼意味着,這條數據,如果將規則從F0換爲F1,則數據需要被移動,移動的方向應該是從y0到y1.

這樣,我們就很輕鬆的使用多版本的方式,獲得了“哪些數據應該被移動,以及從哪個源頭移動到哪個目標去”,這兩個信息。

最後,在知道了上面的兩個關鍵的信息後,還需要一套東西來幫用戶把數據儘可能平滑的從一個源機器中移動到目標機器中。

這就是我們在平衡遷移中進行的思考,如果有想探討的歡迎一起參與。

下面,我們進入工程實踐,來看一下我們的規則引擎在做的事情吧。

角色介紹

對於規則引擎,它實現瞭如下特性:
多版本支持
只有支持多版本,才能夠方便的知道哪些數據應該從哪裏移動到哪裏去。
枚舉支持
用來支持用戶按照日期進行切分,但需要注意的是,這裏的日期切分不是傳統意義上B樹模型的那種切分方式,原因見後續分析。
內建多種切分函數支持
允許方便的直接使用內置定義的一致性hash,虛擬節點hash等函數方法,減少代碼量。


與規則引擎配套的,還有一套我們目前叫做“大禹”的項目工程,他主要完成了以下幾件事:
切分數據收集
能夠協助收集用戶切分後的數據狀態,如訪問熱點情況,硬件情況等。
決策支持
能夠幫助用戶定義新的擴容策略,但我們不做“自動化擴容”,因爲擴容本身不是常態。
自動遷移
能夠根據用戶的多版本規則,協助用戶自動化的進行規則遷移,最終能夠將數據遷移導致的不可用時間降低到深夜1分鐘內,基本不造成影響。
工程實踐描述

在我們的工程實踐中,我們選擇了groovy來實現java的規則引擎,使用javaScript來實現跨平臺的規則引擎。

從規則引擎來看,他只需要一個引擎,能夠運行一個函數就可以了,所以上述平臺都可以滿足我們的需求,從速度角度考慮,我們選擇了可靜態編譯的groovy和js v8引擎。

在這個引擎之上,我們對引擎進行了包裝,針對淘寶的特殊需求進行了二次開發:
在淘寶,有很大一批數據是需要按照多個條件進行切分的,如,按照用戶切庫後,按照時間切表等,針對這種需求,我們要擴展原來的函數定義,允許用戶使用類似table+"_"+ #userid# % 1024 +"_" + dayofmonth(#gmt#);
這樣的方式來拼裝類似table_0001_23這樣的表後綴.實現多維度的切分。

同時,還需要滿足用戶的範圍查詢需求,如,返回一個用戶在某個時間段內的所有數據。這往往意味着可能要遍歷多個分表的需求,針對這種需求,我們允許用戶使用表達式的方式填入y = F(x)中的'x' ,如 'x' = (gmt <= now() ) and (gmt >'2012-01-01' ) 這樣的輸入參數。
針對這樣的參數,傳統的解決方案是使用排序後的樹形結構來滿足查詢(如hbase),我們認爲,因爲數據節點的個數本身是有限的,我們沒有必要維持複雜的數據結構,只需要使用枚舉的方式就可以達到類似的效果,因爲顆粒度可控。


對於大禹工程

排開數據收集以及分析後展現之外,最重要的部分無疑是能夠根據多版本的規則進行自動化的擴容和遷移這一塊了。

他的主要流程如下:
海量存儲之十三



舉個例子來說明這個流程

從整體來看,大禹在做的事情就是,全量遷移所有需要移動的數據,然後將在全量過程中產生的增量數據append到新節點上,然後部分停寫1分鐘,推送規則的新版本。完成遷移。


我們假定原來有一臺機器,裏面有兩條記錄:
row a : id = 0 ,name = "a"
row b : id = 1 ,name = "b"

切分的規則爲 id % 1 ,
那麼我們根據表達式 y0 = id % 1 ,分別將id(row a) = 0 ;id(row b) = 1代入表達式,得到y0(row a) = 0; y0(row b) = 0;
這兩個結果。

然後,我們要將機器擴容爲兩臺,
這時候規則變爲 y1 = id % 2,分別將id(row a) = 0 ;id(row b) = 1代入表達式,得到y0(row a) = 0; y0(row b) = 1;

這時候,用戶新寫入了一條數據row c : id = 3 , name="c"

因爲用戶在使用老規則寫入,所以使用老規則後,數據應該通過老規則計算出結果y0(row c) = id % 1 = 0;
在按老規則寫入後,數據就已經可見了,這時候,大禹會讀取這條記錄,按照新規則進行計算,y1(row c) = id % 2 = 1; 因爲1 != 0,所以row c 需要進行遷移,遷移目標是從0機器--> 1機器。

這時候大禹會將這條數據保存在本地磁盤中。

而如果假定row d 通過新老規則計算出的結果y0 (row c) == y1 ( row c) 則該數據會被大禹增量複製組件丟棄,因爲數據在規則變動後不需要移動位置。

在增量開啓後,會進行全量的遷移。
全量的過程與增量類似,是按照選擇條件,將老機器內的指定數據遍歷一次,對每一條記錄,進行老規則和新規則的計算,如果計算結果相同則丟棄,計算結果不同,則將數據寫入新規則算出後的結果。

當全量結束後,大禹增量複製組件會將記錄在本地磁盤中的增量數據覆蓋到全量後的數據上,並且繼續隨着新的數據產生,將數據雙寫在老規則和新規則所對應的機器上。併發出catch up的狀態指令。

在catch up後,我們可以認爲,老規則內的數據和新規則內的數據,是異步一致的,中間的數據延遲是異步複製的延遲,一般來說在幾百個毫秒內。

這時候,就可以選擇一個合適的時機,比如夜裏5點,進行部分停寫,等待新老數據絕對一致以後,發佈新規則。完成遷移。

整個遷移過程,只有最後的“部分停寫,等待新老數據絕對一致以後,發佈新規則。完成遷移“ 是會影響業務應用的,這之前的所有過程都是個外加過程,對業務完全沒有影響,就算異常失敗了,也可以全部放棄掉以後重新來過,這就保證了整套邏輯的儘可能簡單清晰。

好的軟件就是少做不該做的事情的軟件嘛 :)

以上是好的地方,下面來自暴家醜,說說不足。

規則引擎所面向的目標,其實是有狀態數據的節點管理,對於節點管理來說,大家的追求一般都是有共識的,也就是說,可以按照需求,隨便的增加或減少節點。但遺憾的是,目前在我們的工程實踐中,目前還沒能很好的解決“隨便”這個需求。

所謂隨便,就是指可以達到這樣一個效果,某天某個監控人員,發現某些數據突然的成爲熱點了,那麼它可以快速反應,點個按鈕,上線100臺機器,立刻load下降,保證了系統穩定。然後呢,發現某個集羣load很低,就點個按鈕,下線100臺機器作戰略儲備。

可惜,這樣的事情在有狀態的機器中是很難做到的,原因很簡單,有狀態節點的數據遷移是需要成本的,而且成本不小,這也是爲什麼foursquare會掛的原因。

以上,就是我對淘寶TDDL 數據庫切分tool kits中規則引擎和配套的自動擴容組件的介紹了。
目前淘寶的TDDL組件被廣泛的使用在淘寶300多個不同的業務系統中,並且沒有使用過強制命令進行推廣。

在未來的一個Q內,我們會逐漸的開源我們目前的這套工程實踐產品,希望有更多的人能夠受益。




海量存儲之十四

這一次,我們來講講數據安全和讀寫高可用


oh no,親,於是我們又掉入了CAP所描述的陷阱。

好吧,那麼我們也就進入這個領域,來看看這數據安全所代表的一切。

在20年以前,數據安全對於大部分用戶來說,只意味着數據庫ACID中的”D”,數據寫入到數據庫,並返回成功後,這個數據也就是安全的了,在老師教給我們的計算機原理課上,似乎最多也就講到,數據庫有冷備份,也有熱備份,因此寫入數據庫內的數據是安全的。

然而,真的如此麼? 

最簡單的問題,就是,如果這臺機器的硬盤掛掉,那應該怎麼辦呢?

於是,有些人就想到,那我們用多塊硬盤來備份不就好了?於是,RAID技術就應運而生了。Raid技術的核心,就是利用磁盤陣列的手段,提升數據的寫入效率和安全性。

那麼,raid也不是完美的,首先,磁盤放在機器上,這機器的磁盤就不可能無限增加的。單機的磁盤容量受到磁盤架個數的限制。

於是,有一些人就想到:那我們就專門的設計一種可以掛無數磁盤的櫃子來好了。這就是盤櫃技術的產生,關鍵詞 SAN,不過這東西不是我們要討論的東西,所以我們就不在這裏細說了。 

但因爲盤櫃技術本身有其優勢也有一定的侷限性,所以目前這套東西不大爲人所知了。

似乎所有人談起存儲必談GFS,HDFS…但其實個人認爲在原教旨主義的文件系統上,更多的依靠硬件的盤櫃,也不失爲一套非常好的解決方案。

正所謂分久必合合久必分,目前Oracle攜ExtraData 一體機技術在市場上殺的風生水起,不也是盤櫃技術的新時代體現麼?呵呵。。

好了,廢話不多扯,盤櫃在目前不大容易成爲主流,主要原因是

1.      冗餘容錯性不好

2.      價格較貴

3.      核心技術把持在大公司手裏

Etc.

那麼,既然盤櫃技術不大容易成爲主流,那麼目前的主流是什麼呢?

這就是多機的同步技術,利用廉價的tcp/ip網絡,將多臺pc server聯繫到一起,使用軟件邏輯而非硬件邏輯來進行數據的多磁盤備份。

這其實就已經涉及到了問題的核心:什麼是數據安全和數據高可用?我們將數據安全的級別從低到高,做成表格列在下面。

海量存儲之十四
 

可見,從目前來看,解決數據安全的唯一辦法是將數據同步的寫到多個不同的地方(可以是用raid5的方式寫,也可以用raid10的方式,不過核心都是一個--- 冗餘。

 

而且不能簡單的就用單機的冗餘,必須要多機冗餘才靠得住。

 

如果要最安全,那麼數據就要同步的複製多機。

 

下面,我們就專注於這同步複製多機的case,來看看目前我所知的幾種常見的,以解決高可用爲基礎的數據冗餘方案吧。

 

需要強調的是,這些方案本身,沒有絕對的一家獨大之說,每一種方案,都有其自己的特性和適用場景,所以不存在某一種方案一定比其他方案好的這種說法,每一種模式都有其自己的優勢和劣勢。

所謂可用性,就是儘可能的保證,無論發生什麼變故,數據庫都能夠正常的提供讀寫訪問的這種方式。

而所謂安全性,就是儘可能的保證,無論發生什麼變故,數據庫內,持久化的數據都不會丟失。

這裏的數據丟失,其實是個很寬泛的詞彙,爲了表達的更爲清楚,我們需要仔細的描述一個最關鍵的問題:什麼時候能夠叫做“數據寫入成功”,換句話說,也就是,數據存儲系統,與前端的無狀態調用者之間的承諾關係是什麼呢?

認清這個問題,對於我們定義數據安全,至關重要。

從一個請求走到網絡,提交給存儲系統的過程,細化下來可以認爲是以下幾個動作的分解:

海量存儲之十四

 

可以認爲,在時間線上來說,所有的這些操作都可能出現!異常!,導致寫失敗。這裏又涉及到以前我們提到的網絡三種反饋狀態問題了,讓我分階段來進行討論:

A. 客戶端發起請求出現失敗 -> 客戶端明確的知道自己失敗,所以所有操作都未進行,對整個系統的一致性沒有影響。

B. 發起請求後,因爲網絡因素,導致請求未被server接受 -> 客戶端等待,直到超時,但Server未接受請求。客戶端應認爲失敗。

C. Server端接受到寫入請求,但內部操作失敗-> Server端應該保證操作的原子性,並反饋client操作失敗。

D. Server端一系列操作成功,反饋Client,但因爲網絡異常導致Client無法接收請求 ->Server端成功,但Client端等待超時,則client端對操作有疑問。

E. Client端收到Server端反饋的成功 -> 認爲成功。

可以認爲,只有最後一步成功的時候,纔算做成功,而其他操作,因爲網絡因素導致的異常,都認爲是失敗的。

從上面的分析中可以看出,存儲對於完整性的保證,只存在於E步驟成功時。

而client端能明確的知道操作失敗的,是A,,C場景。

Client端不能明確知道操作成功或失敗的,是B,D,場景,需要人工驗證,或默認丟超時異常,並被認爲是失敗。

而我們所定義的數據安全性,就是指,當E完成時,也就是Server對client端反饋成功時,則數據在各種變故出現時,所能保證的完整性的一種體現( T_T ..真繞。。。)

而我們要在後面,花費大量篇幅來討論的,就是“進行一系列操作處理”這個過程,如何能保證數據的完整性的問題。

 因爲篇幅的關係,今次就介紹第一種,也是最簡單的一種,使用異步複製隊列的方式來提升可用性和安全性吧。

這是所有數據存儲中基本都提供的一種模式,而大部分的其他方案的核心和基礎都是這個異步的複製的模型的權衡版本。所以,我們就從這裏開始。

要講清這個問題,我們先來看一張圖

海量存儲之十四

所謂異步傳輸,簡單來說就是數據寫入主機後,不等待其他機器反饋結果請求,直接反饋用戶寫入成功的一種策略。

 

好處:

數據寫入速度快(因爲只需要保證一臺機器寫成功,那麼就算成功了)。

副作用:

如果主機寫了位置102,但備機還沒來得及收102的數據到備機,這時候主機down機。數據實際上還未寫入備機呢。於是就出現了數據丟失。

好,就到這裏。。下次我們來討論,在這種情況下,如何能夠保證可用性。




海量存儲之十五

罪過啊。。竟然發現沒有寫行列存的關係。。


行存/基於行的存儲

海量存儲之十五

 

一個數據塊內的數據類似上面圖除了藍色以外的部分的順序排列,一般來說會在block頭記錄一個block有幾行,每行的偏移量是多少,如果超過一個block,需要用extend結構扔到塊的外面去。

優勢是一次寫全部數據,一次取一行全部的數據。在事務操作中,訪問控制相對的比較容易做一些。另外因爲大部分情況下對編號列不做壓縮處理,所以更新效率較高。


 

列存/基於列的存儲

海量存儲之十五

海量存儲之十五

海量存儲之十五

 

一個block內存放的數據是

海量存儲之十五

 

這樣的順序數據,因爲這樣存儲數據,所有數據的類型是一樣的,所以對數字類型可以做差量壓縮,比一般壓縮效率要好很多,在block內一般會額外描述在當前這個block內的列的一些信息,比如sum / count/最大/最小。用以方便在不解壓縮的情況下進行快速計算。

優勢其實很明顯,對數字可以做差值壓縮,取單個列的count sum的時候速度很快。

但如果做了壓縮,那麼不適於做頻繁的update操作。代價較大。

加鎖可能比行的要複雜一些。不過也沒有本質區別。

海量存儲之十五

 

海量存儲之十五

海量存儲之十五

海量存儲之十五

 

這種存儲方式就是json/pb/常用的方式,數據以key-value對的方式存放在裏面。所有的數據都是以extends的方式進行記錄。優勢是可以隨意增加,減少行的數量(如果你用行存,意味着所有行都必須有值,哪怕1000000個數據裏只有一個有這個列。也需要佔用較多空間,如果用這種方式,就不會有問題。

代價是所有的數據都要存schema.一種常見的優化是額外的有個map,映射schama->bit。這樣可以通過兩次映射的方式(schema->bit 。 bit->數據) 來減少在一行內的重複的冗餘schema信息。文檔類數據庫的常用組織形式。mongodb是在這個的基礎上額外的做了索引。所以其實就是個行存儲。只是能schema free.對於初期建模有一定幫助。

 

還有一種常見的,針對行存的優化方式,也很巧妙。oracle exadata在使用

海量存儲之十五

 

因爲數據是按照block取的,所以oracle允許你按照block取數據,但block內的數據按照列存的方式組織。

這樣可以在一個block內存放更多的壓縮後的數據,提升空間利用率。是個很好的想法。。




Nov16

海量存儲之十六--一致性和高可用專題

很久木有和大家見面了,因爲博主也需要時間來沉澱。。博主也需要學習和思考。。

好吧,不多廢話,進入正題,今天我們談的東西是一致性和安全性。

一致性這個問題,非常繞,想用語言表述,難度很大,我給別人去講的時候,一般都是白板,因爲白板有類似“動畫”的效果,能夠幫助別人理解,但使用文字,就沒有辦法了,只好要求各位有一定的抽象思維能力,能在自己的腦袋裏模擬這種動畫吧:)

 

主要會聊到: 簡單的雙機兩階段提交,三階段提交,vector clock ,paxos思路,paxos改進思路,既然要闡述問題,那我們就需要先給自己畫個框框,首先來對這個問題做一個定義。

 ============

寫在前面,發現不少讀者都會用自己以前的2pc知識來套用文章裏面提到的2pc改。

我想說的是,在這裏的兩段提交,與大家所理解的兩段提交,有一定區別。

他是爲了滿足文中提到的C問題而改過的2pc.所以能夠解決一些問題,但也會產生一些新的問題。各位一定要放棄過去對2pc的理解,一步一步的跟着文中的思路走下來,你就會發現他其實不是真正事務中所用到的2pc,而是專門爲了同步和高可用改過的2pc協議。

============

問題描述

 

人們在計算機上面,經常會碰到這樣一個需求:如何能夠保證一個數據寫入到某臺或某組機器上,並且計算機返回成功,那麼無論機器是否掉電,都能夠保證數據不會丟失,並且能夠保證數據按照我寫入的順序排列呢?

面對這個問題,一般人的最常見思路就是:每次寫都必須保證磁盤寫成功纔算成功就好了嘛。

 

沒錯,這就是單機一致性的最好詮釋,每次寫入,都落一次磁盤,就可以保證在單機的數據安全了。

那麼有人問了,硬盤壞了怎麼辦?不是還丟了麼?沒錯啊,所以又引入了一種技術,叫做磁盤陣列(這東西,在我們目前的這個一致性裏面不去表述,感興趣的同學可以自己去研究一下)

 

好像說到這,一致性就說完了嘛。。無非也就是保證每次成功的寫入,數據不會丟失,並且按照寫入的順序排列,就可以保證數據的一致性了。

 

別急,我們這纔要進入正題:

如果給你更多的機器,你能做到更安全麼?

那麼,我們來看看,有了更多機器,我們能做到什麼?

以前,單機的時候,這臺機器掛了,服務也就終止了,沒有任何方式能夠保證在這臺機器斷電或掛了的時候,他還能服務不是?但如果有更多的機器,那麼你就會忽然發現,一臺機器掛了,不是還有其他機器麼?一個機房裏面的所有機器都掛了,不是還有其他機房麼?美國被核武器爆菊了以後,不是還有中國的機房麼?地球被火星來客毀滅了,不是還有火星機房麼?

 

哈,排比了這麼多,其實就是想說明,在機器多了以後,人們就可以額外的追求更多的東西了,這東西就是服務的可用性,無論如何,只要有錢,有網絡,服務就可用。怎麼樣?吸引人吧?(不過,可用性這個詞在CAP理論裏面,不只是指服務可以被訪問,還有個很扯淡的屬性是延遲,因爲延遲這個屬性很難被量化定義,所以我一般認爲CAP是比較扯淡的。。。)

 

好,我們現在就來重新定義一下我們要研究的問題:

尋求一種能夠保證,在給定多臺計算機,並且他們相互之間由網絡相互連通,中間的數據沒有拜占庭將軍問題(數據不會被僞造)的前提下,能夠做到以下兩個特性的方法:

         1)數據每次成功的寫入,數據不會丟失,並且按照寫入的順序排列

         2)給定安全級別(美國被爆菊?火星人入侵?),保證服務可用性,並儘可能減少機器的消耗。

我們把這個問題簡寫爲C問題,裏面有兩個子問題C1,C2.

 

爲了闡述一下C問題,我們需要先準備一個基礎知識,這知識如此重要而簡單,以至於將伴隨着每一個分佈式問題而出現(以前,也說過這個問題的哦..:) )

假定有兩個人,李雷和韓梅梅,假定,李雷讓韓梅梅去把隔壁班的電燈關掉,這時候,韓梅梅可能有以下幾種反饋:

         1)"好了,關了"(成功)

         2)"開關壞了,沒法關"(失敗)

         3)

 

呵呵,3是什麼?韓梅梅被外星人劫持了,消失了。。於是,反饋也沒有了。。(無反饋)

這是一切網絡傳遞問題的核心,請好好理解哈。。。

 

--------------準備結束,進入正題---------------------

 

兩段提交改:

 

首先,我們來看一種最容易想到的方式,2pc變種協議。

如果我有兩臺機器,那麼如何能夠保證兩臺機器C問題呢?

 

海量存儲之十六--一致性和高可用專題

我們假定A是協調者,那麼A將某個事件通知給B,B會有以下幾種反饋:

         1.成功,這個可以不表。正常狀態

         2.失敗,這個是第二概率出現的事件,比如硬盤滿了?內存滿了?不符合某些條件?爲了解決這個情況,所以我們必須讓A多一個步驟,準備,準備意味着如果B失敗,那麼A也自然不應該繼續進行,應該將A的所有已經做得修改回滾,然後通知客戶端:錯誤啦。

         因此,我們爲了能做到能夠讓A應付B失敗的這個情況,需要將同步協議設計爲:

         PrepareA -> Commit B -> Commit A.

         使用這個協議,就可以保證B就算出現了某些異常情況,數據還能夠回滾。

 

我們再看一些異常情況,因爲總共就三個步驟,所以很容易可以枚舉所有可能出現的問題:

         我們將最噁心的一種情況排除掉,因爲網絡無反饋導致的問題,看看其他問題。

         PA ->C B(b機器掛掉): 也就是說,如果在Commit B這個步驟失敗,這時候可以很容易的通過直接回滾在A的修改,並返回前端異常,來滿足一致性問題,但可用性有所喪失,因爲這次寫入是失敗的。

         在這時的可用性呢? B機器掛掉,對A來說,應該允許提交繼續進行。這樣才能保證服務可用,否則,只要有任意的一個機器掛掉,整個集羣就不可用,這肯定是不符合預期的嘛。        

         PA -> C B -> C A(A機器掛掉) :這種情況下,Commit A步驟失敗,應該做的事情是,在A這個機器重新恢復後,因爲自己的狀態是P A,所以他必須詢問B機器,你提交了沒有啊?如果B機器回答:我提交成功了,那麼A機器也必須將自己的數據也做提交操作,就能達到一致。

         在可用性上面,一臺機器掛掉,另外一臺還是可以用的,那麼,自然而然的想法是,去另外一臺機器上做嘗試。

         從上面可以看到,因爲B機器已經提交了這條記錄,所以數據已經是最新了,可以基於最新數據做新的提交和其他操作,是安全的。

 

 

         怎麼樣?覺得繞不繞?不過還沒完呢,我們來看看2pc改的死穴在哪裏。。

        

還記得剛開始的時候,我們提到了排除掉了一種最噁心的情況,這就是網絡上最臭名昭著的問題,無反饋啊無反饋。。

         無反饋這個情況,在2pc改中只會在一個地方出現,因爲只有一次網絡傳輸過程:

         A把自己的狀態設置爲prepare,然後傳遞消息給B機器,讓B機器做提交操作,然後B反饋A結果。這是唯一的一次網絡調用。

 

那麼,這無反饋意味着什麼呢?

         1.B成功提交

         2.B 失敗(機器掛掉應該被歸類於此)

         3.網絡斷開

 

更準確的來說,其實從A機器的角度來看這件事,有兩類事情是無法區分出來的:

         1)B機器是掛掉了呢?還是隻是網絡斷掉了?

         2)要求B做的操作,是成功了呢?還是失敗了呢?

不要小看這兩種情況。。。他意味着兩個悲劇的產生。

 

首先,一致性上就出現了問題,無反饋的情況下,無法區分成功還是失敗了,於是最安全和保險的方式,就是等着。。。沒錯,你沒看錯,就是死等。等到B給個反饋。。。這種在可用性上基本上是0分了。。無論你有多少機器,死等總不是個辦法。。

然後,可用性也出現了個問題,我們來看看這個著名的“腦裂”問題吧:

         A得不到B的反饋,又爲了保證自己的可用性,唯一的選擇就只好像【P A ->C B(b機器掛掉):】這裏面所提到的方法一樣:等待一段時間,超時以後,認爲B機器掛掉了。於是自己繼續接收新的請求,而不再嘗試同步給B。又因爲可用性指標是如此重要,所以這基本成爲了在這種情況下的必然選擇,然而,這個選擇會帶來更大的問題,左腦和右腦被分開了!

         爲什麼?我們假定A所在的機房有一組client,叫做client in A. B 機房有一組client 叫做client in B。開始,A是主機,整個結構worked well.

海量存儲之十六--一致性和高可用專題

 

一旦發生斷網

海量存儲之十六--一致性和高可用專題

 在這種情況下,A無法給B傳遞信息,爲了可用性,只好認爲B掛掉了。允許所有client in A 提交請求到自己,不再嘗試同步給B.而B與A的心跳也因爲斷網而中斷,他也無法知道,A到底是掛掉了呢?還是隻是網絡斷了,但爲了可用性,只好也把自己設置爲主機,允許所有client in B寫入數據。於是。。出現了兩個主機。。。腦裂。

 

這就是兩段提交問題解決了什麼,以及面臨了什麼困境。

碰到問題,就要去解決,所以,針對一致性問題上的那個“死等”的萌呆屬性,有人提出了三段提交協議,使用增加的一段提交來減少這種死等的情況。不過3PC基本上沒有人在用,因爲有其他協議可以做到更多的特性的同時又解決了死等的問題,所以3pc我們在這裏就不表了。3pc是無法解決腦裂問題的,所以更多的人把3pc當做發展過程中的一顆路旁的小石頭。。

 

而針對腦裂,最簡單的解決問題的方法,就是引入第三視點,observer。

既然兩個人之間,直接通過網絡無法區分出對方是不是掛掉了,那麼,放另外一臺機器在第三個機房,如果真的碰到無響應的時候,就去問問observer:對方活着沒有啊?就可以防止腦裂問題了。但這種方法是無法解決一致性問題中的死等問題的。。。

 

所以,最容易想到的方式就是,3pc+observer,完美解決雙機一致性和安全性問題。

 

後記3317字。nnd我本來以爲可以5篇兒紙說完這個問題的。。現在發現剛闡述了很小一部分。。果然一致性和可用性真不是個簡單的問題。今天到這,這個做個專題吧。





海量存儲之十七--一致性和高可用專題

 

 

-------三段提交改-----------

回顧上文,我們已經提到了,在兩段提交協議裏面有個“死等”的過程,那麼我們來看看三段提交協議是怎麼解決這個問題的,需要注意的是,3pc只是解決了死等問題,對腦裂沒有貢獻。用的也不多,我們只把它當做路邊的小石頭,理解了作爲模型的一種,參考一下就行了。

海量存儲之十七--一致性和高可用專題

 

首先分析原因,死等的關鍵,在於B機器掛了,A機器沒有收到B機器的反饋。這時候A不知道應該怎麼辦,所以只能死等。否則都可能造成不一致。

在這裏,我要着重強調一下,3pc的假定是,你能確切的知道B是機器掛掉了,不是網絡斷開了(雖然在A看來,它無法區分這兩種情況,我們在開始已經提到)。

 

3pc解決B在commit B這個階段掛掉方法,是做兩次通信。

 

增加了一次通信,叫pre commit,【我們這裏,因爲B是跟隨A的,不會在B出現寫讀衝突或者寫寫衝突,所以我們也可以減少B的一個prepare(canCommit)狀態,我們把它叫做3pc!改!】

 

下面,讓我來說明一下這個問題:

爲了解決死等的問題,我們只有一種選擇,就是讓每次請求都有一個超時時間。也就意味着,每次請求都要有個計時器,計算多少時間以後,這個請求就算是超時了。

但是,光有超時是不夠的,在上一篇文章裏面也提到,在2pc改中,請求超時可能意味着B提交成功了,或者B提交失敗了。這時候A是無所適從的,提交也不是,不提交也不是。

 

最簡單的一種思路,其實大家經常用到:

 

大家還記得在有些影片裏面,某大boss要參加一場鴻門宴,於是跟下面的將軍說:“我現在要去參加鴻門宴,不一定能回來,我們約定一下,如果今天晚上10點我還沒回來,你就給我帶軍隊平了他們,給我報仇。”

 

這其實就是三段提交核心思想的真實寫照,問題的關鍵就是約定延遲某時間後,最終雙方就按照某種"先期約定"進行後面的操作。(在這個case,先期約定是對方出老千,我們出兵跟他拼命。在3pc內的先期約定是雙方都算作提交成功)。

那麼,在這延遲的時間內,其實是數據狀態是混沌的(或者說量子態的?笑),10點前,這boss是死了啊?還是活着?沒人知道,這種混沌只有被揭開蓋子的那個時刻(發送doCommit(),doAbrot(),boss派人你雙方和解了,boss派人告訴你快來救我),纔會變成決定論的。

 

好,我們回到3pc上來。

在第一次通信(pre commit)的時候,A和B的先期約定是如果某個時間後兩方都沒有後續反饋,那麼算作提交成功。這裏需要注意的是,沒有後續反饋的原因,在3pc理論裏只是指B掛掉這種情況,【而不是】指網絡出現問題這個情況。

這樣,如果A機器發送pre commit這條信息,能夠拿到這麼幾個反饋:

         1.成功

         2.失敗

         3.超時【超時的原因是B掛掉,不是網絡斷開】

而B呢?在收到precommit 這條信息之後,能夠發送給A幾種反饋

         1.成功

         2.失敗

【這裏要非常注意,是"沒有"超時這個實際會發生的選項的,因爲3pc協議是排除了網絡超時這種情況的】

 

所以B的策略很簡單,如果pre commit我反饋了成功給A,那麼如果我發現在等待超時之後仍然沒有獲得A的提交或終止請求,那麼我就提交。反之我就終止。

這裏的等待超時,我們來看看可能由哪些問題引起:

         1.因爲A機器掛了,無法發送最終的提交或回滾命令給B

         2.因爲B機器掛了,沒有收到A機器的提交或回滾命令。

 

再來看A這臺協調機:

         如果preCommit 得到了失敗或超時的結果,那麼我們可以立刻發送abort命令給所有人,這個命令可能有兩種結果:

         abort成功,那麼整個事物回滾

         abort失敗,意味着B機器超時,已經提交。那麼A機器也只能提交。

 

         如果preCommit成功,那麼爲了加速提交過程,可以再發送一條commit命令給所有人,加速提交過程。

 

以上的過程就是3pc的核心思路,各位看官可以根據這個思路,去推斷和補充這套協議的其他內容,我就不再細說了,因爲其他推論如果不是真正要去實現,意義不大,關鍵是核心思路。

 

然後,我們來看看3pc的問題,其實,從我們討論的過程中多次出現的東西,大家就能很容易的看出問題所在,他的假定實際上不是真實的雙機或多機一致性場景,在這種場景裏面,網絡無響應也可能意味着網絡隔離斷開這種情況。但這種情況在3pc內是沒有被考慮的。

 

所以也因爲這樣,我們只是需要了解這東西的一個簡單思路就行了,不過,所有的思路在你未來的生活中,都可能會成爲你解決問題的利器,所以,我還是把這種模型的核心思路寫出來。給大家做個參考。

 

 

-----統一思想,做出統一決策---------解決腦裂問題

 

我們剛纔也提到,網絡上經常會因爲不可抗力造成思維上的隔閡,比如A機房和B機房之間的主幹路由器掛掉了啊等等情況。那麼,在這種情況下,有什麼辦法能夠在這種時候,通過一種機制來選擇應該聽哪一組集羣的呢?

 

問題在一致性問題的第一篇已經分析過了,所以我就不再重複了,只重複我們要做到的事情:"給定安全級別(美國被爆菊?火星人入侵?),保證服務可用性,並儘可能減少機器的消耗。"

 

我們也給出了一種最簡單的模型,觀察者模式:

海量存儲之十七--一致性和高可用專題

 

在這種模式中,A和B機房的網絡如果斷掉,只要他們到C機房的網絡不同時斷掉,那麼就可以利用在機房C的觀察者來協助判斷誰是正確的。

這樣似乎問題不就解決了嘛?你或許會這樣想?呵呵,那你就錯過最好玩的東西了。。

我們來看看,這是三臺機器的情況,任意一臺掛掉或者網絡斷掉,都可以保證結構不亂。

那麼,如果我有10臺機器,有更多機器可能“掛掉”更多網段可能斷開,這時候你會怎麼安排機器的角色和網絡結構呢?

 

仔細想想,無論給你多少臺機器,用observer模式,那麼只要observer掛掉並且在{A}機房{B}機房的網絡斷開的情況下,系統會退化到腦裂問題上了。

那麼,簡單的思路是,給Observer加機器不就好了?問題來了,Observer如果有兩臺,你到底應該聽誰的?這些Observer部署到哪些機房?哪個Observer是真正管事兒的?比如如果有兩個Observer, {Observer C和Observer D},各自爲政,那麼A機房正好問了Observer c,C說你是主機吧。然後B機房問了Observer D ,D說,你是主機吧。最後還是個腦裂的結局。。

 

怎麼樣?再給你來幾臺機器?暈不暈?

 

呵呵,我也不是難爲大家,而是這些情況會實際的發生在一致性和高可用的整個過程裏面。

然後,我們能做的事情是什麼?只好去找現實中的解決方法了。。

 

讓我們來吐槽一下Lamport.. 不得不說,這大神是個很好的數學家,但寫的Paxos論文你妹怎麼就這麼晦澀呢?本來很簡單的一個思路,讓他描述一下我是看了很久沒看懂啊- -。。最後還是得看in simple才勉強弄明白。。

 

不過,paxos的論文文章其實也正披露了他思路的核心來源:

         信使就是消息傳遞,也就是通過網絡將消息傳遞給其他人的機制。類似的機制還有人通過空氣傳遞語言給其他人,作家通過文字將情感傳遞給讀者,這些都是消息傳遞。所以也會有像我們李雷,韓梅梅關電燈一樣的幾種反饋。

         然後,Lamport大神還提到了一個重要的概念:議會。一看parliament,估計中國人就都暈了:這是嘛?我們偉大光榮正確的領路人的所有決議不都全票通過了麼,也沒討論什麼的。和paxos有神馬關係?。

其實這就是個悲哀了。。。在古希臘城邦時代,很多決議就是議會討論並作出決議的。

這種作出決議的方式,叫做“少數服從多數”。

當然當然,你也可以說,不是說有多數派暴政麼?不是說多數派更多的是《烏合之衆》麼?其實這也沒辦法的事情,所以丘吉爾纔會說:“民主並不是什麼好東西,但它是我們迄今爲止所能找到的最不壞的一種。”

 

我們就來看看,我們爲什麼需要少數服從多數這個原則

我之後的討論,純粹的是在機器和機器之間做出決策所需,在實際的zz過程中,環境遠比大家想想的複雜得多,變數很大,但作爲計算機,所有的東西其實都是可預期的,所以我們還是回到計算機科學領域。

假定有兩個機房,A機房和B機房,A機房有5臺機器,B機房有3臺機器。他們的網絡被物理隔離了。我們來看看我們有哪些選擇:

       1.A,B機房獨立的都可以提供服務。

       這種方式明顯是不靠譜的,會出現不可逆轉的不一致問題。咔嚓掉。

       2.A,B機房都不可以提供服務

       合理的方式,有些場景的時候會選擇這種情況。但明顯在高可用上面是0分,只保持了一致性而已。

       3.讓A機房的機器服務。

       好吧。你真的認爲剩下三臺機器提供服務的安全性比5臺的高?。。

       4.讓B機房的機器服務。

       “民主並不是什麼好東西,但它是我們迄今爲止所能找到的最不壞的一種。”

 

這就是qurom產生的核心原因。

 

今天到這,晚上我們會討論paxos的一些細節,散會~





海量存儲之十八--一致性和高可用專題

我們已經在上面的分析中,我們已經看到observer模型在多機場景下的問題,所以,paxos模型的目標就是解決這個問題,他解決這個問題的方法就是quorum模型。


我的目標是讓大家能弄明白,掌握這些複雜的概念,所以我也會將以前我在淘寶java中間件團隊內分享時候,大家經常犯的一些錯誤,也寫到【】裏面,儘可能讓大家少走彎路,如果有什麼感想,疑問,後面可以留言。

 

PS 廣告插播 : 淘寶java中間件團隊,你值得擁有:)

 

----PAXOS------

好,我們回顧一下上下文,我們在上篇文章中談到,當機器變得更多的時候Observer不能只有一個。必須有更多個Observer,但Observer多了,到底聽誰的又成了問題。你一言我一語,大家都覺得自己是老大,誰也不服誰。咋辦捏?

這時候就得有人站出來,說:那我們少數服從多數吧!制定一套策略,在各種情況下都能夠選出一個決議不就行了!

這其實就是paxos協議的核心想法之一,我們來看一下他是怎麼做到的。在這裏,我不想去做那個繁瑣的證明過程,那個過程如果你感興趣,可以去看paxos made simple這篇文章,有中文,這裏給出http://blog.csdn.net/sparkliang/article/details/5740882 ,數星星同學也翻譯過。可以直接google.


我在這裏只說結論,因爲結論更容易理解一些。

         我們假定有A,B,C,D,E五臺機器。kv系統需要put一個數據[key=Whisper -> val=3306]到我們這5臺機器上,要保證只要反饋爲真,任意兩臺機器掛掉都不會丟失數據,並且可以保證高可用。怎麼做:

         1.首先,客戶端隨機選擇一個節點,進行寫入提交,這裏我們隨機選擇了C這個節點,這時候C節點就是這次提議的發起人【也叫proposer,在老的2pc協議裏也叫做coodinator】,當C收到這個提議的時候,C首先要做的事情是根據當前節點的最新全局global id,做一次自增操作,我們假定,在當時全局id,Global ID是0,所以,這個議案就被對應了一個編號,1--->[key=Whisper -> val=3306]。

         【【這裏有兩個我們經常犯的錯誤,下面做一個解說:

                   1.global id問題,在老的論文裏,Lamport沒有描述這個自增id是怎麼生成的,所以大家的第一個疑問一般是問id怎麼生成,從我目前能夠看到的所有實現裏面,基本上就是選擇哪一臺機器,就是以那臺機器當前所保持的全局id(snapshot,可能不是全局來看的最高值,但沒關係,只要是自己這臺機器的最高值就行了),然後做一下自增就行了。我們後面會看到協議如何保證非全局最高值的globalID提議會被拒絕以至於不能夠形成決議。

                   2.global id --->[key=Whisper -> val=3306] . 這也是個會讓人困惑的問題,在原文中,他被表示爲一個key-value的形式,比如proposal[0->value] 。這會讓人自然的聯想到與數據庫的kv相對應,key是0,value是value。然後就會困惑,這個數據是怎麼和數據庫對應起來的呢?這是我當時的困惑,現在也把他列在這裏。其實很簡單,這裏的global id對應value.global id只是對paxos協議有意義,對於數據庫,其實只需要關心value裏面的數據即可,也即將global id --->[key=Whisper -> val=3306]裏面的value: [key=Whisper-> val=3306] 作爲數據庫構建映射時所需要的redoLog就行了,global id的作用只是告訴你這些數據的順序是按照global id來排列的,其他無意義。    】】


我們回到文中,我們已經將這個新的議案標記了從C這臺機器看起來最大的global id : 1--->[key=Whisper -> val=3306]。然後,他會嘗試將這個信息發送給其餘的A,B,D,E這幾臺機器。

         我們來看這些機器的操作流程。 在這個過程中,Paxos將A,B,D,E叫做accepter【老的協議裏沒有區分,管這些都叫做參與者,cohorts】,他們的行爲模式如下:

         如果A,B,D,E這幾臺機器的globalID 小於C給出的決議的GID(1--->[key=Whisper -> val=3306]),那麼就告訴C,這個決議被批准了。而如果A,B,D,E這幾臺機器的GlobalID 大於或等於C給出決議的GID.那麼就告知C 這個決議不能夠被批准。

         我們假定A,B兩臺機器當時的Max(GID)是0 ,而D,E的Max(GID)是1.那麼,A,B兩臺機器會反饋給C說協議被接受,這時候我們算算,C的議案有幾票了?A+B+!C!,一定要算自己哦:) 。所以,這個議案有三票,5臺機器的半數是3.超過法定人數,於是決議就被同意了。



         我們保持這個上下文,來看看D,E這邊的情況。首先,要思考的問題是,爲什麼D,E的Max(GID)是1呢?

         其實很簡單,D可能在C發起決議的同時,也發起了一個決議,我們假定這個決議是由D發起的,決議是 1--->[key=taobao ->val=1234]。既然D,E的Max(GID)是1,那麼意味着E已經告知D,它同意了他的決議,但D馬上會發現,A,B,C裏面的任意一個都返回了D不同意。他的議案只拿到兩票,沒有通過,它雖然有點不爽,但也是沒辦法的事情啊。。

 

這時候C的決議已經被多數派接受,所以他需要告知所有人,我的議案1--->[key=Whisper -> val=3306]已經被接受,你們去學習吧。




這時候還有一個問題是需要被考慮的,如果在C已經得知決議已經達到法定人數,在告知所有人接受之前,C掛了,應該怎麼辦呢?

 我之所以沒有將這個放到開始的描述裏,主要原因是覺得這是個獨立因素,不應該影響議案被接受時候的清晰度。

爲了解決這個問題,需要要求所有的accepter在接受某個人提出的議案之後,額外的記錄一個信息:當前accepter接受了哪個提議者的議案。


爲什麼要記錄這個?很簡單,我們看一下上面出現這個情況時候的判斷標準。

A 機器:角色-accepter 。 批准的議案 1--->[key=Whisper-> val=3306] 。提議人:C

B 機器:角色-accepter 。 批准的議案 1--->[key=Whisper-> val=3306] 。提議人:C

C機器:角色-proposer 。 掛了。。不知道他的情況。

D 機器:角色-accepter 。 批准的議案 1--->[key=taobao->val=1234] 。提議人:自己

E 機器:角色-proposer。 “提議的”議案 1--->[key=taobao->val=1234] 。提議人:D。

 

因爲有了提議人這個記錄,所以在超時後很容易可以判斷,議案1--->[key=Whisper -> val=3306] 是取得了多數派的議案,因爲雖然D,E兩臺機器也是可以達成一致的議案的。但因爲有個人本身是提議者,所以可以算出這個議案是少數派。

就可以知道哪一個議案應該是被接受的了。

 



在這之後,提議者還需要做一件事,就是告知D,E,被決定的決議已經是什麼了。即可。

這個過程在文章中叫Learn. D,E被稱爲Learner.

別看寫的簡單,這個過程也是變數最大的過程,有不少方法可以減少網絡傳輸的量,不過不在這裏討論了。

 




下面,我們討論一下我們在2pc/3pc中面臨的問題,在paxos裏面是怎麼被解決的。

2pc最主要的問題是腦裂,死等。兩個問題。

對於腦裂,paxos給出的解決方案是,少數服從多數,決議發給所有人,盡一切努力送達,總有一個決議會得到多數派肯定,所以,不在糾結於某一臺機器的反饋,網絡無響應?沒有就沒有吧,其他人有反饋就行了。

所以,如果出現了機房隔離的情況,比如A,B,C在機房1,D,E在機房2,機房1和機房2物理隔離了,那麼你會發現,D,E永遠也不可能提出能夠得到多數派同意的提案。

所以,少數派的利益被犧牲了。。換來了多數派的可用性。我們分析過,這是唯一能夠既保證數據的一致性,又儘可能提高可用性的唯一方法。

而對於死等問題,解決的方法也是一樣的,對於某一臺機器的無響應,完全不用去管,其他機器有相應就行了,只要能拿到多數,就不怕一小撮別有用心的反對派的反攻倒算~。

 



---------------------------------paxos就是這樣一個協議----------

休息一下

----------------------------------------------------------------------------------------

 




那麼Paxos有沒有什麼值得改進的地方?有的,很簡單,你會發現,如果在一個決議提議的過程中,其他決議會被否決,否決本身意味着更多的網絡io,意味着更多的衝突,這些衝突都是需要額外的開銷的,代價很大很大。

爲了解決類似的問題,所以纔會有zoo keeper對paxos協議的改進。zk的協議叫zab協議,你可以說zab協議不是paxos,但又可以說是paxos.但將paxos和zab協議之間做直接的等同關係,無疑是【錯誤】的。

 

其實,這也是在我們的現實生活中經常能夠發現的,如果每個議案都要經過議會的討論和表決,那麼這個國家的決策無疑是低效的,怎麼解決這個問題呢?弄個總統就行了。zab協議就是本着這個思路來改進paxos協議的。



---------paxos 改進----zab協議討論-----------------

zab協議把整個過程分爲兩個部分,第一個部分叫選總統,第二個部分叫進行決議。

選總統的過程比較特殊,這種模式,相對的給人感覺思路來源於lamport的麪包房算法,這個我們後面講。,選擇的主要依據是:

         1.如果有gid最大的機器,那麼他是主機。

         2.如果好幾臺主機的gid相同,那麼按照序號選擇最小的那個。

        

         所以,在開始的時候,給A,B,C,D,E進行編號,0,1,2,3,4。 第一輪的時候,因爲大家的Max(gid)都是0,所以自然而然按照第二個規則,選擇A作爲主機。

         然後,所有人都知道A是主機以後,無論誰收到的請求,都直接轉發給A,由A機器去做後續的分發,這個分發的過程,我們叫進行決議。

         進行決議的規則就簡單很多了,對其他機器進行3pc 提交,但與3pc不同的是,因爲是羣發議案給所有其他機器,所以一個機器無反饋對大局是沒有影響的,只有當在一段時間以後,超過半數沒有反饋,纔是有問題的時候,這時候要做的事情是,重新選擇總統。

        具體過程是,A會將決議precommit給B,C,D,E。然後等待,當B,C,D,E裏面的任意兩個返回收到後,就可以進行doCommit().否則進行doAbort().

         爲什麼要任意兩個?原因其實也是一樣的,爲了防止腦裂,原則上只能大於半數,不能少於半數,因爲一旦決議成立的投票數少於半數,那麼就存在另立中央的可能,兩個總統可不是鬧着玩的。

         定兩個,就能夠保證,任意“兩臺”機器掛掉,數據不丟:),能夠做到quorum。。  

 

然後是我的個人評述,寫zab協議的人否認自己的協議是paxos.變種 其實我也是有些認同的。不過,他們是針對一個問題的兩種解決方法:

因爲他們解決的問題的領域相同

解決網絡傳輸無響應這個問題的方法也一樣:也即不在乎一城一池的得失,盡一切努力傳遞給其他人,然後用少數服從多數的方式,要求網絡隔離或自己掛掉的機器,在恢復可用以後,從其他主機那裏學習和領會先進經驗。

並且也都使用了quorum方式來防止腦裂的情況。

核心思路是類似的,但解決問題的方法完全是兩套。 paxos在其他公司的實現裏面也對paxos進行了這樣,那樣的改進。不過核心思路都是這個。


我們對paxos協議的講解,就到這裏。

 

也留下一個問題,zab協議,如果我們用在google 全球數據庫spanner上,會不會有什麼問題呢?請大家思考哈 :)

 

後記,抱歉,這篇文章一個圖都沒有。。我已經儘可能用簡單的方式來描述paxos和他的變種協議了(當然有一個作爲了問題)。如果有哪個地方不明白,也還請在後面留言吧。友情提示這篇文章不適於跳躍性閱讀,想要理解,必須從第一行開始讀到最後。。。。

 

 google的工程師說,所有的一致性協議都是paxos的特例,我表示不置可否吧。。。。下一篇我們要討論另外一系的實現,gossip模型的實現。我個人感覺:把gossip歸類到paxos模型,似乎也不是很合適。gossip協議的兩個主要的實現方式,是dynamo和cassandra.我們在下一篇裏面進行討論




海量存儲之十九--一致性和高可用專題

--------Dynamo and Cassandra ----------

這兩套系統,其實是同源的,我其實不是很願意來說這兩套系統,因爲他們用的技術比較學術化,所以比較複雜一些。。Anyway ,I'll try my best !

 

提到這兩個系統,他們在覈心思路上是非常類似的,但有一些細節性的東西又有所偏重,在分佈式系統中也算是獨樹一幟了,很有代表性的一個系列,這些不一致的地方,最明顯的地方就在於一致性上。可見,哪怕是從追求簡單爲上的工程化實現來說,各種不同的方式實現一致性也都有很大的不同,不過他們也有一些共性和一些獨樹一幟的概念,下面來做一下分別解說。

 

先說共性:

 

W+R>N

相信這個大家都耳熟能詳了吧?呵呵,我從其他角度來說明這件事吧。

 

N表示這個複製集羣的總數【很多地方解釋的不是很準確造成了不少誤解】。

W表示寫入份數

R表示一次一致性讀所需要的份數(這裏要注意,是隨機從N中選擇的機器數量哦)

 

這個公式表示爲:如果滿足W+R>N(W,R,N 屬於不爲負數的整數且R,W<N),那麼集羣的讀寫是強一致的。如果不滿足,那麼這個複製集羣的讀寫是非強一致的。

這裏我強調一下,這裏的“集羣”,是指數據完全相同的多份拷貝。不涉及數據切分哦:)

這個公式的最常用的用法是求R,也就是說公式應該寫成N-W<R(W,R,N 屬於不爲負數的整數且R,W<N)。

我們舉幾個常見的例子:

 

1. 簡單的主備強一致複製。

因爲是強一致,所以數據一定是寫夠兩份的,W=2.

這個集羣的節點數呢?只有兩臺,所以N=2.

那麼R>(N-W = 2 - 2 = 0).

R> 0 所以R可以取 1, 2。 不能取三,因爲機器數只有2.。取不來3 :).

 

2. 假定有三臺機器,使用quorum的方式強一致的寫入數據。

因爲有三臺機器,所以N=3.

因爲是進行quorum寫入,只要寫兩臺就算成功了,所以W=2

這時候R>(N-W=3-2=1)

所以R的取值只可能是2,3

R的取值意味着什麼?意味着你必須從N=3臺機器中,最少隨機選擇兩臺進行讀取,纔有可能讀取到數據的最新值。

 

3. 假定有三臺機器,寫一臺就算成功。

因爲有三臺機器,所以N=3.

因爲是進行quorum寫入,只要寫一臺就算成功,所以W=1.

這時候R>(N-W=3-1=2)

R的取值只可能是3。

這意味着什麼?意味着如果你只寫一臺機器就算做成功,那麼你在讀取的時候需要讀取3臺機器,才能取得數據的最新值。

 

具體證明我就不列了,感興趣自己去看一下:) . 枚舉一下很容易可以得出結論的。

 

 

gossip協議

gossip協議是這兩套存儲的基礎之一,說複雜也複雜,說不復雜也不復雜。。其實gossip就是p2p協議。他主要要做的事情是,去中心化。

怎麼做到的呢?我只希望在這篇文章裏給大家留下幾個印象:gossip是幹什麼的?怎麼做到的?優勢劣勢是什麼?即可。對協議的細節感興趣的,可以自己去深入研究。

         gossip的核心目標就是去中心。

         做到的方式:

                   根據種子文件,按照某種規則連接到一些機器,與他們建立聯繫,不追求全局一致性,只是將對方機器中自己沒有的數據同步過來。這裏就設計到如何能夠快速的知道自己的數據和其他人的數據在哪些部分不一致呢?這時候就要用到Merkle tree了,它能夠快速感知自己所持有的數據中哪裏發生了變更。

         優勢:

                   去中心化,看看偉大的tor,只要能連到一個seed,有一個口子能出長城,那麼你最終就能跳出長城。。

         劣勢:

                   一致性比較難以維持,

(這裏我們介紹很簡單,因爲我也沒有實際的寫過。。。如果誰有這方面經驗歡迎補充)

 

 

不同選擇:

Dynamo : vector clock vs. Cassandratimestamp.

這兩個協議的目標都是解決一致性的。也是我們要說的重點。

我們先來說說Vector clock:

提到vector clock ,不能不提Lamport的另一篇論文Time Clocks and the Ordering of Events in a Distributed System(中文翻譯http://t.cn/zlEwziN)

這篇文章核心講的是多進程之間的互斥和排隊問題。不過這不是我們主要的要吸收的,在這篇文章中,更多的能夠讓你意識到一個問題:原來你跑到了一個相對論的世界裏。也即,在進程之間沒有消息相互通知的時候,他們就是各自爲政的,遵循着自己的時鐘的。只有在當他們之間有了相互之間的消息傳遞的時候,纔有可能創造一個全局時間序出來。

 

vector clock 給我的感覺,就正是沿着這條路子思考下去得出來的一種方式。如果要我一定有一個現實生活的類比的話,我想說,vector clock給我的感覺更像git .

讓我們從他與paxos的比較上面開始吧。

在paxos裏面,我們使用quorum和類三階段提交的方式來保證數據提議是順序的,一次只會有一個提議被接受。

這樣在一個場景下效率不是最高的:如果我們假定,大部分場景更新的數據都不重複,那麼效率就不會高了。

比如,如果我們不斷地往一個kv裏面進行以下操作:

{查看A夠不夠100塊?如果夠,減少100塊}

 

{查看A夠不夠100塊?如果夠,減少100塊}

{查看A夠不夠100塊?如果夠,減少100塊}


 

...

如果不斷地塞入這種數據,那麼實際上每次的寫入都依託了上一次數據的值。這種操作是必須排隊纔會高效的,否則會出現超減的情況的。

 

但如果我們的操作只是我們不斷地往一個kv裏面進行以下操作:

A登陸了

B登陸了

C登陸了

D登陸了

E登陸了

F登陸了

 

那麼可以認爲,所有的數據之間都是“相互沒有關係的”, 這時候,再讓這些寫入全部排隊一次,代價明顯就高了。

我理解的Dynamo和Cassandra,他們的場景主要是適合後面的這種方式,也即數據之間衝突很小的情況。在這種情況下,維持一個全局有序的隊列的效率太低了,不如這種分散式的方式。

但衝突的概率小,並不意味着沒有衝突,所以,還是需要有一套機制,能夠幫你感知哪些數據出現了衝突,允許你進行衝突處理的。

而在這個問題上dynamo和Cassandra選擇了不同的道路。

dynamo選擇了vector clock .

他的主要方式是:在數據傳遞的信息中,額外帶上這數據是從哪裏來的版本是多少。

我們來看看,用這種方式,如何能夠知道什麼時候發生了衝突:

我們假定有A,B,C三臺機器。

初始的時候,A,B,C三臺機器的數據sequence都是100.

這時候,我隨機挑了一臺機器,B寫了一行記錄[key=Whisper , Val=0]

這時候B生成的數據是議案[101 from B->[key=Whisper , Val=0]].

然後,又有另外一個人選擇C寫了另外一條記錄,比如[key=Whisper , Val=10000]

這時候C生成的數據議案是[101 from C->[key=Whisper , Val=10000]]

這時候,B的數據傳遞給C. 因爲C也有個101的議案,所以【他會保持兩份議案】(請注意,這是和paxos不一致的地方哦)

所以C接受的議案是

[101號議案

         {fromB [key=Whisper , Val=0]}

         {fromC [key=Whisper , Val=10000]}

]

然後怎麼辦?然後。。。。然後你在get("Whisper")這個數據的時候,vector clock會把這兩條記錄都反饋給你,告訴你,衝突,你自己選擇一條吧:)

那麼,這樣我們有幾種選擇,對於{count ++ 類}操作,應該將所有決議合併加到一起。

而對於其他數據,則應該按照時間戳,取時間戳最大的數據,這是最新的。

這就是vector clock的工作流程,在合併這段,讓我很容易就想到svn或git中的衝突解決。。

怎麼樣?是否覺得思路更開闊了?歡迎大家基於paxos和vector clock再進行其他思考,一致性的研究遠遠沒有結束。

就我個人嘛。。我更喜歡能保證順序的一致性模型,不喜歡這種各自爲政按需合併的模型。

 

好,說完了vector clock 我們來說說Cassandra 的Timestamp.

其實,TimeStamp模型是vector clock的劣化和簡化版本。在vector clock裏面,衝突是由用戶處理的,系統只是幫你檢查衝突,但Cassandra做的更粗暴和簡單一些。他不檢測衝突,所有數據只保留時間戳最大的那個。

這種模型可以應對80%場景了,模型得到了極大簡化,不過我估計應該是不能做count++操作了吧?我沒實際使用過。

 

 

好,回顧一下,我們在剛纔講了三個新概念: W+R>N .用來決定一致性級別。gossip協議和Merkle tree,用來進行去中心化的節點間數據同步。vector clock或timestamp.

現在是拼裝時間。

dynamo : 數據同步使用了gossip+Merkletree, 使用vector clock來標記衝突數據,衝突數據會交給用戶做出處理。允許通過配置,指定一組小集羣有幾個相同數據的Equalty Group(N).以及你要寫入並保證同時成功的份數(W),以及你要讀的份數(R)。

Cassanra :與dynamo類似(因爲同源), 在選擇上,放棄了vectorclock,使用Timestamp來進行衝突合併。其餘的類似。




MVCC 問答

然後因爲qingblog我看sina自己都不怎麼維護了,所以轉戰新浪博客。http://blog.sina.com.cn/u/1765738567


==============================================

Q:先要謝謝你的文章<海量存儲>,很系統且講清‘爲什麼’(而不是簡單的‘是什麼’),收穫不少。

看到mvcc時 http://qing.blog.sina.com.cn/1765738567/693f08473300067j.html,有一些不理解,還望詳解。


A: 

 

我先從原理開始,然後介紹mysql實現(MVCC實現非常自由。。我對一些實現也沒有那麼瞭解,有說錯的地方就海涵了)

如果要提到MVCC,就應從他最原始的需求出發來看。

對同一個數據的訪問來說,所謂的一致性和隔離性,其實就是針對以下四種情況的不同處理方式。

寫寫

寫讀

讀寫

讀讀

你可以用這四個衝突順序,拼裝出針對同一個數據的全部訪問順序。

 

那麼如何能夠限制這些請求的先後順序呢? 一個最簡單的方式就是加鎖。

第一個人訪問或寫入數據的時候,其他人不能夠寫入數據。

但這樣併發度明顯是上不去的,於是自然要想到,有沒有更快的方法呢?

那麼在java裏面,比加排他鎖更快的方法就是加入讀寫鎖咯。

它能夠保證”讀讀“ 這個衝突不會相互阻塞。

然而,讀寫和寫寫這兩個case,卻還是要全部鎖住的。

爲了解決這個問題,纔會有MVCC這個概念產生,對應java就是copyOnWrite . 

本質是爲了讓”讀寫“和”寫讀“衝突而出現的一種技術,每次針對一個數據的寫入,都會把原有數據複製一份出來,然後寫道新的地方去。這樣,這樣,如果訪問順序是寫讀寫,對於讀寫鎖來說等於加鎖三次,而對於MVCC來說只需要針對寫寫加鎖就可以了,甚至寫寫都可以不加鎖。對吧?

怎麼做到呢? 我們假定有個全局時間戳,每次事務都加1 。 那麼對於”寫讀寫“這個順序,第一個寫的時候申請事務id  0, 第二個讀因爲不修改數據所以事務id 不增加,第三個寫時事務id加1. 那麼數據的版本是0,1. 而讀數據的時候的id是0.

這樣,讀取的數據是0這個版本,因爲已經有1這個版本了,我們就可以推斷0這個版本的數據一定是完整的,而不需要關心1這個版本是否完整的寫入到了節點中,從而就不需要等待版本1的完整寫入了。於是,寫不阻塞讀,讀不阻塞寫。

從而,寫寫,寫讀,讀寫,都可以相互不阻塞,提升了系統的併發度。

 

理解了copyOnWrite,再來看真實的場景 中還需要解決什麼問題。

爲了說明這個問題,我們就需要理解什麼是事務, 所謂事務,並不是指更新或讀取一條記錄的。 事務是在事務開始後,你可能會更新的數據的集合。比如,update table set a = 1 where a > 10 . 那麼A > 10的所有數據(絕對不止一條),都需要“同時”被更改。

如果有一些計算機的基本知識,你就應該知道,計算機從本質來說還是個圖靈機實現,在目前是不可能“同時”更改所有數據的,但從需求來說又要有這樣的需求,於是只能通過其他方式模擬,這個模擬的方法,就只能是加鎖,將所有A>10的記錄都加鎖,然後再更新掉。

那麼這樣做的時候,就不大可能使用樂觀方式來做寫寫更新了,以我個人的理解,樂觀鎖的前提是,在爭用不明顯的場景下,因爲減少了上下文切換的開銷,從而可以獲得性能的提升,但如果假定我們要針對一組記錄做多次頻繁的更新時,就要權衡,到底是上下文切換的開銷大呢,還是頻繁rollback並且額外消耗大量cpu空轉的開銷大了。

於是你就知道了,爲什麼mysql 目前的實現裏面,只做到了讀不阻塞寫,寫不阻塞讀。寫和寫是相互阻塞的,原因就是因爲必須加鎖保證數據完整性。

 

----------------

針對mysql的實現方式的介紹。

爲了簡化模型,我們只討論插入的情況,實際上還有個刪除要考慮,不過原理類似。

在每行記錄上維持一個trx_id.

每一個事務開始的時候,trx_id都會增加,事務可以是顯示的setAutoCommit(false),也可以是個普通的update insert 等。

然後,他還有個當時正在進行的事務的trx_id的列表,維持在系統信息裏,所有正在運行的事務都會將自己的id記錄在這個列表中,等到事務提交後則從這個列表中刪除掉。

而每個事務又維持了”自己的“一個正在進行的事務trx_id的列表,這個列表是系統列表的一個snapshot.

 

在實際運行過程中,讀的時候其實mysql是用事務trx_id列表中最小的trx_id去與數據列中的記錄做比較的,只有比這個在當前正在運行的snapshot中最小的trx_id還要小的數據,才允許被讀取出來。

如,

全局事務id 列表是: 100,101,120,150

當前事務的id列表snapshot是 : 120,150

那麼,記錄中那些小於120 ,或者自己事務id所更新的記錄,並且符合未被刪除這個條件的的(這個原理類似,我不在這裏說是減少複雜度)。 才能夠被當前事務讀取。

然後,就來看看MVCC能做到的兩個隔離級別,讀已提交RC (其他事務提交後的數據立刻能夠被當前事務看到), 和可重複讀RR(其他事務提交後的事務在當前事務內不可見)。

瞭解了原理,不難推斷如何做到RC和RR

RC , 在事務內,在開始事務時更新一次當前事務的id列表snapshot,並且在每次運行了一個更新的sql後,都更新當前事務的id列表snapshot . 

RR , 只在事務開始時更新當前事務的id列表snapshot 。

 

這就是mysql的mvcc實現方式。

我說的那個是以oracle的實現模式爲模板的。。所以不大一樣。 PG的實現也不一樣。。不過核心思路就是我上面提到的那種。



==========================================================================



發佈了8 篇原創文章 · 獲贊 7 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章