原文鏈接: BASE: An Acid Alternative
數據庫 ACID,都不陌生:原子性、一致性、隔離性和持久性,這在單臺服務器就能搞定的時代,很容易實現,但是到了現在,面對如此龐大的訪問量和數據量,單臺服務器已經不可能適應了,而 ACID 在集群環境,幾乎不可能達到我們的預期,保證了 ACID,效率就會大幅度下降,更要命的是,這么高的要求,不好擴展~于是又了 CAP 原則(Consistency(一致性)、Availability(可用性)、Partition tolerance(分區容錯性))和 BASE 原則(Basically Available(基本可用)、Soft state(軟狀態)、Eventually consistent(最終一致)),看看它們的英文,Availability/Basically Available,Consistency/Eventually consistent,基本上,BASE 原則對 CAP 原則的進一步詮釋。
本文是Ebay的架構師在2008年發表給ACM的文章,是一篇解釋BASE原則,或者說最終一致性的經典文章. 文中Dan討論了BASE與ACID原則的基本差異, 以及如何設計大型網站以滿足不斷增長的可伸縮性需求,期間如何對業務做調整與折衷. 以及一些具體的折衷技術的介紹.
在對數據庫進行分區后,為了可用性(Availability)犧牲部分一致性(Consistency)可以顯著的提升系統的可伸縮性(Scalability).
——By DAN PRITCHETT, EBAY ,Translated by Jametong
Web應用在過去10年變得越來越普及.無論是為最終用戶還是為應用開發者構建的應用,對這個應用的希望很可能都是,此應用被最廣泛的用戶使用-廣泛的使用會帶來交易的增長.業務如果依賴于持久化,數據存儲就很可能成為瓶頸.
擴展任何應用都有兩種策略.第一種,也是最簡單的一種,就是 縱向擴展 :將應用遷移到更大更強的計算機上. 目前可用的最大的機器也滿足不了它的容量是它最明顯的限制.縱向擴展也很昂貴,增加交易容量通常都需要購買下一個更大的機器.縱向擴展通常還會產生對供應商的依賴,從而進一步增加成本.
橫向擴展 (Horizontal Scaling)提供了更多的靈活性,但也會顯著的增加復雜度.橫向數據擴展可能沿著兩個方向發展.按 功能擴展 (Functional Scaling)牽涉到按功能對數據進行分組,并將不同的功能組分布在多個不同的數據庫上.在功能內部將數據拆分到多個數據庫上,也就是進行 分片 (Sharding),它為橫向擴展增加一個新的維度.圖-1簡要闡釋了橫向數據擴展策略.
圖-1
如圖-1所示,橫向擴展的兩種方法可以同時進行運用.用戶信息(Users)、產品信息(Products)與交易信息 (Transactions)可以存儲在不同的數據庫中.另外,每個功能區域根據其交易容量(transactional capacity)可以再拆分到多個數據庫中.如圖所示,功能區域可以相互獨立地進行擴展.
功能分區(Functional Partitioning)
功能分區對于實現高可伸縮性相當重要.每一種好的數據庫架構都會根據功能將概要(Schema)分解到多張表中.用戶(Users)、產品 (Products)、交易(Transactions)以及通訊都是功能分區的例子. 常用的方法是,利用諸如外鍵(foreign key)一類的數據庫概念來維持這些功能區域之間的數據一致性.
依賴數據庫的約束保證功能組之間的一致性,會導致數據庫的不同概要(schema)在部署策略上高度耦合.要支持約束,表必須存在單一的數據庫服務器上,當交易率(transaction rate)增長時也無法對其進行橫向擴展.很多情況下, 將數據的不同功能組遷移到相互獨立的數據庫服務器上是最容易實現的向外擴展(Scale-out)方案.
可擴展到非常高的交易量的概要會將不同的功能的數據放置在不同的數據庫服務器上.這需要將數據之間的約束從數據庫遷移到應用中去. 同時這也將引入一些新的挑戰,本文的后續內容會對此進行深入探討.
CAP定理(CAP Theorem)
Eric Brewer,一位加州大學伯克利分校的教授,Inktomi公司的共同創辦人以及首席科學家,作出了以下推測,Web服務無法同時滿足以下3個屬性(由其首字母構成縮寫CAP):
- 一致性(Consistency).客戶端知道一系列的操作都會同時發生(生效).
- 可用性(Availability).每個操作都必須以可預期的響應結束.
- 分區容錯性(Partition tolerance).即使出現單個組件無法可用,操作依然可以完成.
具體地講,在任何數據庫設計中,一個Web應用至多只能同時支持上面的兩個屬性.顯然,任何橫向擴展策略都要依賴于數據分區;因此,設計人員必須在一致性與可用性之間做出選擇.
ACID解決方案
ACID數據庫事務極大地簡化了應用開發人員的工作.正如其縮寫標識所示,ACID事務提供以下幾種保證:
- 原子性(Atomicity).事務中的所有操作,要么全部成功,要么全部不做.
- 一致性(Consistency).在事務開始與結束時,數據庫處于一致狀態.
- 隔離性(Isolation). 事務如同只有這一個操作在被數據庫所執行一樣.
- 持久性(Durability). 在事務結束時,此操作將不可逆轉.(也就是只要事務提交,系統將保證數據不會丟失,即使出現系統Crash,譯者補充).
數據庫廠商在很久以前就認識到數據庫分區的必要性,并引入了一種稱為2PC(兩階段提交)的技術來提供跨越多個數據庫實例的ACID保證.這個協議分為以下兩個階段:
- 第一階段,事務協調器要求每個涉及到事務的數據庫預提交(precommit)此操作,并反映是否可以提交.
- 第二階段,事務協調器要求每個數據庫提交數據.
如果有任何一個數據庫否決此次提交,那么所有數據庫都會被要求回滾它們在此事務中的那部分信息.這樣做的缺陷是什么呢? 我們可以在分區之間獲得一致性.如果Brewer的猜測是對的,那么我們一定會影響到可用性,但,怎么可以這樣呢?
任何系統的可用性都是執行操作的相關組件的可用性的產物.此陳述的后半段尤其重要.系統中可能會使用但又不是必需的組件,不會降低系統的可用性.在兩階段提交中涉及到兩個數據庫的事務,它的可用性是這兩個數據庫中每一個的可用性的產物.例如,如果我們假設每個數據庫都有為99.9%的可用性,那么這個事務的可用性就是99.8%,或者說每月43分鐘的額外停機時間.
關于兩階段提交,你可以看看"改變未來的九大算法",里邊有精辟的講解~
一種ACID的替代方案
如果ACID為分區的數據庫提供一致性的選擇,那么你如何實現可用性呢?答案是BASE(基本上可用、軟(弱)狀態、最終一致性).
BASE與ACID截然相反.ACID比較悲觀,在每個操作結束時都強制保持一致性,而BASE比較樂觀,接受數據庫的一致性處于一種動蕩不定的狀態.雖然,聽起來很難應付,實際上這相當好管理,并且可帶來ACID無法企及的更高級別的可伸縮性.
BASE的可用性是通過支持局部故障而不是系統全局故障來實現的.下面是一個簡單的例子:如果用戶分區在5個數據庫服務器上,BASE設計鼓勵類似的處理方式,這樣一個用戶數據庫的故障只會影響這臺特定主機上的那20%的用戶.這里不涉及任何魔法,不過,它確實可以帶來更高的可感知的系統可用性.
因此,到目前為止,你已經將數據分解到了多個功能組中,并將最繁忙的功能組分區到了多個數據庫中,如何在你的應用中應用BASE原則呢?與ACID 的典型應用場景相比,BASE需要對邏輯事務中的操作進行更加深入的分析.到底該如何進行分析呢?后續的內容將提供部分指導原則.
一致性模式(Consistency Patterns)
沿著Brewer的猜測,如果BASE在分區數據庫中選擇保留可用性(Availability), 那么,弱化一定程度的一致性就成為必然的選擇.這通常難以決策,因為商業投資方與開發人員都傾向于認為一致性(Consistency)對應用的成功至關重要.哪怕是臨時的不一致也瞞不過最終用戶,因此,技術部門與產品部門都需要參與進來,以決定將一致性弱化到什么程度.
圖-2是一個簡單的概要,它闡釋了BASE中一致性要考慮的事情.用戶表存儲用戶信息,同時還包含總銷售額與總購買額.這些都是運行時的統計.交易表存儲每一筆交易,將買家、賣家以及交易金額關聯在一起.這些是對實際使用的表進行過度簡化后的結果,不過,它已經包含闡釋一致性的多個方面的必要元素.
圖 2
一般來說,功能組之間的一致性要比功能組內部的一致性要更加容易弱化.這個示例概要包含兩個功能組:用戶與交易.每當售出一個條目(的商品),交易表中就會增加一條記錄,買家與賣家的計數器都會被更新.使用ACID風格的事務,SQL語句可能如圖-3所示.
圖 3
用戶表中的總銷售額的列與總購買額的列可以被認為是交易表的一份緩存(Cache).它的存在是為了提高系統的效率.有鑒于此,一致性的約束可以被弱化. 可以調整一下買家與賣家的期望設置,從而他們的運行結余(running balance)不能立即反映交易的結果.這種情況很常見,實際上,人們經常會遇到交易與運行結余之間的這種延遲(例如,ATM取款或者手機通話).
如何修改SQL語句來弱化一致性要取決于如何定義運行結余,如果它們只是簡單的估計,也就是部分交易可以被錯過不統計,SQL的修改非常簡單,如圖-4所示.
圖 4
現在,我們已經將對用戶表與交易表的更新做了解耦.兩個表之間的一致性將再也無法保證.實際上,在第一個事務與第二個事務處理間隔發生故障,將導致用戶表持久處于不一致的狀態,不過,如果合同約定運行時匯總(running total)是估計值的話,這樣做也足夠了.
如果無法接受估計值,該怎么辦呢?如何繼續對用戶表與交易表的更新進行解耦呢?引入一個持久消息隊列來解決此問題. 有多種選擇可以實現持久消息.然而,實現此消息隊列的最關鍵的因素是,確保隊列的持久化支持與數據庫使用同樣的資源.要實現隊列在不涉及2PC的情況下按事務提交,這樣做很有必要 .現在的SQL操作看上看去有點不同了,如圖-5所示.
圖 5
這個例子中的語法有點隨意,為了闡釋概念對其邏輯也做了大量的簡化.通過在插入語句的同一個事務中對持久消息進行排隊,可以抓取更新用戶運行結余所需的信息.這個事務包含在同一個數據庫實例中,因此,它不會影響系統的可用性.
一個獨立的消息處理組件,會從隊列中取出每條消息,并將此信息應用到用戶表.這個例子看似解決了所有的問題,但是,還有一個問題沒有解決.為了避免排隊時發生2PC,消息是持久化在交易的主機上的.如果在涉及到用戶主機的事務中從隊列中取出消息,我們仍將遇到2PC的情景.
消息處理組件中的2PC的一種解決方案是什么都不做.通過將更新操作解耦到一個獨立的后端(back-end)組件,可以保持面向客戶的組件的可用性.業務需要或許可以接受較低的消息處理器的可用性.
不過,假定你的系統完全無法接受2PC.這個問題該如何解決呢?首先,你需要理解等冪概念.如果一個操作被應用一次或多次都能取得同樣的結果,就被認為是等冪的.等冪操作非常有用,因為它們允許局部故障,重復執行它們不會改變系統的最終狀態.
從等冪的角度看,所選的這個例子是有問題的.更新操作通常不等冪.這個例子中有累加賬戶列的操作.重復應用此操作顯然會導致錯誤的賬戶余額.然而, 即使是僅僅設定一個值的更新操作也不是等冪的,因為它還涉及到操作執行的順序.如果系統無法保證更新操作按照接收到的順序被應用,系統的最終狀態也將是不正確的.后面的內容會進一步討論此問題.
在賬戶更新的例子中,你需要一種方式來跟蹤哪些更新已經應用成功,哪些更新仍然未解決.一種技術是,使用一個表來記錄已經應用的那些交易的唯一識別號.
圖-6中展示的表會記錄交易ID、更新了哪個帳號以及應用此帳號的用戶ID.現在,我們的樣本偽代碼如圖-7所示.
圖 6
圖 7
這個例子取決于可以窺視隊列中的一條消息,并在成功處理后立即刪除此消息.如有必要,可以通過兩個獨立的事務來處理它:消息隊列上一個事務,用戶數據庫上一個事務.數據庫操作成功提交,才提交隊列操作.目前的算法可以支持局部故障,而且又能提供不依賴于2PC的事務保證.
如果只是關注更新的順序的話,還有一個更加簡單的技術可以確保等冪更新.我們來稍微調整一下我們的示例概要,來闡釋面臨的挑戰以及相應的解決方案(見圖-8).
圖 8
假設兩筆購買交易在一個很短的時間窗口內發生,我們的消息系統無法確保順序操作.您現在面臨的情況是,取決于消息被處理的順序,last_purchase可能出現一個不正確的值.幸運的是,可以通過對SQL語句做點簡單調整來解決此類更新問題, 如圖-9所描述.
圖 9
僅僅通過不允許last_purchase時間做逆向調整,就可以做到更新操作順序不相關.也可以通過這種方法來保護任何更新免遭無序更新(out-of-order update).你還可以嘗試使用單調遞增的事務ID來取代時間.
消息隊列的順序
關于順序消息投遞,下面這個簡短地附屬說明可能有用.消息系統可以提供確保消息發送的順序與接收的順序一致的能力.不過,支持此功能可能非常昂貴,通常也沒有必要,實際上,有時它也只是給出了一種虛假的安全感.
這里提供的例子闡釋了如何弱化消息的順序,并在最終仍然能夠提供一個數據庫的一致性視圖.弱化消息排序所需的開銷是名義上的,在大部分情況下,此開銷要顯著的少于在消息系統中確保消息順序的開銷.
進一步講,無論互動風格如何,Web應用在語義上都是一個事件驅動的系統.客戶端請求以任意順序達到系統.每個請求所需的處理時間要求也各不相同. 整個系統的不同組件的請求調度也是不確定的,導致了消息排隊的不確定.要求保持消息的順序給出的是一種虛假的安全感.簡單的事實是,不確定的輸入會導致不確定的輸出.
弱狀態/最終一致性(Soft State/Eventually Consistent)
到此為止,重點一直是為了可用性而權衡犧牲部分一致性.硬幣的另外一面是,理解軟狀態與最終一致性對應用設計有何影響.
由于軟件工程師傾向于認為系統是閉環(closed loop)的.從預見投入產生預見的產出方面講,我們可以這樣考慮他們行為的可預測性.這對于創建正確的軟件系統非常必要.好的消息是,在大部分情況下使用BASE不會改變一個閉環系統的可預測性,不過,它確實需要從整體上來進行審視.
一個簡單的例子就可以幫助解釋這一點.考慮這樣一個系統,用戶可以在此將資產轉移給另一個用戶.哪種類型的資產都沒有關系,它可以是錢或者游戲中的裝備.對于這個例子,我們假設,已經通過使用一個用于解耦的消息隊列,對如下兩個操作進行了解耦:從一個用戶取出資產,將資產給另一個用戶.
很快,系統就會感覺到有問題與不確定性.在資產離開一個用戶到達另一個用戶中間,有一段時間的延時. 這個時間窗口的大小由消息系統的設計所決定.無論如何,在開始狀態與結束狀態之間,始終會有一個時間間隔,在這段時間內, 看似任何用戶都不享有這筆資產.
不過,如果我們從用戶的視角來考慮這個問題,這個時間間隔可能就是無所謂的或者根本就不存在.無論是接收的用戶還是發出的用戶可能都不知道資產將在何時到達.如果在發送與接收之間的時間間隔是幾秒鐘,對于具體溝通資產轉移的用戶來講,它將是隱蔽的或確實可以忍受的.在這種狀況下,這種系統行為對用戶來講,就是一致并可接受的,即使,我們在實現中依賴了軟狀態以及最終一致性.
事件驅動架構(Event-Driven Architecture)
如果你確實需要知道,系統將在何時達到一致的狀態?你可能需要一種算法,來應用到這個狀態上,不過,僅僅在它達到一個與后續請求相關的一致狀態時才會被應用.
繼續討論前面的例子,如果在資產到達時,需要通知用戶,怎么辦? 在將資產交付給接收用戶的那個事務內創建一個事件,就可以提供一種機制,當達到一個事先確定的狀態時,可以做進一步的處理.EDA(事件驅動架構,Event-Driven Architecture)可以顯著改善可伸縮性以及架構的解耦.對于EDA應用的進一步討論超出了本文的范疇.
結論
顯著的擴展系統的交易率,需要以一種全新的方式來考慮如何對資源進行管理.當負載需要分布到大量的組件上時,傳統的事務模型會漏洞百出.對操作進行解耦,并依次對它們進行處理,可能提供更好的可用性與伸縮性,不過是以犧牲一致性為代價.BASE提供了一種模型來考慮這種解耦.
參考
- http://highscalability.com/unorthodox-approach-database-design-coming-shard.
- http://citeseer.ist.psu.edu/544596.html.
Dan Pritchett是Ebay的一位技術人員,他過去4年一直是Ebay架構團隊的成員.在此崗位上,他與Ebay市場部、Paypal以及Skype的戰略、商業、產品與技術團隊進行合作.他已經有20年技術公司的工作經歷,他服務過的公司包含Sun,HP以及硅谷圖形公司(Silicon Graphics),Pritchett具有豐富的技術經歷,從網絡層協議與操作系統到系統設計與軟件模式.他擁有密蘇里州Rolla大學的計算機科學學士學位.
文章列表
留言列表