【譯】Object Storage on CRAQ 上篇
阿新 • • 發佈:2020-09-07
## 摘要
大型儲存系統通常會在許多可能出故障的元件上進行資料複製和資料分割槽,從而保證可靠性和可擴充套件性。但是許多商業部署系統為了實現更高的可用性和吞吐量,犧牲了強一致性,特別是那些實時互動系統。
本論文介紹了CRAQ的設計、實現和評估。CRAQ是一個挑戰上述僵化取捨的分散式物件儲存系統。我們的基本方法是對鏈式複製進行改性,在保證強一致性的同時,大幅提高讀取吞吐量。通過在所有物件副本上分配負載,CRAQ可以隨鏈的大小線性擴充套件,而無需增加一致性協調。同時,為了滿足某些應用程式的需求,CRAQ提供了弱一致性保證,這在系統處於高故障時期尤其有用。本文探討了為跨多個數據中心進行地理複製的CRAQ儲存而進行的額外設計和實現,從而提供優化區域性性的操作。本文也討論了多物件原子更新和大物件更新的多播優化。
## 1. 引言
許多線上服務需要基於物件的儲存,將資料作為整個單元呈現給應用程式。物件儲存支援兩個基本原語:*讀*(或查詢)操作返回以物件名稱儲存的資料塊,*寫*(或更新)操作修改單個物件的狀態。這一類基於物件的儲存由鍵值資料庫(例如BerkeleyDB或Apache的CouchDB)支援,並部署到商業資料中心(例如Amazon的Dynamo,Facebook的Cassandra以及Memcached)。為了在這類系統中實現可靠性、負載平衡和可擴充套件性,物件名稱空間在許多機器上進行了分割槽,每個資料物件都被複制了幾次。
當應用程式有某些特殊需求時,基於物件的系統會比其對應的檔案系統更具吸引力。與分層目錄結構相反,物件儲存更適合於水平名稱空間,例如在鍵值資料庫那樣。物件儲存簡化了支援整個物件修改的流程。並且,它們通常只需要推理某個指定物件的修改順序即可,而不是整個儲存系統;為每個物件提供一致性比而不是為所有操作和/或物件,代價要低的多。
在構建作為眾多應用程式基礎的儲存系統時,商業站點將高效能和高可用的需求放在首位。複製資料是為了承受單個節點甚至整個資料中心的故障帶來的威脅,無論這個故障是計劃內的還是計劃外的。確實在新聞媒體中處處可見資料中心離線導致期間整個網站都被關閉了的例子。對可用性和效能的高度關注,導致許多商業系統由於感知成本而犧牲了強一致性語義(例如Google、Amazon、eBay、Facebook等)。
Van Renesse和Schneider近期提出了一種為物件儲存在故障停止伺服器上的鏈式複製方法,該方法旨在提供強一致性的同時提高吞吐量。基本方法是將所有儲存物件的節點組織在一條鏈中,其中鏈的尾節點處理所有讀取請求,而鏈的頭節點處理所有寫入請求。在客戶端收到確認之前,寫操作沿鏈向下傳播,因此尾節點可以得到所有物件操作的執行順序,具有強一致性。該方法沒有任何複雜或多輪通訊的協議,但是提供了簡單、高吞吐量和容易故障恢復的特性。
不幸的是,基礎的鏈複製方法有一些侷限性。對一個物件的所有讀取都在頭節點,從而導致潛在的熱點問題。雖然可以通過一致性雜湊方法或更中心化的目錄方法將叢集中的節點組織到多個鏈中,以實現更好的負載均衡,但是如果特定物件訪問較少,這些演算法仍然可能會負載不平衡,這在實踐中是一個真實的問題。當嘗試跨多個數據中心構建鏈式,甚至可能出現更嚴重的問題,因為所有的讀取操作都可以由一個遠距離節點(鏈的尾節點)處理。
本文介紹了CRAQ的設計、實現和評估,CRAQ是一個物件儲存系統,在保持鏈式複製的強一致性特性的同時,通過支援分配查詢為讀取操作提供了較低的延遲和較高的吞吐量;分配查詢指的是將讀取操作分配給鏈中的所有節點執行,而不是所有操作都由單個主節點處理。本文的主要貢獻如下:
1. CRAQ使任何鏈節點都能在保持強一致性的同時處理讀操作,從而支援儲存物件在所有節點之間的負載平衡。此外,當大多數工作負載是讀取操作時,(例如GFS和Memcached系統中做的假設),CRAQ的效能可以和僅提供最終一致性的系統相媲美。
2. 除了強一致性外,CRAQ的設計還自然支援讀操作之間的最終一致性,從而降低寫操作期間的等待時間,並在短暫的分割槽期間降級為只讀。CRAQ允許應用程式指定讀取操作可接受的最大陳舊度。
3. 利用負載均衡的特性,我們介紹了一種廣域系統設計,用於在跨地理位置的叢集中構建CRAQ鏈,並保留了強區域性性。具體而言,讀操作可以由本地叢集進行處理,在最壞情況下(高寫爭用的時候),需要在廣域網中傳輸簡短的元資料資訊。我們還介紹了使用Zookeeper(一種類似於PAXOS的組成員系統)來管理部署。
最後,我們討論了CRAQ的其他擴充套件,包括將微事務整合到多物件原子更新中,以及使用多播來提高大物件更新的寫入效能。但是,我們尚未完成這些優化的實現。
CRAQ的初步效能評估顯示,與基礎的鏈式複製方法相比,它具有更高的吞吐量,在大多數負載都是讀操作的情況下,吞吐量與節點的數量成正比:三節點的鏈可以提升約200%的吞吐量,七節點的鏈可以提升約600%的吞吐量。在高寫爭用的情況下,CRAQ在三節點的鏈中的讀取吞吐量仍然比基礎的鏈式複製高出兩倍,並且讀取延遲較低。我們總結了CRAQ在各種工作負載和故障情況下的效能。最後,我們評估了CRAQ在跨地域複製方面的效能,證明其延遲遠低於基礎鏈式複製方法的延遲。
本文的剩餘部分安排如下,第2節介紹了基礎鏈式複製與CRAQ協議之前的對比,以及CRAQ的最終一致性支援。第3節介紹了CRAQ在單資料中心和跨資料中心擴充套件到多條鏈的方法,以及管理鏈和節點的組成員服務。第4節涉及到諸如多物件更新和利用多播等擴充套件。第5節介紹了CRAQ的實現,第6節展示了CRAQ的效能評估,第7節回顧了相關工作,第8節進行總結。
## 2. 基礎系統模型
本節介紹了我們基於物件的介面和一致性模型,簡要概述了標準的鏈式複製模型,然後介紹了強一致的CRAQ模型及其變體。
### 2.1 介面和一致性模型
基於物件的儲存系統為使用者提供了兩個簡單的原語:
- $write(objID, V)$: 寫(更新)操作儲存與物件識別符號$objID$關聯的值$V$。
- $V\leftarrow read(objID)$: 讀(查詢)操作檢索與物件識別符號$objID$關聯的值$V$。
我們將討論關於單個物件的兩種主要的一致性型別。
- **強一致性** 我們系統中的強一致性保證對於單個物件的所有的讀和寫操作均按一定順序執行,並且對於單個物件的讀始終能看到最新的值。
- **最終一致性** 我們系統中的最終一致性意味著對單個物件的寫入仍然按一定順序應用於所有的節點,但是對於不同的節點,最終一致性讀可能在一段時間內返回舊資料(即在寫入被應用到所有節點之前)。但是,一旦所有的副本接收到寫入請求後,讀操作將永遠不會返回比最近提交的寫入版本更舊的版本。實際上,如果客戶端保持與一個特定節點的會話(儘管不是與不同的節點的會話),則會看到單調的讀一致性(作者注:即對於一個物件的讀取將返回相同的先前的值或一個更新的值,但是絕不會返回舊版本的值)。
接下來,我們介紹一下鏈式複製和CRAQ是如何提供強一致性的。
### 2.2 鏈式複製
鏈式複製(CR)是一種在多節點之間複製資料的方法,提供了強一致性的儲存介面。節點組成一條長度為$C$的*鏈*。鏈的*頭節點*處理來自客戶端的所有*寫*操作。當節點收到寫操作的請求時,他會繼續傳播給鏈中的下一個節點。一旦寫操作請求到達尾節點,該操作就已經被應用到了鏈中的所有副本中,此時認為該寫操作*已提交*。尾節點處理所有的讀操作,因此只有已提交的值才會被返回。
![](https://img2020.cnblogs.com/blog/2119462/202009/2119462-20200907191052824-1178721852.png)
圖1提供了一個長度為四的鏈的例項。所有讀請求的到達和處理都在尾節點。寫請求到達鏈的頭部,並向下傳播到尾部。當尾節點提交寫操作後,向客戶端傳送回覆。CR論文中介紹由尾節點直接向客戶端傳送訊息;由於我們使用TCP,因此我們的實現實際由頭部節點複用之前與客戶端的連線,在收到尾節點的確認後直接進行響應。確認回傳在圖中用虛線表示。
CR簡單的拓撲結構使寫操作比其他提供強一致性的協議成本更低。多個併發寫入可以在鏈中進行流水線傳輸,傳輸成本平攤在所有節點上。之前工作的模擬結果顯示,與主/備複製相比,CR具有更高的吞吐量,同時還能更快、更容易地恢復。
鏈式複製實現了強一致性:由於所有的讀都在尾部進行,並且所有的寫入只有當到達尾部後才提交,因此鏈的尾部可以按序應用所有的操作。然而,這的確要付出一些代價,因為只有一個節點處理讀操作,因此降低了讀操作的吞吐量,無法隨著鏈的長度增加而進行擴充套件。但是這是有必要的,因為查詢中間節點可能為違反強一致性保證;特別是,在傳播過程中,對不同節點的併發讀取可能會看到不同的寫入值。
儘管CR專注於提供儲存服務,但也可以將其查詢/更新協議視為複製狀態機的介面。儘管本文的剩餘部分僅從讀/寫物件儲存介面兩個角度考慮問題,但可以用類似的角度看待CRAQ。
### 2.3 分攤查詢的鏈式複製
當前只讀工作負載環境大受歡迎,因此CRAQ試圖通過允許鏈中的任意節點都來處理讀操作,同時仍提供強一致性保證,來提高吞吐量。CRAQ主要的擴充套件如下:
1. CRAQ中的單個節點允許儲存物件的多個版本,每個版本都包含一個單調遞增的版本號以及一個附加屬性:該版本是髒的還是乾淨的。所有的版本初始化標記為乾淨。
2. 當節點收到物件的新版本時(通過沿鏈路向下傳播的寫操作),該節點將此最新版本附加到該物件的列表中。
- 如果該節點不是尾節點,則將該版本標記為髒,並將寫操作傳播到後繼節點。
- 如果該節點是尾節點,則將版本標記為乾淨,此時我們將物件版本稱為*已提交*。然後,尾節點可以通過在鏈中反向傳播確認來通知所有其他節點此次提交。
3. 當節點接收到某個物件版本的*確認*訊息時,該節點會將該物件版本標記為乾淨。然後,該節點就可以刪除該物件的所有先前版本。
4. 當節點收到對物件的讀取請求時:
- 如果最新已知的版本是乾淨的,則節點將返回該值。
- 如果最新已知的版本是髒的,該節點會與尾節點進行通訊,查詢尾節點最後提交的版本號。然後節點返回該物件的版本;按照規則,可以確保該節點儲存了該版本的物件。我們注意到,儘管尾節點可以在它回覆版本請求和中間節點向客戶端傳送回覆之前提交新版本,但是這不違反強一致性的定義,因為讀操作從尾節點來說是序列化的。
請注意,如果節點收到寫提交的確認後立即刪除舊版本,則也可以隱式確定節點上的物件的狀態是髒還是乾淨。也就是說如果節點中的物件只有一個版本,那麼該物件是乾淨的;否則,物件是髒的,必須從尾節點檢索正確的版本。
![](https://img2020.cnblogs.com/blog/2119462/202009/2119462-20200907191107578-1951949611.png)
![](https://img2020.cnblogs.com/blog/2119462/202009/2119462-20200907191117721-1621149115.png)
圖2顯示了處於初始乾淨狀態的CRAQ鏈。每個節點都儲存物件的相同副本,因此到達鏈中任何節點的任何讀請求都將返回相同的值。除非收到寫請求,否則所有節點都將保持在乾淨狀態。
圖3顯示了寫操作的傳播過程(由紫色虛線顯示)。頭節點收到寫入該物件的新版本($V_2$)的初始訊息,因此頭節點的物件狀態是髒的。然後,頭節點將寫訊息沿著鏈向下傳播到第二個節點,該節點也將該物件標記為髒(物件$K$有多個版本$[V_1,V_2]$)。如果一個處於乾淨狀態的節點收到讀請求,它們將立即返回該物件的舊版本:這是正確的,因為新版本尚未在尾節點提交新版本。但是,如果兩個髒節點中的其中一個收到讀請求,它們會向尾節點發送版本查詢請求(圖中使用藍色的虛線箭頭顯示),尾節點將返回被請求物件的已知版本號。然後,髒節點返回與指定版本號相關聯的舊物件值($V_1$)。因此,即使有多個未完成的寫操作在鏈中傳播,鏈中的所有節點仍將返回同一版本的物件。
當尾節點收到並接受寫入請求時,它會在鏈上傳送包含此寫入版本號的確認訊息。每個前繼節點收到確認後,將此版本號標記為乾淨(可能刪除所有較舊的版本)。當其最新的版本狀態變成乾淨後,節點就可以在本地處理讀請求了。這種方式利用了寫操作是序列傳播的事實,因此尾節點總是最後一個收到寫入請求的節點。
CRAQ在以下兩種場景下吞吐量會比CR有所提升:
- **Read-Mostly Workloads** 該場景下大多數都是讀請求,這些讀取請求由$C-1$個非尾節點進行處理。因此,在這類場景下吞吐量與鏈長度$C$呈線性關係。
- **Write-Heavy Workloads** 該場景下有許多對非尾節點的大多數讀請求資料為髒,因此需要對尾節點進行版本查詢。但是,我們認為這些版本查詢並完整讀取更輕量,允許尾節點在它飽和之前以更高的速率處理它們。這使得總的讀取吞吐量仍高於CR。
第六節中的效能資料可以支援以上兩個主張,即使對於小物件也是如此。對於持續寫請求繁重的較長鏈,即使我們不評估這種優化,也可以想象通過使尾部結點僅處理版本查詢而不是處理所有的讀請求的方式,可以優化讀取吞吐量。
### 2.4 CRAQ上的一致性模型
某些應用程式或許可以以較弱的一致性保證來執行,並且它們可能會試圖避免版本查詢的效能開銷(根據3.3節,在廣域部署中是很重要的),或者它們可能希望當系統無法提供強一致性時繼續執行(例如在分割槽期間)。為了支援這類需求的變化,CRAQ同時支援三種不同的一致性模型。讀取操作使用哪一類一致性模型是可選的。
- **強一致性**(預設)上面的模型中描述了強一致性。所有物件讀取都與最後一次提交的寫入一致。
- **最終一致性** 允許對鏈中的節點的讀操作返回已知的最新物件版本。因此,另一個點的後續讀取操作可能返回比先前返回的物件更舊的版本。因此,儘管對單個鏈節點的讀取操作的確在本地,但它不滿足單調讀一致性。
- **最大範圍不一致的最終一致性** 允許讀操作在寫操作提交前將儲存的新物件返回,但只允許在某些條件下這樣做。施加的條件可以基於時間或是基於絕對版本號。在該模型中,保證讀操作返回的值具有最大的不一致性週期。如果鏈仍然是可用的,這種不一致性實際上是因為返回的版本比上次提交的版本新。如果系統被分割槽,並且節點無法參與寫入,那麼版本可能比當前提交的版本舊。
### 2.5 CRAQ中的故障恢復
由於CRAQ的基本結構與CR相似,因此CRAQ使用相同的技術進行故障恢復。每個鏈節點需要知道它的前繼節點和後繼節點,以及鏈的頭部和尾部。當頭部節點故障了,它的後繼節點將接任新的鏈的頭部。同樣,當尾節點出現故障時,它的前繼節點也會接任成為新的尾節點。需要加入到鏈中間的節點要像雙鏈表一樣插入到兩個節點之間。處理系統故障的正確性證明與CR相似;由於篇幅所限,這裡不展開說明。第5節介紹了CRAQ中故障恢復的細節以及協作服務的整合。特別是CRAQ允許節點加入到鏈中的任何位置(而不是僅在尾部),以及在恢復過程中正確處理故障的選擇都需要詳細介紹。
## 3. CRAQ的擴充套件
在本節中,我們討論應用程式如何在單資料中心以及跨多資料中心的條件下,設計CRAQ中鏈的佈局方案。然後,我們討論如何使用協作服務來儲存鏈的元資訊和組成員身份資訊。
### 3.1 鏈佈局策略
使用分散式儲存服務的應用程式的要求可能會有所不同。一些常見的情況如下:
- 對物件的大部分或全部的寫入操作可能源自單個數據中心
- 一些物件可能只存放在一個數據中心的某些節點中
- 熱點物件可能需要大量複製,而非熱點物件可能較少
CRAQ提供了靈活的鏈配置策略,通過使用物件的兩級命名結構來滿足這些變化的需求。物件的識別符號包括*鏈識別符號*和*鍵識別符號*。鏈識別符號決定CRAQ中的哪些節點將儲存該鏈中的所有鍵,而鍵識別符號為每條鏈提供唯一命名。我們介紹了多種滿足應用程式定製化需求的方法:
- **隱式資料中心和全域性鏈長度:**
$\{num\_datacenters,chain\_size\}$
該方法中定義了將儲存鏈的資料中心的數量,但未明確定義儲存在哪個資料中心。為了明確具體哪個資料中心儲存了鏈,使用一致性雜湊結合唯一的資料中心識別符號。
- **顯式資料中心和全域性鏈長度:**
$\{chain\_size,dc_1,dc_2,\dots,dc_N\}$
該方法中每個資料中心使用同樣的鏈長度在資料中心中儲存副本。鏈的頭節點位於資料中心$dc_1$中,鏈的尾節點位於資料中心$dc_N$中,鏈基於資料中心列表進行排序。為了確定資料中心中的哪些節點儲存分配給鏈的物件,對鏈識別符號做一致性雜湊。每個資料中心$dc_i$都有一個連線到資料中心$dc_{i-1}$尾節點的節點和一個連線到資料中心$dc_{i+1}$頭節點的節點。另一個額外的功能是允許$chain\_size$為0,表示該鏈使用每個資料中心內的所有節點。
- **顯式資料中心和不同鏈長度**
$\{dc_1,chain\_size,\dots,dc_N,chain\_size_N\}$
這裡每個資料中心的鏈長度是獨立的。這允許鏈負載均衡是非均勻的。每個資料中心的鏈節點的選擇方式與之前的方式相同,並且$chain\_size$也可以設定為0。
在上述方法2和方法3中,$dc_1$可以設定為**主資料中心**。如果一個數據中心是鏈的主資料中心,那麼對於鏈的寫入將僅在短暫故障期間被該資料中心接受。否則,如果$dc_1$與鏈的其他節點斷開連線,則$dc_2$可能會成為新的頭節點,並接管寫操作,直到$dc_1$恢復線上。如果未設定主節點,寫操作將僅在包含全域性鏈中大多數節點的分割槽中繼續進行。否則,如第2.4節中定義的那樣,對於最大範圍不一致的讀取操作,該分割槽會變成只讀。
CRAQ可以輕鬆支援其他更復雜的鏈配置方法。例如可能需要指定一個顯式備份資料中心,僅當另一個數據掛了的時候開始加入鏈中。還可以設定一組資料中心(例如東海岸資料中心),其中的任意一個都可以填充到上述方法2的有序列表中。為簡便起見,我們不再詳細介紹更復雜的方法。
可以寫入單個鏈的鍵識別符號的數量沒有限制,這樣可以根據應用需求對鏈進行靈活的配置。
### 3.2 單個數據中心中的CRAQ
在最初的鏈式複製工作中,已經研究瞭如何在多個數據中心分佈多個鏈。在CRAQ的當前實現中,我們使用一致性雜湊將鏈放置在資料中心內,將潛在的鏈識別符號對映到頭節點上。這類似於基於資料中心的物件儲存。GFS採用並在CR中推廣的另一種方式是在分配和儲存隨機鏈成員時,使用成員管理服務作為目錄服務,即每個鏈可以包含一些隨機伺服器的集合。這種方式提高了並行系統恢復的能力。但是,這是以增加集中度為代價的。CRAQ可以輕鬆的使用這種設計,但是它將需要在協作服務中儲存更多的元資訊。
### 3.3 跨多個數據中心的CRAQ
當鏈延伸到廣域網時,CRAQ能夠從任何節點進行讀取的能力可以降低它的延遲:客戶端在選擇節點時具有靈活性,它們可以選擇物理距離較近的節點(或者輕負載的節點)。只要鏈的狀態是乾淨的,那麼節點可以直接返回本地副本的值,而不用傳送任何廣域請求。而在傳統的CR中,所有讀取都需要由可能距離較遠的尾節點處理。實際上,由於物件可能處於不同的位置,因此多種設計可能會基於資料中心在鏈中選擇頭結點和/或尾節點。實際上雅虎的新分散式資料庫PNUTS就是受其資料中心中的高寫入區域性性的影響而進行設計的。
也就是說應用程式可能會進一步優化廣域網下鏈的選擇,從而最大程度地減少寫入延遲,降低網路成本。當然,在所有節點集合中使用一致性雜湊這種樸素的方式來構建鏈可能會導致鏈的前繼和後繼是隨機的,前繼和後繼可能距離很遠。此外,一條鏈可能會多次跨入和跨出一個數據中心。而通過我們的鏈優化,應用程式可以通過謹慎選擇組成鏈的資料中心的順序來最小化寫延遲,並且可以確保一條鏈只單向跨越資料中心的網路邊界一次。
即使使用優化後的鏈,隨著越來越多的資料中心被新增到鏈中,廣域網中的鏈的寫操作延遲也會增加。儘管與以並行方式分發寫操作的主/備方法相比,這種方式顯著地增加了延遲,但是它允許將寫操作在鏈中流水線進行,這極大的提高了寫操作的吞吐量。
### 3.4 ZooKeeper 協作服務
眾所周知,為分散式應用程式構建一個容錯的協作服務很容易出錯。CRAQ的早期版本包含一個非常簡單、集中控制的協作服務,用於維護成員管理。後來,我們選擇利用Zookeeper為CRAQ提供一種健壯的、分散式的、高效能的方式來管理組成員,並提供一種簡單的方式來儲存鏈的元資料。通過Zookeeper,當組內新增節點或刪除節點時,CRAQ節點一定會收到通知。同樣當節點關注的元資料傳送變化時,該節點也可以收到通知。
Zookeeper為客戶端提供類似於檔案系統的分層名稱空間。檔案系統儲存在記憶體中,並且在日誌中為每個Zookeeper例項進行備份,檔案系統狀態會在多個Zookeeper節點之間進行復制,從而提高可靠性和可擴充套件性。為了達成一致,Zookeeper使用類似兩階段提交的原子廣播協議。經過優化後,Zookeeper能夠為大量讀的小型工作負載提供出色的效能,因為它可以直接在記憶體中響應大部分的服務請求。
與傳統的檔案系統名稱空間類似,Zookeeper客戶端可以羅列目錄的內容、讀取檔案、寫入檔案以及在檔案或目錄被修改或刪除時收到通知。Zookeeper的原始操作允許客戶端實現許多更高級別的語義,例如組成員、領導選舉、事件通知、鎖和佇列。
跨多資料中心進行管理成員和鏈的元資訊的確帶來了一些挑戰。實際上,Zookeeper並未針對在多資料中心環境中執行進行優化:將多個Zookeeper節點放在單個數據中心,可以提高Zookeeper在該資料中心的讀取可擴充套件性,但是在廣域網下的效能會受損。因為原始實現並不知道資料中心的拓撲和層次結構,所以Zookeeper節點之間進行訊息交換會通過廣域網進行傳輸。儘管如此,我們當前的實現仍然確保了CRAQ節點總是能收到本地Zookeeper節點的通知,並且與它們相關的關於鏈和節點列表的訊息也會進行通知。我們在第5.1節使用Zookeeper進行了擴充套件。
為了消除Zookeeper在跨資料中心時產生的流量冗餘,可以構建一個Zookeeper例項的層次結構:每個資料中心可以擁有自己本地的Zookeeper例項(由多個節點組成),並擁有一個全域性Zookeeper例項的代表(可以通過本地例項的領導選舉選出)。然後獨立的功能可以協調兩者之間的資料共享。一種替代設計是修改Zookeeper本身,就像CRAQ一樣讓節點知道網路拓撲結構。我們尚未重複研究這兩種方法,將其留給以後的工作。
## 4. 擴充套件
本節討論對CRAQ的一些其他擴充套件,包括微事務功能、使用多播優化寫操作。我們目前正在實現這些擴充套件。
### 4.1 CRAQ上的微事務
在一些應用程式中,對於物件儲存中的整個物件的讀/寫介面可能會受限。例如BitTorrent或其他目錄服務可能需要支援列表的新增或刪除。分析服務可能需要儲存計數器。或者應用程式可能希望提供對某些物件的條件訪問。這些需求都不是僅僅提供純粹的物件儲存介面就可以滿足的,但是CRAQ提供了支援事務操作的關鍵擴充套件。
#### 4.1.1 單鍵操作
單鍵操作很容易實現,CRAQ已經支援以下操作:
- **前置/追加:** 將資料新增到當前物件值的開頭或結尾。
- **增加/減小:** 在鍵的物件上增加或減少,以整數形式表示。
- **測試並設定:** 僅在鍵的當前版本號等於操作中執行的版本號時,才更新鍵的物件。
對於前置/追加和增加/減小操作,儲存鍵物件的鏈的頭節點可以簡單地將操作應用於物件的最新版本,即使最新的版本是不乾淨的,然後在鏈中向後傳播替換寫操作。此外,如果這些操作很頻繁,則頭節點可以快取請求然後批量更新。如果使用傳統的兩階段提交協議,實現這些功能付出的代價會很高。
對於測試並設定操作,鏈的頭節點檢查其最近提交的版本號是否等於操作中執行的版本號,如果沒有該物件最近未提交的版本,頭節點接受該操作並在鏈中傳播更新。如果有未完成的寫操作,則拒絕該操作,並且如果連續被拒絕,客戶端需要考慮降低請求速度。還有另一種方案,頭節點可以通過禁止寫入直到物件乾淨為止並重新檢查最新的版本號來鎖定物件,但是由於未提交的寫入被中止是非常少見的,以及鎖定物件會顯著影響效能,因此我們選擇不採用該方案。
測試並設定操作也可以設計為接受值而不是版本號,但是當存在未提交的版本時,會引入額外的複雜性。如果頭節點與物件的最新提交版本(通過與尾節點通訊)比較發現不同,則當前進行中的任何寫入都將被拒絕。而如果頭節點與最新未提交版本比較,就違反了一致性保證。為了實現一致性,頭節點將需要通過禁止寫入直到物件乾淨為止來暫時地鎖住物件。這不會違反一致性保證,並確保不會丟失任何更新,但是會顯著影響寫入效能。
#### 4.1.2 單鏈操作
Sinfonia最近提出的“微事務”提供了一種具有吸引力方法,它能夠較為輕量地在單個鏈的多個鍵上執行事務。微事務由比較、讀取和寫入集合定義;Sinfonia提出了一種跨越多個記憶體節點的線性地址空間。比較集測試指定地址位置的值,如果它們與提供的值匹配,則執行讀取和寫入操作。Sinfonia提出的微事務使用樂觀的兩階段提交協議,專為較低的寫爭用的情況而設計。準備訊息嘗試在指定的記憶體地址上獲取鎖。如果所有的地址都被鎖了,則協議提交;否則,參與者釋放所有的鎖並稍後重試。
CRAQ的鏈拓撲結構對於支援類似微事務有特殊的優勢,因為應用程式可以指定多個物件儲存在同一條鏈上,從而保持了局部性。共享同一個*chainid*的物件被分配在同一個鏈頭節點上,由於只有一個頭節點,因此可以避免在一次通訊中發生兩階段提交。CRAQ的獨特之處在於,在涉及單個鏈的微事務中就可以僅使用頭節點來接受訪問,因為頭節點控制對鏈所有鍵的寫訪問。唯一的缺點就是如果頭節點需要等待事務中的所有節點變乾淨(如4.1.1節所述),那麼寫吞吐量會收到影響。但是這個問題在Sinfonia中更為嚴重,因為它需要等待跨多個節點的鍵解鎖。同樣,在CRAQ中從故障恢復也很容易。
#### 4.1.3 多鏈操作
即使在多物件更新涉及到多個鏈時,樂觀兩階段提交協議也僅需使用鏈頭節點來實現,而不是所有涉及的節點。鏈頭節點可以鎖住任何微事務中涉及的鍵,直到事務完全提交為止。
當然,應用程式寫程序在使用昂貴的鎖和微事務時需要小心:由於寫同一個物件無法再流水線化執行(鏈式複製極其重要的優勢),CRAQ的寫吞吐量會被降低。
### 4.2 多播降低寫入延遲
CRAQ可以利用多播協議來提高寫入效能,特別是對於大規模的更新或是長鏈而言。由於鏈成員在節點成員修改期間是穩定的,因此可以為每個鏈建立一個多播組。在一個數據中心內,可以採用網路層多播協議的形式,而應用程式層多播可能更適用於廣域網中的鏈。這些多播協議不需要順序或可靠性保證。
然後,實際的值可以多播到整個鏈,而不是在鏈上順序傳播完整寫入,增加與鏈長度成正比的延遲。與此同時,只有較小的元資料資訊需要在鏈中傳播,以確保所有尾節點前的副本都能收到寫操作。如果節點因為任何原因而未收到多播的訊息,節點可以在接收到寫提交訊息之後和向下傳播提交訊息之前,與它的前繼節點進行通訊獲取物件。
此外,當尾節點收到傳播的寫請求時,多播確認資訊可以傳送到多播組中,而不是將其沿鏈向後傳播。這樣既減少了節點物件在寫入後等待重新進入乾淨狀態的時間,又減少了客戶端感知的寫入延遲。同樣,在多播確認時不需要順序或可靠性保證——如果鏈中的節點沒收到確認訊息,它會在下一個讀取操作要求它查詢尾節點時重新進入乾淨