取代ZooKeeper!高并发下的分布式一致性开源组件StateSynchronizer
StateSynchronizer是開源分布式流存儲平臺Pravega的核心組件。StateSynchronizer組件以stream為基礎(chǔ),對外提供一致性狀態(tài)共享服務(wù)。StateSynchronizer允許一組進程同時讀寫同一共享狀態(tài)而不必擔(dān)心一致性問題。本文將從共享狀態(tài)和一致性的角度出發(fā),詳細描述StateSynchronizer的整體架構(gòu)、工作機制和實現(xiàn)細節(jié)。利用stream的天然特性,StateSynchronizer可以高效地確定出更新操作的全局順序,并且從邏輯上實現(xiàn)了對共享狀態(tài)的一致性更新與存儲。由于stream訪問的高效與輕量,StateSynchronizer特別適用于高并發(fā)(\u0026gt;= 10000 clients) 的場景,并在此場景下可以作為替代ZooKeeper和etcd的解決方案。
StateSynchronizer設(shè)計者之一Flavio是著名開源組件ZooKeeper的最早作者,他同時也是《ZooKeeper:分布式過程協(xié)同技術(shù)詳解》這本書的作者。
StateSynchronizer不僅是Pravega公共API的一部分,許多Pravega內(nèi)部組件也大量依賴StateSynchronizer共享狀態(tài),如ReaderGroup的元信息管理。并且我們可以基于StateSynchronizer實現(xiàn)更高級的一致性原語,例如跨stream的事務(wù)。
開源項目地址:https://github.com/pravega/pravega/tree/v0.4.0
1 背景簡介
1.1 什么是StateSynchronizer(狀態(tài)同步器)
Pravega [1]既可以被想象成是一組流存儲相關(guān)的原語,因為它是實現(xiàn)數(shù)據(jù)持久化的一種方式,Pravega也可以被想象成是一個消息訂閱-發(fā)布系統(tǒng),因為通過使用reader,writer和ReaderGroup它可以自適應(yīng)地進行消息傳遞。本文假設(shè)讀者已經(jīng)熟悉Pravega的有關(guān)概念,否則可以參考相應(yīng)的官方文檔 [2]和已發(fā)布的4篇專欄文章(見文末鏈接)。
Pravega實現(xiàn)了各種不同的構(gòu)建模塊用以實現(xiàn)stream相關(guān)原語,StateSynchronizer [2]就是其中之一,目的在于協(xié)調(diào)分布式的環(huán)境中的各個進程^2。從功能上看,StateSynchronizer為一組進程提供可靠的共享的狀態(tài)存儲服務(wù):允許多個客戶端同時讀取和更新同一共享狀態(tài)并保證一致性語義,同時提供數(shù)據(jù)的冗余和容錯。從實現(xiàn)上看,StateSynchronizer使用一個stream為集群中運行的多個進程提供了共享狀態(tài)的同步機制,這使得構(gòu)建分布式應(yīng)用變得更加簡單。使用StateSynchronizer,多個進程可以同時對同一個共享狀態(tài)進行讀取和修改,而不必擔(dān)心一致性問題 [3]。
StateSynchronizer的最大貢獻在于它提供了一種stream原生的一致性存儲方案。由于stream具有只允許追加(Append-Only)的特性,這使得大部分現(xiàn)有的存儲服務(wù)都無法很好地應(yīng)用于stream存儲的場景。相比于傳統(tǒng)的狀態(tài)存儲方案,stream原生的存儲使得StateSynchronizer具有以下優(yōu)點:
與常見的鍵值存儲(Key/Value Store)不同,StateSynchronizer支持任意抽象的共享狀態(tài),而不僅僅局限于維護鍵值集合。
與常見的數(shù)據(jù)存儲不同,StateSynchronizer以增量的方式維護了共享狀態(tài)的整個變更歷史,而不僅僅是維護共享狀態(tài)的最新快照。這一特性不僅大大減少了網(wǎng)絡(luò)傳輸開銷,還使得客戶端可以隨時將共享狀態(tài)回滾到任意歷史時刻。
與常見的狀態(tài)存儲不同,StateSynchronizer的服務(wù)端既不存儲共享狀態(tài)本身也不負責(zé)對共享狀態(tài)進行修改,所有共享狀態(tài)的存儲和計算都只發(fā)生在客戶端本地。這一特性不僅節(jié)約了服務(wù)端的計算資源,還增加了狀態(tài)計算的靈活性,例如:除了基本的CAS(Compare-And-Swap)語義,還支持高隔離級別的復(fù)雜事務(wù)^3。
與現(xiàn)有的基于樂觀并發(fā)控制(Optimistic Concurrent Control, OCC) [4] [5]的存儲系統(tǒng)不同,StateSynchronizer可以不依賴多版本控制機制(Multi Version Concurrent Control, MVCC) [6] [7]。這意味著即使在極端高并發(fā)的場景下,狀態(tài)更新的提交也永遠不會因版本沖突而需要反復(fù)重試。
StateSynchronizer無意于也不可能在所有場景中替代傳統(tǒng)的分布式鍵值存儲組件,因為它的運行機制大量依賴stream的特性。但是,在具有stream原生存儲和較強一致性需求的場景下,StateSynchronizer可能是一種比其它傳統(tǒng)鍵值存儲服務(wù)更為高效的選擇。
1.2 “一致性”的不同語義
在不同的上下文環(huán)境中,“一致性”一詞往往有著不同的語義 [8] [9]。在分布式存儲和數(shù)據(jù)高可用(High Availability)相關(guān)的語境下,一致性通常指數(shù)據(jù)副本(Replica)的一致性 [8]:如何保證分布在不同機器上的數(shù)據(jù)副本內(nèi)容不存在沖突,以及如何讓客戶端看起來就像在以原子的方式操作唯一的數(shù)據(jù)副本,即線性化(Linearizability) [10]。常見的分布式存儲組件往往依賴單一的Leader(主節(jié)點)確定出特定操作的全局順序,例如:ZooKeeper [11]和etcd [12]都要求所有的寫操作必須由Leader轉(zhuǎn)發(fā)給其它數(shù)據(jù)副本。數(shù)據(jù)副本的一致性是分布式系統(tǒng)的難點,但卻并不是一致性問題的全部。
脫離數(shù)據(jù)副本,在應(yīng)用層的語境下,一致性通常指數(shù)據(jù)滿足某種約束條件的不變性(Invariant)[13],即:指的是從應(yīng)用程序特定的視角出發(fā),保證多個進程無論以怎樣的順序?qū)蚕頎顟B(tài)進行修改,共享狀態(tài)始終處于一種“正確的狀態(tài)”,而這種正確性是由應(yīng)用程序或業(yè)務(wù)自身定義的。例如,對于一個交易系統(tǒng)而言,無論同時有多少個交易在進行,所有賬戶的收入與支出總和始終都應(yīng)該是平衡的;又如,多進程操作(讀/寫)一個共享的計數(shù)器時,無論各進程以怎樣的順序讀寫計數(shù)器,計數(shù)器的終值應(yīng)該始終與所有進程順序依次讀寫計數(shù)器所得到的值相同。參考文獻 [8]將這種一致性歸類為“事務(wù)性的一致性(Transactional Consistency)”,而參考文獻 [9]則將此類一致性簡單稱為“涉及多對象和多操作的一致性”。應(yīng)用層的數(shù)據(jù)一致性語義與數(shù)據(jù)副本的一致性語義完全不同,即使是一個滿足線性化的分布式系統(tǒng),也需要考慮應(yīng)用層的數(shù)據(jù)一致性問題^4。
1.3\tStateSynchronizer與現(xiàn)有的一致性存儲產(chǎn)品
目前常用的分布式鍵值存儲服務(wù),例如ZooKeeper和etcd,都可以看作是一種對共享狀態(tài)進行存儲和維護的組件,即所有鍵值所組成的集合構(gòu)成了當(dāng)前的共享狀態(tài)。在數(shù)據(jù)副本層面,ZooKeeper和etcd都依賴共識(Consensus)算法提供一致性保證。ZooKeeper使用ZAB(ZooKeeper’s Atomic Broadcast)協(xié)議 [14]在各節(jié)點間對寫操作的提交順序達成共識。在廣播階段,ZAB協(xié)議的行為非常類似傳統(tǒng)的兩階段提交協(xié)議。etcd則使用Raft協(xié)議 [15]在所有節(jié)點上確定出唯一的寫操作序列。與ZAB協(xié)議不同,Raft協(xié)議每次可以確認出一段一致的提交序列,并且所有的提交動作都是隱式的。在應(yīng)用層數(shù)據(jù)層面,ZooKeeper和etcd都使用基于多版本控制機制的樂觀并發(fā)控制提供最基礎(chǔ)的一致性保證。一方面,雖然多版本控制機制提供了基本的CAS語義,但是在極端的高并發(fā)場景下仍因競爭而存在性能問題。另一方面,僅僅依靠多版本控制機制無法提供更加復(fù)雜的一致性語義,例如事務(wù)。盡管在數(shù)據(jù)副本層面,ZooKeeper和etcd都提供很強的一致性語義,但對于應(yīng)用層面的數(shù)據(jù)一致性卻還有很大的提升空間:ZooKeeper無法以原子的方式執(zhí)行一組相關(guān)操作,而etcd的事務(wù)僅支持有限的簡單操作(簡單邏輯判斷,簡單狀態(tài)獲取,但不允許對同一個鍵進行多次寫操作)。
在應(yīng)用層數(shù)據(jù)層面,ZooKeeper和etcd都使用多版本控制機制提供最基礎(chǔ)的一致性保證。例如,ZooKeeper的所有寫操作都支持樂觀并發(fā)控制:只有當(dāng)目標(biāo)節(jié)點的當(dāng)前版本與期望版本相同時,寫操作才允許成功;而etcd則更進一步,還支持非常有限的簡單事務(wù)操作。一方面,雖然多版本控制機制提供了基本的CAS語義,但是在極端的高并發(fā)場景下仍因競爭而存在性能問題。另一方面,僅僅依靠多版本控制機制無法提供更加復(fù)雜的一致性語義,例如事務(wù)。盡管在數(shù)據(jù)副本層面,ZooKeeper和etcd都提供很強的一致性語義,但對于應(yīng)用層面的數(shù)據(jù)一致性卻還有很大的提升空間:ZooKeeper無法以原子的方式執(zhí)行一組相關(guān)操作,尤其是同時操縱多個鍵;而etcd的事務(wù)僅支持非常有限的簡單操作(簡單邏輯判斷,簡單狀態(tài)獲取,但不允許對同一個鍵進行多次寫操作)。為應(yīng)用層數(shù)據(jù)提供比現(xiàn)有的分布式存儲組件更強的一致性語義(復(fù)雜事務(wù))和更高的并發(fā)度是StateSynchronizer的主要目標(biāo),尤其是在stream原生場景下,因為傳統(tǒng)的以隨機訪問為主的存儲組件很難適配stream存儲的順序特性。得益于stream的自身特性,StateSynchronizer可以不依賴樂觀并發(fā)控制和CAS語義,這意味著不會出現(xiàn)版本沖突也無需重試,從而更加適用于高并發(fā)的場景(2.2.4小節(jié))。在“無條件寫”模式下,StateSynchronizer的理論更新提交速度等價于stream的寫入速度。
與現(xiàn)有的絕大多數(shù)存儲服務(wù)不同,StateSynchronizer反轉(zhuǎn)了傳統(tǒng)的數(shù)據(jù)存儲模型(2.2.3小節(jié)):它并不存儲共享狀態(tài)本身,轉(zhuǎn)而存儲所有作用在共享狀態(tài)上的更新操作。一方面,這一反轉(zhuǎn)的數(shù)據(jù)模型直接抽象出了共享狀態(tài),使得共享狀態(tài)不再局限于簡單的鍵值存儲,而可以推廣到任意需要一致性語義的狀態(tài)。另一方面,反轉(zhuǎn)數(shù)據(jù)存儲的同時還不可避免地反轉(zhuǎn)了數(shù)據(jù)相關(guān)的操作,使得原本大量的服務(wù)端狀態(tài)計算可以直接在客戶端本地完成(2.2.1小節(jié))。這一特性不僅大大降低了服務(wù)端的資源消耗,同時也使得StateSynchronizer可以提供更靈活的更新操作和更強一致性語義:復(fù)雜事務(wù)。在StateSynchronizer的框架中,客戶端提交的所有更新操作都是以原子的方式順序執(zhí)行的,并且所有更新操作的執(zhí)行都發(fā)生在本地。從邏輯上看,每一個更新操作都等價于一個本地事務(wù)操作。這也意味著客戶端可以在更新操作中使用復(fù)雜的業(yè)務(wù)邏輯(幾乎是不受限的操作,只要操作本身的作用是確定性的)而無需擔(dān)心一致性問題。
2 實現(xiàn)細節(jié)
2.1\tStateSynchronizer的本質(zhì)
圖 1 StateSynchronizer的整體架構(gòu) [3] StateSynchronizer包括一個嵌入在應(yīng)用里的客戶端和一個用于“存儲”共享狀態(tài)的stream。
從整體架構(gòu)上看,StateSynchronizer是一個很典型的客戶端/服務(wù)器結(jié)構(gòu)(如圖 1所示):它包括一個以庫的形式(當(dāng)前版本僅支持Java)嵌入在應(yīng)用中的客戶端,以及服務(wù)器端的一個對應(yīng)stream。從概念上看,StateSynchronizer服務(wù)端負責(zé)以stream的形式“存儲”共享狀態(tài)。嚴(yán)格說來,stream存儲的是更新操作而不是共享狀態(tài)本身。2.2.3小節(jié)將對此進行更加深入的討論。
StateSynchronizer客戶端是一個輕量級的組件,它與所有其它的stream客戶端(例如reader和writer)并沒有本質(zhì)上的不同:StateSynchronizer客戶端使用標(biāo)準(zhǔn)的stream API與服務(wù)器端的stream交互,并且服務(wù)器端也并不存在任何特定于StateSynchronizer的特性或?qū)崿F(xiàn)。也就是說,StateSynchronizer客戶端具有其它stream客戶端共同的優(yōu)點,高效。所有StateSynchronizer特定的行為都是在客戶端實現(xiàn)的,服務(wù)器端僅僅用于提供stream形式的存儲媒介。StateSynchronizer的客戶端還非常精巧,核心部分的實現(xiàn)不過數(shù)百行代碼 [16]。
2.2\tStateSynchronizer的工作機制
2.2.1\t維護本地共享狀態(tài)
從概念上說,每一個StateSynchronizer都對應(yīng)一個共享狀態(tài):所有的客戶端都可以并發(fā)地對這個共享狀態(tài)進行讀寫操作,并且保持一致性。這個共享狀態(tài)既可以很簡單(例如,它可以是一個基本的數(shù)值變量),也可以很復(fù)雜(例如,它也可以是一個任意復(fù)雜的數(shù)據(jù)結(jié)構(gòu))。但是,如果從物理實現(xiàn)角度上看,根本不存在這樣一個可以被共享訪問的狀態(tài):每一個StateSynchronizer的客戶端都只在各自的本地維護著一個“共享”狀態(tài)的副本(Copy),除此以外沒有任何地方存儲這個狀態(tài)。所有的讀和寫(更新)操作都是直接作用在這個本地共享狀態(tài)副本上:讀操作直接返回本地共享狀態(tài)副本,而更新操作作用于本地共享狀態(tài)并生成新的共享狀態(tài)。
為了達到順序一致性 [8],所有共享狀態(tài)必須滿足全序(Total Order)關(guān)系 [17]。如果用符號“?”表示二元happens-before語義 [18],則任意N個狀態(tài)必須能夠確定出唯一全局順序,如下:
(1)
注意,happens-before關(guān)系必須滿足傳遞性,反自反性和反對稱性 [19]。
如果讀者閱讀過StateSynchronizer接口 [20]的實現(xiàn)類StateSynchronizerImpl,就會發(fā)現(xiàn)它有一個名為currentState的StateT類型的成員,并且StateT類型實現(xiàn)了Revisioned接口。這就是StateSynchronizer所維護的本地共享狀態(tài)副本。Revisioned接口僅有兩個成員方法:getScopedStreamName()用于獲取該狀態(tài)對應(yīng)的stream的名字,getRevison()方法用于獲取該狀態(tài)對應(yīng)的Revision(一個抽象的版本概念,也可以近似等價為Kafka的offset)。而Revision接口最終繼承了Comparable接口,允許任意兩個Revision進行比較,用于保證共享狀態(tài)的全序關(guān)系。感興趣的讀者可以繼續(xù)閱讀Revision接口的標(biāo)準(zhǔn)實現(xiàn)類RevisionImpl的compareTo()方法,就會發(fā)現(xiàn)Revision的比較實際上是基于Segment偏移量進行的。由于StateSynchronizer的底層stream僅包含一個segment,基于該segment的偏移量天然就是一個全序關(guān)系的良定義(well-defined)。
2.2.2\t更新操作的抽象模型
StateSynchronizer上的更新操作的實現(xiàn)是遞歸式的,也可以說是生成式的。StateSynchronizer的客戶端接受一個更新操作un ,將其成功持久化后(細節(jié)將在下文討論)應(yīng)用于當(dāng)前的本地共享狀態(tài)副本sn,從而生成新狀態(tài)sn+1 ,如下:
sn+1 = un(sn) (2)
從純數(shù)學(xué)的角度看,這是一個很典型的一階馬爾科夫模型/鏈(Markov Model) [21]:如果把n看作是離散的時間,那么sn就構(gòu)成了系統(tǒng)狀態(tài)隨時間遷移(Transition)的一個有序序列,并且該系統(tǒng)在任意時間點的狀態(tài)sn+1只依賴前一時刻的狀態(tài) sn ,并由當(dāng)前更新un 確定,而與任何其它狀態(tài)無關(guān)。也可以這么理解,我們假設(shè)了狀態(tài)sn 已經(jīng)包含了所有之前時刻的狀態(tài)信息。這就是所謂的馬爾科夫假設(shè)。為了啟動狀態(tài)遷移,我們規(guī)定系統(tǒng)必須具有一個起始狀態(tài)s0 ,而更新操作引起了隨后的狀態(tài)遷移。
如果從集群的視角看,有多個StateSynchronizer客戶端獨立同時運行并接受更新操作,而每個客戶端本地的共享狀態(tài)則分別經(jīng)歷著基于馬爾科夫模型的狀態(tài)遷移。為保證每個StateSynchronizer客戶端的本地共享狀態(tài)都能夠收斂于相同的最終狀態(tài),首先要求狀態(tài)遷移是確定性的(deterministic),也就是說,更新操作un 本身必須是確定性的(我們將在2.3.1小節(jié)深入討論更新操作與確定性問題)。從這個角度看,上述馬爾可夫鏈其實已經(jīng)退化成一個普通狀態(tài)機。其次,所有的StateSynchronizer客戶端必須具有相同的起始狀態(tài)s0,并且以相同的順序應(yīng)用更新un。整個集群的這種行為模式非常類似經(jīng)典的復(fù)制狀態(tài)機(Replicated State Machine)模型 [22]。復(fù)制狀態(tài)機模型是一個應(yīng)用廣泛的分布式模型,許多常見的全序廣播/原子廣播協(xié)議都是基于該模型進行的,如ZAB協(xié)議和Raft協(xié)議等。我們有意忽略了著名的Paxos協(xié)議 [23] [24],因為原生的Paxos協(xié)議并非用于解決全序廣播問題,盡管共識算法與全序廣播之間確實被證明存在等價關(guān)系 [25]。復(fù)制狀態(tài)機模型可以簡單描述如下:
在各自獨立的服務(wù)器節(jié)點上放置同一狀態(tài)機的實例;
接受客戶端請求,并轉(zhuǎn)譯成狀態(tài)機的輸入;
確定輸入的順序;
按已確定的順序在各個狀態(tài)機實例上執(zhí)行輸入;
用狀態(tài)機的輸出回復(fù)客戶端;
監(jiān)測各個狀態(tài)副本或者狀態(tài)機輸出可能出現(xiàn)的差異。
復(fù)制狀態(tài)機最核心也是最困難的部分是如何確定出一個輸入順序,以便讓每個狀態(tài)機實例都嚴(yán)格按照該順序執(zhí)行狀態(tài)遷移,從而保證一致性。從整體架構(gòu)上來說,ZAB協(xié)議和Raft協(xié)議都依賴單一的主節(jié)點確定輸入順序:所有的更新操作只能通過主節(jié)點進行,因此順序由主節(jié)點唯一確定。所不同的是,ZAB協(xié)議通過顯式的類兩階段提交方法保持廣播更新操作的原子性,而Raft協(xié)議甚至沒有顯式的提交過程,直接依賴計數(shù)的方法實現(xiàn)隱式提交。
在StateSynchronizer的場景下,狀態(tài)機實例即StateSynchronizer客戶端,輸入順序即更新操作的應(yīng)用順序,執(zhí)行狀態(tài)遷移即應(yīng)用更新操作至本地共享狀態(tài)。StateSynchronizer使用完全不同的方式解決輸入順序的確定問題,使得StateSynchronizer不需要依賴任何主節(jié)點。從嚴(yán)格意義上說,StateSynchronizer并不負責(zé)維護數(shù)據(jù)副本,但是其本地共享狀態(tài)的維護和更新模型都與數(shù)據(jù)副本有著相似之處。我們將在下文詳細討論StateSynchronizer如何確定輸入順序以及和傳統(tǒng)模型的差別。
如果讀者仔細閱讀過StateSynchronizer的源代碼,就會發(fā)現(xiàn)StateSynchronizer接口內(nèi)定義有一個名為UpdateGenerator的函數(shù)式接口。UpdateGenerator接口本質(zhì)上是一個二元消費者:它接受兩個參數(shù),其中一個是StateT類型的當(dāng)前共享狀態(tài),另一個是以List形式存在在更新操作(Update類型)列表,而列表內(nèi) 的更新操作最終都將被持久化到相應(yīng)的stream上。從概念上看,UpdateGenerator接口其實就是公式 2的等價實現(xiàn)。
2.2.3\t只存儲更新操作
在傳統(tǒng)的數(shù)據(jù)庫模型中,數(shù)據(jù)庫的服務(wù)器端負責(zé)維護一個全局的持久化的共享狀態(tài),即數(shù)據(jù)庫中所有數(shù)據(jù)所組成的一個集合。多個獨立的客戶端同時向服務(wù)器端提交更新操作(事務(wù)),更新操作作用于共享狀態(tài)上引起狀態(tài)改變,而客戶端本地不存儲任何狀態(tài)。在這個模型中,服務(wù)器端的共享狀態(tài)無論從邏輯上看還是從物理上看,它都是共享的(這與StateSynchronizer的共享狀態(tài)有很大的不同):因為幾乎所有的數(shù)據(jù)庫系統(tǒng)都允許多個事務(wù)并發(fā)執(zhí)行。從形式化的角度看,所謂“事務(wù)ui和uj是并發(fā)的”指的是它們既不滿足 ui ? uj 關(guān)系,也不滿足uj ? ui 關(guān)系,即ui 的作用對uj不完全可見,并且uj的作用對ui也不完全可見 [13]??梢圆皇呛芫_地將并發(fā)理解為:ui和uj之間無法確定順序。也可以從直覺上這樣理解:ui和uj的執(zhí)行,在時間上存在重疊部分。并發(fā)直接導(dǎo)致了數(shù)據(jù)一致性問題。傳統(tǒng)數(shù)據(jù)庫模型解決并發(fā)問題的手段是設(shè)置事務(wù)的隔離級別 [26]:并發(fā)事務(wù)在不同的隔離級別下有著不同的可見性。
StateSynchronizer擯棄了傳統(tǒng)的數(shù)據(jù)庫模型,從一個完全不同的角度解決并發(fā)問題和狀態(tài)機輸入順序問題。其核心思想是,StateSynchronizer的服務(wù)器端只存儲(持久化)了更新操作本身而不是共享狀態(tài),共享狀態(tài)由每個客戶端獨立維護,如2.2.1小節(jié)所述。由于StateSynchronizer架構(gòu)中并不存在物理上的共享狀態(tài),因此不會因為狀態(tài)共享而導(dǎo)致競爭,也不會因此產(chǎn)生并發(fā)問題。對于每一個StateSynchronizer的客戶端而言,所有的更新操作都是順序地作用于本地的共享狀態(tài)副本(物理上順序執(zhí)行),這也不存在并發(fā)問題。但是,單憑這一點還不足以保證共享狀態(tài)的一致性,除非能夠保證唯一的更新操作應(yīng)用順序。StateSynchronizer的服務(wù)器端用單segment的stream存儲了所有的更新操作:每一個更新操作作為一個event被持久化 ^5。Stream的最大特性就是只允許追加:所有的event寫入操作只允許在尾部進行(原子操作),并且一個event一旦寫入就不允許修改。這一特性不僅使得多個writer可以同時進行寫入并且保持一致性,還使得所有event的順序得以唯一確定,即每個event最終在Segment內(nèi)的相對順序。所以,對于每一個StateSynchronizer客戶端來說,都能夠看見一個一致的有序的更新操作視圖。
細心的讀者可能還希望進一步了解服務(wù)器端的stream是如保持只允許追加的特性和一致性的。與Kafka的消息代理節(jié)點(Broker)直接用本地文件系統(tǒng)存儲stream數(shù)據(jù)的方法不同,Pravega的消息代理節(jié)點將數(shù)據(jù)的存儲完全交由一個抽象的存儲層代理,包括數(shù)據(jù)副本的維護。目前已經(jīng)支持的具體存儲層實現(xiàn)包括:BookKeeper [27],HDFS [28],Extended S3 [29],NFS [30]等等。也就是說,數(shù)據(jù)副本的實現(xiàn)對消息代理節(jié)點來說是完全透明的。具體的segment分層存儲設(shè)計細節(jié)已經(jīng)超出本文的討論范圍,感興趣的讀者可以自行閱讀Pravega的相關(guān)文檔 [31]。
StateSynchronizer的這種數(shù)據(jù)模型其實非常類似Change Data Capture(CDC) [32]和Event Sourcing [33]的設(shè)計模式:不存儲系統(tǒng)狀態(tài),而是通過推導(dǎo)計算得出 [13]。以stream形式存在的更新操作其實可以看作是系統(tǒng)狀態(tài)的另一種視圖。從這一視圖出發(fā),不僅能夠推導(dǎo)出系統(tǒng)的最終狀態(tài),還可以得出系統(tǒng)在歷史任意時刻的狀態(tài)。
為了讓所有的更新操作本身都能被持久化到stream中,StateSynchronizer要求所有的更新操作都以類的形式實現(xiàn),封裝好所有所需的狀態(tài)并且支持序列化/反序列化。這一點從StateSynchronizer的接口定義上也可以反映出來:創(chuàng)建一個StateSynchronizer實例必須提供兩個Serializer接口實例,分別用于對更新操作和起始狀態(tài)作序列化/反序列化,并且UpdateGenerator接口的定義要求所有更新操作必須實現(xiàn)Update接口。
2.2.4\t更新操作的寫入模式:條件寫與無條件寫
將更新操作本身持久化到相應(yīng)的stream中是StateSynchronizer實現(xiàn)更新操作接口的重要步驟之一,因為只有這樣才能使所有的StateSynchronizer客戶端都看見一個全局唯一的更新操作序列。目前,StateSynchronizer支持以兩種不同的模式將更新操作持久化到stream端:條件寫模式(Conditionally Write)與無條件寫模式(Unconditionally Write)。這兩種更新模式分別有各自的適用場景。
圖 2 條件寫示意圖 每個矩形框代表已經(jīng)持久化到stream(右側(cè)為尾端)中的一個更新操作。實線框為已經(jīng)累積到當(dāng)前某個StateSynchronizer客戶端本地狀態(tài)的更新操作,而虛線框為尚未作用到本地狀態(tài)的更新操作,即:其它StateSynchronizer客戶端提交但尚未被當(dāng)前StateSynchronizer客戶端拉取的更新操作。兩條豎線分隔符分別對應(yīng)當(dāng)前StateSynchronizer客戶端所見的Revision以及此時真正的最新Revision。只要存在虛線框所示的更新操作,或者說只要當(dāng)前StateSynchronizer客戶端所見的Revision不是最新,那么條件寫操作就無法成功完成。
在條件寫模式下(參考StateSynchronizer接口上updateState()方法的實現(xiàn)),當(dāng)StateSynchronizer客戶端嘗試把一個更新操作寫入stream內(nèi)時需要首先檢查當(dāng)前本地的共享狀態(tài)是否是對應(yīng)stream上的最新狀態(tài)。如果是,則寫入成功,可以繼續(xù)將該更新操作作用于本地的共享狀態(tài)并更新為新狀態(tài);如果不是,說明已經(jīng)有其它的客戶端搶先往stream中寫入了其它更新操作,此時本地的共享狀態(tài)已經(jīng)“過期”,本次寫入失敗,如圖 2所示。對于寫入失敗的情況,StateSynchronizer會自動嘗試從stream拉取所有缺失的更新,并將所有拉取到的更新順序作用于當(dāng)前本地共享狀態(tài)以便將其更新到最新狀態(tài),然后重試條件寫。這一“失敗-重試”的過程可能重復(fù)多次,直至寫入成功。從概念上看,條件寫表現(xiàn)出的行為與多線程編程中的CAS操作有著諸多相似之處。
如果讀者仔細思考條件寫的實現(xiàn)細節(jié),不難得出如下的結(jié)論:檢查狀態(tài)是否過期與實際的stream寫入動作必須是一個整體的原子操作,否則將出現(xiàn)競爭條件。事實上,檢查狀態(tài)是否過期這一動作并不是在客戶端進行的,而是由stream的相關(guān)接口直接代理的,否則很難與發(fā)生在服務(wù)器端的寫入動作合并為一個原子操作。在閱讀過StateSynchronizer實現(xiàn)類StateSynchronizerImpl的源代碼之后,讀者會發(fā)現(xiàn)一個名為client的RevisionedStreamClient類型成員。RevisionedStreamClient是StateSynchronizer客戶端用來與后端stream交互的唯一入口,所有stream的讀寫操作都通過該接口進行,包括條件寫。RevisionedStreamClient接口上有一個名為writeConditionally()的方法(即條件寫的真正實現(xiàn)),允許在寫入一個event的同時指定一個Revision。正如其名字所暗示的那樣,Revision接口可以近似理解為stream的“版本”:每次成功的寫入操作都會導(dǎo)致對應(yīng)stream的Revision發(fā)生變化,writeConditionally()方法甚至還直接返回該Revision以方便客戶端用作多版本并發(fā)控制?,F(xiàn)在繼續(xù)討論writeConditionally()方法的行為,只有當(dāng)stream的當(dāng)前的實際Revision與指定的Revision相同時(即:從上次成功條件寫入到目前為止都沒有其它的成功寫入發(fā)生),真正的寫入動作才發(fā)生,否則寫入失敗。很明顯,這是一個典型的樂觀并發(fā)控制模式。
聰明的讀者甚至還可以從物理實現(xiàn)角度理解Revision。從2.2.1小節(jié)的討論中我們知道,Revision是基于segment內(nèi)的偏移量實現(xiàn)的,而segment本質(zhì)上就是一個無邊界的字節(jié)流。所謂stream的“版本”其實就是stream當(dāng)前尾端的偏移量。由于stream只允許追加的特性,往指定偏移位置執(zhí)行寫入操作時,只有當(dāng)該偏移確實處于尾端時才能成功。圖 2中所標(biāo)記的Revision既可以看作是當(dāng)前本地共享狀態(tài)所對應(yīng)的stream版本,也可以看作是當(dāng)前StateSynchronizer客戶端所看見的stream尾部位置。從這個角度看,stream的特性和操作得到了統(tǒng)一。
由于條件寫的失敗-重試機制,在某些極端場景下(例如更新操作極度頻繁引起的激烈競爭),可能導(dǎo)致較多次數(shù)的重試。并且由于條件寫操作目前并未實現(xiàn)公平機制,理論上可能出現(xiàn)某個客戶端“饑餓”的情況。為應(yīng)對這種場景,StateSynchronizer還提供了另一種持久化模式:無條件寫模式。在無條件寫模式下(參考StateSynchronizer接口上updateStateUnconditionally()方法的實現(xiàn)),StateSynchronizer客戶端往stream寫入更新操作時并不會要求比較Revision,而是無條件地將該更新操作寫入當(dāng)前stream的實際尾端,并且在寫入成功后也不會更新本地的共享狀態(tài)。從實現(xiàn)上看,無條件寫模式下的更新動作其實就是一個簡單的stream追加動作。在服務(wù)和資源正常的情況下,stream的追加寫入總是能夠成功的。如果調(diào)用者希望得到更新操作作用后的共享狀態(tài),則還需要手動拉取一次更新(參考StateSynchronizer接口上的fetchUpdates()方法)。由于更新操作的件寫入動作與拉取動作之間存在時間窗口,在這段時間內(nèi)可能已經(jīng)有其它的客戶端繼續(xù)寫入新的更新操作。因此,在拉取得到的更新操作序列上,并不能保證之前提交的更新操作是該序列上的最后一個元素。也就是說,在應(yīng)用該更新操作之前和之后,可能有其它的更新操作已經(jīng)作用或繼續(xù)作用在當(dāng)前本地共享狀態(tài)上。相反,條件寫模式卻總是能保證所提交的更新一定是最后一個作用在當(dāng)前本地共享狀態(tài)上的操作。根據(jù)具體應(yīng)用場景的不同,這可能是個問題,也可能不是。例如,在無條件寫模式下,所有的更新操作現(xiàn)在都變得不可觀測了:假設(shè)你執(zhí)行了一個無條件的更新操作,往一個共享的集合里面添加了一個元素?,F(xiàn)在,哪怕你立刻進行集合遍歷,也不能保證你一定能夠找到剛剛添加的元素,因為可能存在其它客戶端提交的后續(xù)更新操作已經(jīng)將剛剛添加的元素刪除了。這恐怕是一種與直覺相違背的行為表現(xiàn)??傊?#xff0c;與條件寫相比,無條件寫有著優(yōu)異的并發(fā)性能,但是這一切都是有代價的,例如:犧牲了開發(fā)者的可理解性。
2.3\t其它問題
2.3.1\t更新操作與確定性
StateSynchronizer的更新操作模型(2.2.2小節(jié))要求所有更新操作的實現(xiàn)必需是確定性的,因為所有的更新操作都會在每一個StateSynchronizer客戶端被重放。對于相同的輸入,如果更新操作本身不能夠產(chǎn)生確定性的結(jié)果,即使以完全相同的順序在每一個客戶端被執(zhí)行,也會破壞共享狀態(tài)的最終一致性。根據(jù)實際業(yè)務(wù)場景的不同,這一要求可能是一個問題,也可能不是,例如:
不可以使用隨機函數(shù)。這一看似簡單的要求實際上限制了不少可能性,很多科學(xué)計算依賴隨機函數(shù)。
不可以使用絕大多數(shù)的本地狀態(tài),例如:本地時間,本機硬件信息等。
引用任何外部系統(tǒng)的狀態(tài)都需要格外小心可能引入的不一致。例如,如果一個外部系統(tǒng)的狀態(tài)會隨時間變化,各個客戶端可能看到各不相同的外部狀態(tài),因為同一個更新操作在每個客戶端被執(zhí)行的時間點是不確定的。
除了保證更新操作的確定性之外,還需要特別注意更新操作的執(zhí)行是否具有“副作用”,例如:引發(fā)全局狀態(tài)或外部系統(tǒng)狀態(tài)的改變。如果回答是肯定的,那么還需要特別注意這些引發(fā)狀態(tài)改變的動作接口是否具有冪等性 [34],因為同一個更新操作不僅會在每個客戶端被執(zhí)行,即使在同一客戶端也可能被執(zhí)行多次(2.2.4小節(jié))。
2.3.2\t更新操作與更新丟失問題
有人擔(dān)心StateSynchronizer是否存在丟失更新問題 [6]。丟失更新問題一般在如下場景發(fā)生:兩個進程并發(fā)地對同一共享變量進行“讀取-修改-寫入”組合操作。如果這一組合操作不能夠被作為一個原子操作完成,那么后寫入的狀態(tài)有可能覆蓋另一個寫入操作的結(jié)果,導(dǎo)致其中一個修改結(jié)果(更新)“丟失”。如2.2.3小節(jié)所述,所有的更新操作都是在StateSynchronizer的客戶端本地順序執(zhí)行的,因此不存在并發(fā)修改共享狀態(tài)的場景,也不會產(chǎn)生更新丟失問題。
雖然StateSynchronizer客戶端保證了以并發(fā)安全的方式執(zhí)行所有更新操作,但是,一個不正確實現(xiàn)的更新操作仍有可能導(dǎo)致更新丟失問題。如果一個應(yīng)用需要實現(xiàn)“讀取-修改-寫入”組合操作,唯一正確的做法是將所有的讀取,修改和寫入動作都封裝在同一個更新操作中,即按如下偽代碼所示實現(xiàn)更新操作un :
un:
\u0026gt; 讀取狀態(tài)sn;
\u0026gt; 執(zhí)行修改;
\u0026gt; 生成并返回新狀態(tài)sn+1;
源代碼 1 用偽代碼表示的更新操作一般實現(xiàn)
一種常見的錯誤是在更新操作un外部進行“讀取狀態(tài)sn”和“執(zhí)行修改”動作,并將新狀態(tài)sn+1直接封裝進更新操作un。另一種不那么直觀的錯誤是,盡管將“讀取”,“修改”和“寫入”動作都封裝進了同一個更新操作,但是在進行“讀取狀態(tài)sn”動作時有意或無意地使用了某種緩存機制,即并非每次都從StateSynchronizer獲取當(dāng)前共享狀態(tài)sn。這兩種錯誤的實現(xiàn)都將導(dǎo)致很嚴(yán)重的丟失更新問題。2.2.4小節(jié)的相關(guān)討論解釋了其中的原因:由于條件寫操作可能失敗并重試多次,并且每次重試都意味著StateSynchronizer客戶端本地的共享狀態(tài)已經(jīng)改變,任何緩存或者等價的行為都將導(dǎo)致實際的“執(zhí)行修改”動作作用在一個已經(jīng)過期的舊狀態(tài)上,從而導(dǎo)致丟失更新問題。
2.3.3\t更新操作的順序執(zhí)行與性能
在每一個StateSynchronizer客戶端上,所有的更新操作都是順序執(zhí)行并作用在本地共享狀態(tài)上的,正所謂“解決并發(fā)問題最簡單的辦法就是完全消除并發(fā)” [13]。有人擔(dān)心更新操作的順序執(zhí)行是否會顯著降低系統(tǒng)性能。從目前已有的研究看,用單線程的方式執(zhí)行所有事務(wù)是完全可行的 [35],并且在很多現(xiàn)有的數(shù)據(jù)庫實現(xiàn)中已經(jīng)被采用,例如:VoltDB/H-Store [36],Redis [37],Datomic [38] [39]等。當(dāng)然,這對事務(wù)本身以及數(shù)據(jù)集都有所要求 [13],例如:
每個事務(wù)必須足夠小,并且足夠快。
數(shù)據(jù)集的活躍部分必須足夠小,以便能夠全部載入物理內(nèi)存。否則,頁面的頻繁換入和換出會引起大量的磁盤IO操作,導(dǎo)致事務(wù)頻繁阻塞。
寫操作的吞吐量必須足夠小,以便單CPU核心可以有足夠的能力處理。否則,CPU運算能力將成為瓶頸。
對于一個StateSynchronizer應(yīng)用來說,無論是共享狀態(tài)還是更新操作的設(shè)計實現(xiàn),都必須遵循上述要求。
2.3.4\t歷史重放與狀態(tài)壓縮
每一個StateSynchronizer客戶端在進行啟動后的首次更新操作時,都需要從對應(yīng)的stream拉取所有的歷史更新操作,并重放這些操作以便得到當(dāng)前最新的共享狀態(tài)。如果這是一個長時運行的共享狀態(tài),那么stream內(nèi)此時可能已經(jīng)累積了相當(dāng)數(shù)量的更新操作。拉取并重放所有這些更新操作可能需要消耗大量的時間與資源,造成首次更新性能低下。為了應(yīng)對這種場景,StateSynchronizer還提供了所謂的狀態(tài)壓縮機制。狀態(tài)壓縮(compact)是一個特殊的StateSynchronizer接口方法,它允許將StateSynchronizer客戶端的本地共享狀作為一個新的起始狀態(tài),用條件寫模式重新寫入stream^6,并且使用stream的mark機制標(biāo)記該起始狀態(tài)的最新位置^7。StateSynchronizer客戶端每次拉取更新操作時,都會首先嘗試使用mark機制定位到最新的起始狀態(tài)并忽略所有之前的更新操作,從而避免了長時間的歷史重放。
如果首次更新操作的性能對于應(yīng)用程序來說非常重要,那么開發(fā)者可以選擇周期性地進行狀態(tài)壓縮。那么首次更新操作所要拉取和應(yīng)用的更新操作數(shù)量則不會多于一個周期內(nèi)所累積的更新操作數(shù)量,這將大大提升首次更新操作的性能。
3\t總結(jié)
本文主要從狀態(tài)共享和一致性的角度出發(fā),詳細描述了Pravega的狀態(tài)同步組件StateSynchronizer的工作機制和實現(xiàn)細節(jié)。StateSynchronizer支持分布式環(huán)境下的多進程同時讀寫共享狀態(tài),并提供一致性保證。StateSynchronizer具有典型的客戶端/服務(wù)器架構(gòu),但是卻非常輕量和高效,因為服務(wù)器端僅僅用于提供存儲媒介。StateSynchronizer的核心工作機制可以歸納為兩個關(guān)鍵點:維護本地共享狀態(tài)和只存儲更新操作本身。StateSynchronizer利用stream的天然特性實現(xiàn)了更新操作的全局有序。StateSynchronizer還提供了條件寫和無條件寫兩種更新寫入模式,可以適用于并發(fā)度極高的場景。StateSynchronizer未來的工作可能集中在如何向開發(fā)者提供更加便捷易用的編程接口,以減輕開發(fā)者的負擔(dān)。
Pravega系列文章計劃
Pravega根據(jù)Apache 2.0許可證開源,0.4版本已于近日發(fā)布。我們歡迎對流式存儲感興趣的大咖們加入Pravega社區(qū),與Pravega共同成長。本篇文章為Pravega系列第五篇,系列文章如下:
實時流處理(Streaming)統(tǒng)一批處理(Batch)的最后一塊拼圖:Pravega
開源Pravega架構(gòu)解析:如何通過分層解決流存儲的三大挑戰(zhàn)?
Pravega應(yīng)用實戰(zhàn):為什么云原生特性對流存儲至關(guān)重要
“ToB” 產(chǎn)品必備特性: Pravega的動態(tài)彈性伸縮
高并發(fā)下新的分布式一致性解決方案(StateSynchronizer)
Pravega的僅一次語義及事務(wù)支持
與Apache Flink集成使用
作者簡介
蔡超前:華東理工大學(xué)計算機應(yīng)用專業(yè)博士研究生,現(xiàn)就職于Dell EMC,6年搜索和分布式系統(tǒng)開發(fā)以及架構(gòu)設(shè)計經(jīng)驗,現(xiàn)從事流相關(guān)的設(shè)計與研發(fā)工作。
滕昱:現(xiàn)就職于Dell EMC非結(jié)構(gòu)化數(shù)據(jù)存儲部門 (Unstructured Data Storage)團隊并擔(dān)任軟件開發(fā)總監(jiān)。2007年加入Dell EMC以后一直專注于分布式存儲領(lǐng)域。參加并領(lǐng)導(dǎo)了中國研發(fā)團隊參與兩代Dell EMC對象存儲產(chǎn)品的研發(fā)工作并取得商業(yè)上成功。從 2017年開始,兼任Streaming存儲和實時計算系統(tǒng)的設(shè)計開發(fā)與領(lǐng)導(dǎo)工作。
參考文獻
[1] \t“Pravega,” Dell EMC, [Online]. Available: https://github.com/pravega/pravega.
[2] \t“Working with Pravega: State Synchronizer,” Dell EMC, [Online]. Available: https://github.com/pravega/pravega/blob/master/documentation/src/docs/state-synchronizer.md.
[3] \t“Pravega Concepts,” Dell EMC, [Online]. Available: https://github.com/pravega/pravega/blob/master/documentation/src/docs/pravega-concepts.md.
[4] \tH. T. Kung and J. T. Robinson, “On optimistic methods for concurrency control,” ACM Transactions on Database Systems, vol. 6, no. 2, pp. 213-226, 1981.
[5] \tP. A. Bernstein and N. Goodman, “Concurrency Control in Distributed Database Systems,” ACM Computing Surveys, vol. 13, no. 2, pp. 185-221, 1981.
[6] \t“Concurrency Control,” Wikipedia, [Online]. Available: https://en.wikipedia.org/wiki/Concurrency_control.
[7] \t“Multiversion Concurrency Control,” Wikipedia, [Online]. Available: https://en.wikipedia.org/wiki/Multiversion_concurrency_control.
[8] \tP. Viotti and M. Vukoli?, “Consistency in Non-Transactional Distributed Storage Systems,” ACM Computing Surveys (CSUR), vol. 49, no. 1, 2016.
[9] \tP. Bailis, A. Davidson, A. Fekete, A. Ghodsi, J. M. Hellerstein and I. Stoica, “Highly available transactions: virtues and limitations,” in Proceedings of the VLDB Endowment, 2013.
[10] \tM. P. Herlihy and J. M. Wing, “Linearizability: a correctness condition for concurrent objects,” ACM Transactions on Programming Languages and Systems (TOPLAS) , vol. 12, no. 3, pp. 463-492, 1990 .
[11] \t“Apache ZooKeeper,” [Online]. Available: https://zookeeper.apache.org/.
[12] \t“etcd (GitHub Repository),” [Online]. Available: https://github.com/etcd-io/etcd.
[13] \tM. Kleppmann, Designing Data-Intensive Applications, O’Reilly Media, 2017.
[14] \tF. P. Junqueira, B. C. Reed and M. Sera?ni, “Zab: High-performance broadcast for primary-backup systems,” In DSN, pp. 245-256, 2011.
[15] \tD. Ongaro and J. Ousterhout, “In search of an understandable consensus algorithm,” in Proceedings of the 2014 USENIX conference on USENIX Annual Technical Conference, Philadelphia, 2014.
[16] \t“StateSynchronizer Related Source Code in Pravega GitHub Repository,” Dell EMC, [Online]. Available: https://github.com/pravega/pravega/tree/master/client/src/main/java/io/pravega/client/state.
[17] \tM. Hazewinkel, Ed., Encyclopaedia of Mathematics (set), 1 ed., Springer Netherlands, 1994.
[18] \tL. Lamport, “Time, clocks, and the ordering of events in a distributed system,” Communications of the ACM, vol. 21, no. 7, pp. 558-565, 1978.
[19] \t“Happened-before,” Wikipedia, [Online]. Available: https://en.wikipedia.org/wiki/Happened-before.
[20] \t“StateSynchronizer Interface Definition (v0.4),” Dell EMC, [Online]. Available: https://github.com/pravega/pravega/blob/r0.4/client/src/main/java/io/pravega/client/state/StateSynchronizer.java.
[21] \tP. A. Gagniuc, Markov Chains: From Theory to Implementation and Experimentation, New Jersey: John Wiley \u0026amp; Sons, 2017.
[22] \tF. B. Schneider, “Implementing fault-tolerant services using the state machine approach: a tutorial,” ACM Computing Surveys, vol. 22, no. 4, pp. 299-319, 1990.
[23] \tL. Lamport, “The part-time parliament,” ACM Transactions on Computer Systems, vol. 16, no. 2, pp. 133-169, 1998.
[24] \tL. Lamport, “Paxos Made Simple,” SIGACT News, vol. 32, no. 4, pp. 51-58, 2001.
[25] \tX. Défago, A. Schiper and P. Urbán, “Total order broadcast and multicast algorithms: Taxonomy and survey,” ACM Computing Surveys, vol. 36, no. 4, pp. 372-421, 2004.
[26] \tH. Berenson, P. Bernstein, J. Gray, J. Melton, E. O’Neil and P. O’Neil, “A critique of ANSI SQL isolation levels,” in Proceedings of the 1995 ACM SIGMOD international conference on Management of data, San Jose, California, USA, 1995.
[27] \t“Apache BookKeeper,” [Online]. Available: https://bookkeeper.apache.org/.
[28] \t“Apache Hadoop,” [Online]. Available: https://hadoop.apache.org/.
[29] \t“Amazon S3,” Amazon, [Online]. Available: https://aws.amazon.com/s3/.
[30] \t“NFS version 4.2 (RFC 7862),” [Online]. Available: https://tools.ietf.org/html/rfc7862.
[31] \t“Pravega Segment Store Service (v0.4),” Dell EMC, [Online]. Available: https://github.com/pravega/pravega/blob/r0.4/documentation/src/docs/segment-store-service.md.
[32] \t“Change Data Capture,” Wikipedia, [Online]. Available: https://en.wikipedia.org/wiki/Change_data_capture.
[33] \tM. Fowler, “Event Sourcing,” 12 12 2005. [Online]. Available: https://martinfowler.com/eaaDev/EventSourcing.html.
[34] \t“Idempotence,” Wikipedia, [Online]. Available: https://en.wikipedia.org/wiki/Idempotence.
[35] \tM. Stonebraker, S. Madden and D. J. Abadi, “The End of an Architectural Era (It’s Time for a Complete Rewrite),” in Proceedings of the 33rd international conference on Very large data bases, Vienna, 2007.
[36] \tR. Kallman, H. Kimura and J. Natkins, “H-Store: A High-Performance, Distributed Main Memory Transaction Processing System,” Proceedings of the VLDB Endowment, vol. 1, no. 2, pp. 1496-1499, 2008.
[37] \t“Redis,” [Online]. Available: https://redis.io/.
[38] \tR. Hickey, “The Architecture of Datomic,” 2 11 2012. [Online]. Available: https://www.infoq.com/articles/Architecture-Datomic.
[39] \t“Datomic Cloud,” Cognitect, Inc., [Online]. Available: https://www.datomic.com/.
更多內(nèi)容,請關(guān)注AI前線
總結(jié)
以上是生活随笔為你收集整理的取代ZooKeeper!高并发下的分布式一致性开源组件StateSynchronizer的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从零开始学习PYTHON3讲义(二)把P
- 下一篇: ECMS系统服务器死机的处理,内存问题服