超越分散式事務

banq發表於2018-01-11
該文是Salesforce的軟體架構師Pat Helland於2016年12月發表的針對其在2007年CIDR(創新資料庫研究會議)上首次發表的同名文章的更新和縮寫版本。他曾經發表“不變性改變一切”。

業界談到分散式事務通常指兩段提交2PC事務(Spring/JEE中JTA等)或者Paxos與Raft,這些事務都有其明顯缺點和侷限性,Pat Helland在本文討論的是另外一種基於本地事務情況下的事務機制,它是基於實體和活動(Activity)的概念,其實類似DDD聚合根和領域事件的概念,這種工作流型別事務雖然需要程式設計師介入,依靠訊息系統實現,但可以實現接近無限擴充套件的大型系統。Pat文中提出了重要的觀點:“如果你不能使用分散式事務,那麼你就只能使用工作流。”

以下是Pat Helland的Life Beyond Distributed Transactions這篇著名文章的翻譯:

事務是非常強大的機制,我在它上面花費了我40年職業生涯中的大部分時間。1982年,我首先在Tandem NonStop系統上實現事務機制。這個系統有一個每年平均故障時間,包括跨地理位置的分散式的兩階段提交,為強一致性事務提供了極好的可用性。

新的創新,其中包括谷歌的Spanner,提供強一致性環境,在非常大的規模基礎上提供了卓越的可用性。構建一個以支援高度可用的分散式事務應用程式是卓越的創新和重大的挑戰。不幸的是,這個產品不能被廣泛使用。

在大多數分散式事務系統中,單個節點的失敗會導致事務提交失敗。這反過來導致應用程式被卡住。在這樣的系統中,系統越大,系統越可能停機。當一個飛機需要所有發動機都必須工作時才能飛行,增加發動機只會降低飛機的可用性。如果沒有特殊的機制來容忍中斷,那麼在數千個節點上執行分散式事務系統是不切實際的。當應用程式開發人員使用非高度可用的分散式事務構建系統時,解決方案很脆弱,必須丟棄。自然選擇就是放棄它(放棄兩段提交事務)...

相反,應用程式在沒有提供事務性保證前提下仍能滿足其業務需求。

本文探討並在拒絕傳統分散式事務的世界中實施大規模任務關鍵型應用程式時使用的一些實用方法。包括管理細粒度的應用程式資料,隨著應用程式的增長,這些應用程式資料可能會隨著時間的推移重新分割槽。包括一些設計模式用來支援在這些可重新分割槽的資料之間傳送訊息。

這裡的目標是減少人們手工製作大型可擴充套件應用程式所面臨的挑戰。另外,透過觀察這些設計模式,業界可以透過建立平臺來更輕鬆地開發可擴充套件的應用程式。最後,儘管本文的目標是同類性質可擴充套件的應用程式,但這些技術對於支援可伸縮的異構應用程式(如支援移動裝置)也非常有用。

目標

本文重點介紹如何在只有本地資料庫或本地事務系統時構建成功的可伸縮企業應用程式。可用性不是重點,主要瞄準規模和正確性。

1.可擴充套件應用程式的討論

大多數可擴充套件應用程式的設計者都瞭解業務需求。問題在於事務和可擴充套件系統互動的問題,只有概念和抽象卻沒有名稱,也沒有清晰的理解。本文的一個目標是啟動一個討論,以提高對這些概念的認識,為可擴充套件的程式帶來一套通用的術語和一致的方法。

2.考慮應用程式的幾乎無限縮放

文章提出了一個關於幾乎無限縮放的影響的非正式的思考實驗。也就是說可以允許客戶,可購買商品實體,訂單,發貨,生病患者,納稅人,銀行賬戶以及所有其他業務概念的數量隨著時間不斷顯著增長,這就是無限擴充套件的定義。通常情況下,每件事情都不會太大; 只是數量越來越多。如果CPU,DRAM,儲存或其他資源首先飽和,那並不重要。在某種程度上,需求的增加會導致在一臺機器上執行程式需要在大量的機器上執行。這個思想實驗使我們考慮數十或數十萬臺機器。

幾乎無限的縮放是一種鬆散的,不精確的,刻意的無定形的方式,它激發人們需要非常清楚何時何地可以知道某個機器上裝有什麼東西,以及如果無法確保它適合一臺機器,該怎麼辦。此外,您想要與資料和計算負載幾乎線性地縮放。當然,用一個大的日誌以N-log-N的速度進行縮放將會很好。

3.描述可擴充套件應用程式的一些常見模式

幾乎無限的擴充套件對業務邏輯有什麼影響?我斷言縮放意味著在編寫程式時使用稱為實體的新抽象。一個實體一次只能在一臺機器上執行,而應用程式一次只能操縱一個實體。幾乎無限縮放的結果是,這種程式化的抽象必須暴露給業務邏輯的開發者。

透過命名和討論這個尚未命名的概念,我們也許可以商定一致的方案方法,並對構建可擴充套件系統所涉及的問題有一個一致的理解。

此外,實體的使用對用於連線它們的訊息傳遞模式有影響。這導致了狀態機的建立,這些狀態機不一致性包括無辜的應用程式開發人員試圖為業務問題構建可伸縮解決方案時所產生的訊息傳遞不一致。

4.一些假設

考慮以下三個假設,假設這些基於經驗是真實的,這些假設是沒有道理的。。

(1)應用層面和規模不可知論

我們首先假設每個可伸縮應用程式至少有兩層:
程式碼高層 ---> 感知規模的API ----> 程式碼下層

這些層在縮放的感知上有所不同。他們可能有其他的區別,但這些與這個討論無關。

該應用程式的下層理解是:更多的計算機被新增擴大系統規模。除了其他工作之外,它還管理上層程式碼到物理機器及其位置的對映。下層是可以識別的,因為它理解這個對映。我認為下層為上層提供了一個與尺度無關的程式設計抽象。有很多與規模無關的程式設計抽象的例子,包括MapReduce。

使用這種與規模無關的程式設計抽象,編寫應用程式程式碼的上層而不用擔心縮放問題。透過堅持與規模無關的程式設計抽象,您可以編寫應用程式程式碼,而不必擔心在部署日益增加的負載時發生的變化。

隨著時間的推移,這些應用程式的底層可能會演變成一個新的平臺或中介軟體,從而簡化了與規模無關的API的建立。

(2)事務範圍

分散式系統上提供高度一致的事務的概念方面已經做了許多學術工作。這包括2PC(兩階段提交),1個 Paxos和最近的Raft。 經典2PC在一臺機器發生故障時會阻塞,除非事務協調員和參與者本身是容錯的,比如Tandem NonStop系統。Paxos和Raft不會因為節點故障而阻塞,但會像Tandem的系統一樣進行額外的協調工作。

這些演算法可以被描述為在分散式系統上提供強一致的事務。他們的目標是允許任意原子更新傳播到許多機器上的資料。更新存在於跨越多臺機器的單個事務範圍中。

不幸的是,在許多情況下,這不是應用程式開發人員的選擇。應用程式可能需要跨越信任邊界,不同平臺以及不同的運營和部署區域。當你對分散式事務“說不”時會發生什麼?

即使在今天,在這篇論文寫了10年之後,真正的系統開發人員也很少嘗試在超過幾臺計算機上實現強大的一致性事務。相反,他們承擔多個獨立的事務範圍。每臺電腦都是一個獨立的範圍,內部有本地事務。

(3)大多數應用程式使用至少一次訊息

如果你是一個短暫的Unix風格的程式,TCP / IP是非常棒的,但考慮一個應用程式開發人員面臨的困境,他的工作是處理訊息並修改資料庫中相應的一些持久資料。該訊息已被消費但尚未確認。只有資料庫被更新後訊息才被確認。如果發生故障,則重新啟動並重新處理訊息。

這個困境來源於這樣的事實:除了透過應用動作之外,訊息傳遞不是直接耦合到持久資料的更新。雖然可以將訊息的消費與持久資料的更新結合起來,但這並不常見。兩者緊密耦合的喪失會導致失敗視窗的訊息被傳遞多次。為了防止丟訊息,訊息傳遞至少一次(banq注:Apache kafka在1.0之後提供了Stream的緊耦合更新,不同資料來源需要不同Stream外掛)。

這種行為的後果是應用程式必須容忍訊息重試和無序傳遞。

一些意見分析

撰寫評論文章的好處是可以表達狂野的意見。這裡有一些這樣的文章。

(1)可擴充套件的應用程式使用唯一標識的實體

該意見認為,每個應用程式的上層程式碼必須處理一個稱為實體的資料集合。單個實體的大小沒有限制,除了必須在單個事務範圍內(即一臺機器)生存。

每個實體都有一個唯一的識別符號或鍵,實體鍵可以是任何形式,它必須唯一標識一個實體及其包含的資料。

對實體的陳述沒有任何限制。它可能被表示為SQL記錄,XML,JSON,檔案或其他任何東西。一種可能的表示形式是SQL記錄的集合,可能跨越許多表,其主鍵的實體鍵作為其字首。

實體表示不相交的資料集。每個資料只在一個實體中。

一個應用程式由許多實體組成。例如,訂單處理應用程式封裝了許多訂單,每個訂單都由一個唯一的訂單ID標識。要成為可擴充套件的訂單處理應用程式,來自一個訂單的資料必須與其他訂單的資料不相交(banq注:主鍵ID透過全域性分散式主鍵生成器唯一分配)。

(2)原子事務不能跨越實體

每臺計算機被假定為一個單獨的事務範圍。本文稍後介紹原子事務不能跨實體的論點。程式設計師必須始終確保每個事務中資料包含在的單個實體中(banq注:有些類似DDD的聚合根實體概念)。

從程式設計師的角度來看,唯一標識的實體就是事務範圍。這個概念對用於設計可縮放的應用程式具有強大的影響。需要探討的一個含義是,在設計幾乎無限的縮放比例時,可能不能保持事務一致。

(3)訊息被分配給實體

大多數訊息傳遞系統不考慮資料的分割槽,而是針對無狀態程式使用的佇列。標準做法是在訊息中包含一些資料,通知無狀態應用程式程式碼在哪裡獲取所需資料。這是實體的關鍵。實體的資料由應用程式從某個資料庫或其他持久儲存中獲取。

一些有趣的趨勢正在發生。首先,這組實體的大小正在變得比適合一臺計算機的大。每個單獨的實體通常適合一臺計算機,但是它們的集合不一樣。越來越多的無狀態應用程式正在路由以基於某種分割槽方案來獲取實體。

其次,抓取和分割槽方案正被分成應用程式的下層。這是故意與負責業務邏輯的上層隔離的。

該模式透過使用實體鍵進行路由來有效地定位實體。無狀態的Unix風格的程式和應用程式的低層都只是為業務邏輯提供的與規模無關的API的實現的一部分。上層規模不可知的業務邏輯簡單地透過唯一標示將訊息定位到某個實體。

(4)實體管理每個合作伙伴狀態(活動)

與規模無關的訊息能夠在實體之間有效的傳遞。傳送實體以其持久狀態顯示,並由其實體鍵標識。它傳送一個訊息給另一個實體,並透過它的實體唯一鍵來識別它。實體接收者由與規模無關的上層業務邏輯和表示其狀態的持久資料組成。這是透過它的實體鍵來標識的。

回想一下這樣的假設:訊息至少被傳遞一次。接受者實體可能有些困擾,因為必須忽略的冗餘重複訊息。在實踐中,訊息分為兩類:影響實體狀態的訊息和不影響狀態的訊息。不影響實體狀態的訊息很容易 - 它們是冪等的。改變狀態的訊息需要更多的關注(banq注:改變狀態的訊息一般稱為事件)。

為了確保冪等性(即保證重試訊息的處理是無害的),接收者實體通常被設計為記住訊息已經被處理。成功處理後,重複的訊息通常會生成新的另一個響應以表示該訊息重複。

接收到的訊息的實體建立了基於夥伴為基礎的狀態。每個狀態只能存在每個實體中,
術語“活動activity ”這個詞語適用於管理雙方關係,會對關係中每個夥伴狀態有影響的訊息。每個活動都是精確地生活在一個實體中,一個實體有一個為其夥伴實體準備的活動。(banq注:這個活動是一種業務活動,業務工作流)

除了管理訊息傳遞方式之外,還可以使用活動來管理鬆散耦合的協議。在一個原子事務不可能的世界裡,嘗試性的操作被用來進行談判以獲得共同的結果。這些都是在實體之間執行的,由活動管理。

建立工作流程以達成一致是充滿挑戰的,這些挑戰在其他地方都有詳細記錄。本文並沒有斷言活動解決了這些挑戰,而是為針對解決這些挑戰而儲存的狀態奠定了基礎。

幾乎無限的縮放規模要求竟然導致了出人意料的細粒度的工作流風格的解決方案。

參與者都是實體,每個實體使用有關其他實體的特定知識來管理其工作流程。在實體內維護的雙方知識被稱為活動。

活動的例子有時是微妙的。訂單應用程式將訊息傳送到發貨應用程式。它包括貨運ID和傳送訂單ID。訊息型別可用於激發運送應用程式中的狀態更改,以記錄指定的訂單已準備好出貨。通常情況下,實施者不會設計重試,直到出現錯誤。應用程式設計師偶爾也會考慮活動的計劃。

實體

本節更深入地研究實體的性質。

1.不相交的事務範圍

每個實體都被定義為一個擁有一個唯一標識的資料集合,這個標識只存在於一個事務範圍內。原子交易可能總是在一個實體內完成。(banq注:類似DDD中的聚合根實體)

2.唯一鍵的實體

應用程式上層的程式碼自然是圍繞具有唯一鍵的資料集合來設計的。客戶ID,社會安全號碼,產品SKU和其他唯一識別符號可以在應用程式中看到。它們被用作查詢應用程式資料的鍵。交易原子性的保證只能在由唯一標識的實體中。

3.重新分配和實體

之前提到的假設之一是新興的上層是規模不可知的,下層決定了隨著規模的變化,部署如何演變。隨著部署的演變,特定實體的位置可能會發生變化。應用程式的上層不能對實體的位置做出假設,因為這不會成比例。

實體可使用Hash雜湊或基於鍵範圍分配策略進行跨區分配。

比如key是ABC ABZ DEF FAW在A區伺服器 FXQ GHI JKL KZU LMN在B區伺服器,PAZ PJH TVA UTV ZZZ在C區伺服器。

4.原子事務和實體

在可擴充套件的系統中,您不能假設在這些不同實體之間實現更新修改的事務。每個實體都有一個唯一的標識,每個實體很容易放入一個事務範圍。回想一下這樣的前提:幾乎無限的縮放會導致實體的數量不可避免地增加,但是個體的大小仍然足夠小以適應事務範圍(即一臺計算機)。

你怎麼知道兩個獨立的實體保證在同一個事務範圍內,因而可以自行在自己事務中原子更新?只有當全域性只有一個唯一的key主鍵時,你能知道它真的是隻有一個實體!

如果Hash雜湊用於實體鍵的分割槽,則不知道具有不同鍵的兩個實體何時落在同一個伺服器內。如果鍵範圍分割槽用於實體鍵,大部分時間相鄰鍵值會駐留在同一臺機器上。偶爾會遇到不幸,而你的鄰居會在另一臺機器上。

一個簡單的測試案例,透過鍵範圍分割槽中的鄰居之間的計算原子性通常會成功。後來,當重新部署將實體移動到機器上時,潛在的錯誤就出現了。更新不再是原子的。你永遠不能指望在同一個地方居住的不同的實體鍵值(不要期望實體固定在哪個伺服器內被建立)。

簡而言之,應用程式的底層將確保每個實體鍵(及其實體)駐留在一臺機器上。不同的實體可能在任何地方。(banq注:DDD中倉儲工廠模式就是負責一個個聚合根實體的建立)

規模不可知的程式設計抽象必須具有將實體作為原子性邊界的概念。理解實體,使用實體關鍵字以及明確承諾實體之間缺乏原子性是規模無關的程式設計必不可少的知識。

大規模的應用程式悄悄地在今天的行業中已經做到這一點; 只是沒有一個實體的概念的名稱。從上層應用程式的角度來看,它必須假定實體是代表一個事務的範圍。假設部署變化時會產生更多的中斷。

5.考慮候選索引

我們習慣於使用多個鍵或索引來處理資料。例如,有時一個客戶被社會安全號碼(有時是信用卡號碼,有時是街道地址)引用。假設進行大量的縮放,這些索引不能駐留在同一臺機器上,也不能駐留在單個大型叢集中。無法知道關於單個客戶的所有資料都駐留在單個事務範圍內。雖然實體本身駐留在單個事務範圍內。所面臨的挑戰是,用於建立這些候選索引的資訊副本必須被假定為與實體自身資訊駐留在不同的事務範圍中。

首先考慮確保候選索引駐留在相同的事務範圍內。當幾乎無限的縮放開始時,實體的集合被塗抹在巨大數量的機器之中。主索引和候選索引資訊必須位於相同的事務範圍內。確保這一點的唯一方法是使用主索引找到候選索引。這將把你帶到相同的事務範圍。如果您在沒有主索引的情況下啟動並且必須搜尋所有事務範圍,則每個候選索引查詢都必須在幾乎無限數量的範圍內檢查,因為它會使用候選鍵進行匹配查詢。這將最終成為站不住腳的。

唯一合乎邏輯的選擇是做兩步查詢。首先,查詢候選鍵。其次,使用實體鍵訪問實體。這與關聯式資料庫非常相似,因為它就是使用兩個步驟透過候選鍵訪問記錄的。但是幾乎無限縮放的前提意味著兩個指標(主要和候選)不能被認為是在相同的事務範圍內。


過去自動管理的候選索引現在必須由應用程式手動管理。透過非同步訊息傳遞的工作流風格更新是剩下的唯一選擇。當您從候選索引讀取資料時,您必須瞭解它可能與實體本身不同步。候選索引現在更難。這是一個巨大系統的殘酷世界中的生活事實。(banq注:索引是為查詢服務,可使用讀寫分離)

6. 在實體之間傳遞的訊息

本節考慮使用訊息來連線獨立的實體。它檢查命名,事務和訊息,訊息傳遞語義以及重新分割槽實體的影響。

(1)訊息在實體之間進行通訊

如果您無法在同一事務中的兩個實體之間更新資料,則需要一種機制來更新不同事務中的資料。實體之間的連線是透過訊息。

(2)非同步傳送事務

由於訊息在實體之間傳遞,與傳送訊息的決定相關的資料在一個實體中,並且訊息的目的地在另一個實體中。根據一個實體的定義,這些實體不能被原子更新。訊息不能透過這些不同的實體自動傳送和接收。

如果應用程式開發人員在處理事務時傳送訊息,傳送訊息,然後事務中止,那將會非常複雜。這意味著你沒有任何措施記住發生的事情,但這些事情確實發生了。出於這個原因,訊息的事務排隊是必要的。

如果訊息在傳送事務提交之後才能在目的地被看到,則訊息對於傳送事務是非同步的。每個實體在一個事務中會進入一個新狀態。訊息是來自一次事務的刺激,併到達另外一個實體引起新的事務。

(3)命名訊息的目的地

考慮應用程式的規模無關部分的程式設計,因為一個實體想要傳送訊息給另一個實體。規模不可知的程式碼不知道目標實體的位置。實體主鍵就是關鍵。

實體鍵能夠實現應用程式的規模感知,將實體主鍵與實體的具體位置相關聯。

(4)重新分割槽和訊息傳遞

當應用程式與規模無關的部分傳送訊息時,較低階別的擴充套件感知部分搜尋目標並且傳遞訊息至少一次。

隨著系統規模的擴大,實體在移動。這通常被稱為重新分割槽。實體的位置以及因此訊息的目的地可能在不斷變化。有時訊息會追溯到舊的位置,只是為了找出討厭的實體已經傳送到別處什麼地方聊。現在,訊息將不得不這麼做。

隨著實體的移動,傳送者和目的地之間的先進先出佇列的清晰度偶爾中斷。訊息被重複。稍後的訊息在較早的訊息之前到達 生活變得更加混亂。

由於這些原因,與規模無關的應用程式正在不斷演進到以支援所有應用程式對可見訊息的冪等處理。這也意味著在訊息傳遞中可進行重新排序。

相關文章