分佈式事務以及分佈式鎖是分佈式中難點,分佈式事務一篇文章可能寫不完,個人習慣時從基本概念出發,一步一步開始介紹,前面會先梳理事務中一些基本概念,對基本概念十分清楚的話能夠直接看"一致性討論"以及後面的部分。予己方便總結回顧、與他交流分享。html
在平常生活中,不少事要麼所有作,要麼所有不作,不能只作一部分,否則就會產生其餘複雜的問題,不少人喜歡舉轉帳的例子,對於同一個帳號,A在湖北往出轉500,B在廣東取錢500,那麼A轉出去以後要將A帳號的錢數目扣除,B帳號數目增長: 事務 = (A帳號扣除500,B帳號增長500)數據庫
看到沒,像這樣多個步驟放在一塊兒,就是事務,要麼都執行,要麼都不執行,若是咱們的數據存儲在多個數據庫中,也就是存在跨庫調用,因爲網絡具備不安全性以及延時性,如何保證事務分佈式執行呢?若是執行到一半斷電又該如何處理?在講解分佈式事務以前先簡單回顧事務的一些特色,俗稱ACID,下面逐一講解:安全
在化學中,分子構成的物質,分子是保持化學特性的最小單位,如\(H_2O,CO_2\)等,由原子構成的物質,原子保持物質特性,像\(Fe\)啥的,意思就是不可分割,再分紅質子中子啥的就不是咱們認爲的物質了,這兒的原子性也是這個道理,就是事務不能夠再拆分,例如上面的事務,看着能夠是由兩個過程組成的事務,可是你拆開就不是咱們認爲該有的過程,因此,事務不可再分,具備原子性。服務器
一致性也很好理解,對於上面的兩個帳戶,若是銀行想知道本身這兒被存了多少錢,那麼這個事務執行前,A帳號有500塊,B帳號沒有錢,銀行帳戶總共500塊,事務執行後A帳號沒有錢,B帳號有500塊,也就是這個500塊是必定的,不可能出現A帳號有500塊,B帳號也有500塊, 那就數據不一致了,這樣的話,說明事務中某些步驟執行出現了問題,產生中間數據,那麼就不一致。網絡
在分佈式中,對於一個結果,多處同時查詢,得出的結果應該是一致的。架構
一個事務在未完成時,另外一個事務不會影響到它,也就是若是B還給C轉帳1000,記爲事務2:併發
事務1 = (A帳號扣除500,B帳號增長500)分佈式
事務2 = (B帳號扣除1000,C帳號增長1000)微服務
這兩個事務之間不會產生影響,也就是不會發生A轉出的500塊到達C帳號這種狀況。高併發
持久化,通常是意味着將數據寫入磁盤,不會輕易改變的意思,這兒是事務提交以後,會影響到數據庫,不會丟失。這也就意味着,隨着系統愈來愈龐大,咱們爲了提升可用性、維護性、吞吐量等等技術指標,就算改善原有架構,業務計算的問題解決後,數據庫仍是會成爲整個系統中的瓶頸。
ACID本質而言都是爲了保護數據的一致性,而數據數據持久化時會觸發數據庫操做,形成效率低小,因此圍繞一致性(效率)產生了一些討論,分別是強一致性、弱一致性、最終一致性。
任何一次讀都能讀到某個數據的最近一次寫的數據。系統中的全部進程,看到的操做順序,都和全局時鐘下的順序一致。簡言之,在任意時刻,全部節點中的數據是同樣的,這就要求數據一有改變就寫到數據庫。
數據更新後,不要求及時寫會數據庫以及同步到全部節點,也就是這時候數據與真實數據可能有一些出入,對於架構而言,若是能容忍後續的訪問只能訪問到部分或者所有訪問不到,則是弱一致性。
不保證在任意時刻任意節點上的同一份數據都是相同的,也就是有些節點數據多是準確的,有的多是不許確的, 可是隨着時間的遷移,不一樣節點上的同一份數據老是在向趨同的方向變化。簡單說,就是在一段時間後,節點間的數據會最終達到一致狀態。
三種一致性中,強一致性數據更加可靠,可是因爲時時刻刻要求全部數據庫保持數據一致,因此效率低下,數據沒有統一完,請求就無法獲得響應,高併發場景下,體驗不太好,因此在實際使用中,根據不一樣的業務選擇是一致性也不一樣,購物時帳號付錢確定是強一致性,可是商品庫存數據就不必定非要強一致性,至於商品下面的評論啥的,甚至能夠選擇弱一致性。
前面講過集羣的AKF拆分原則(Redis集羣拆分原則之AKF),大概意思是硬件性能是由上限的,當硬件無法支撐請求流量時,能夠將流量分發到不一樣的服務器上,AKF拆分之Y軸、Z軸拆分是業務拆分與數據拆分,那也就會涉及到將數據庫中的數據拆分存儲在不一樣的地方,這就叫分庫分表,不一樣類型數據存儲在不一樣數據庫中作多機存儲和負載,這樣一來,傳統的事務機制ACID便沒法正常運行。
分庫分表內容是數據切分(Sharding),以及切分後對數據的定位、整合。具體來講, 數據切分就是將數據分散存儲到多個數據庫中,使得單一數據庫中的數據量變小,經過擴充主機的數量緩解單一數據庫性能問題,從而達到提高數據庫操做性能的目的。
數據切分根據其切分類型,能夠分爲兩種方式:垂直(縱向)切分和水平(橫向)切分。
垂直切分常見有垂直分庫和垂直分表兩種,兩種含義相似。
垂直分庫就是根據業務耦合性,將關聯度低的不一樣表存儲在不一樣的數據庫。作法與大系統拆分爲多個小系統相似,按業務分類進行獨立劃分。與"微服務治理"的作法類似,每一個微服務使用單獨的一個數據庫。如圖:
垂直分表相似,例如將一張表包含一我的全部信息,例如姓名、身份證、性別、身高、體重、省、市、區、村、專業、G點等等,那麼能夠拆分紅三個表:
第一個表只包含基本信息(姓名、身份證、性別、身高、體重);
第二個表包含籍貫信息(省、市、區、村);
第三個表包含學習信息(專業、G點)。
垂直切分的優勢:
垂直切分的缺點:
上面對數據庫垂直拆分以後,若是某個庫仍是好大,好比存儲的數據極其龐大,那麼能夠再對數據庫進行水平的拆分:
上面的水平拆分時按照ID區間來切分。例如:將userId爲1~10000的記錄分到第一個庫,10001~20000的分到第二個庫,以此類推。某種意義上,某些系統中使用的"冷熱數據分離",將一些使用較少的歷史數據遷移到其餘庫中,業務功能上只提供熱點數據的查詢,也是相似的實踐。
除了上面按照用戶ID區間拆分,也能夠作Hash運算拆分,這兒就不詳細展開了。
水平拆分優勢在於:
水平拆分缺點:
分庫分表能有效的緩解單機和單庫帶來的性能瓶頸和壓力,突破網絡IO、硬件資源、鏈接數的瓶頸,同時也帶來了一些問題,前面說過,事務包含一組子操做,這些造做要麼所有執行,要麼所有不執行,可是分庫以後,一個事務可能涉及多個數據庫或者多個表擴庫執行,而網絡具備不穩定性,也就是事務執行難度加大,分表分庫後事務爲了與傳統事務作出區別,叫作分佈式事務(跨分片事務)。
跨分片事務也是分佈式事務,沒有簡單的方案,通常可以使用"XA協議"和"兩階段提交"處理。
分佈式事務能最大限度保證了數據庫操做的原子性。但在提交事務時須要協調多個節點,推後了提交事務的時間點,延長了事務的執行時間。致使事務在訪問共享資源時發生衝突或死鎖的機率增高。隨着數據庫節點的增多,這種趨勢會愈來愈嚴重,從而成爲系統在數據庫層面上水平擴展的枷鎖。
對於那些性能要求很高,但對一致性要求不高的系統,每每不苛求系統的實時一致性,只要在容許的時間段內達到最終一致性便可,可採用事務補償的方式。與事務在執行中發生錯誤後當即回滾的方式不一樣,事務補償是一種過後檢查補救的措施,一些常見的實現方法有:對數據進行對帳檢查,基於日誌進行對比,按期同標準數據來源進行同步等等。事務補償還要結合業務系統來考慮。
講這個以前須要先簡單回顧CAP原則和Base理論,由於分佈式事務不一樣於 ACID 的剛性事務,在分佈式場景下基於 BASE 理論,提出了柔性事務的概念。要想經過柔性事務來達到最終的一致性,就須要依賴於一些特性,這些特性在具體的方案中不必定都要知足,由於不一樣的方案要求不同;可是都不知足的話,是不可能作柔性事務的。
CAP通常人可能聽了不下一百遍了,不少人都說CAP是"三選二"的關係,讓人誤覺得有AC這種狀況,可是實際CAP是二選一的關係,這個在2012年已經有一篇論文進行解釋:CAP Twelve Years Later: How the "Rules" Have Changed
至關因而對以前三選二說法進行修正,CAP中P(分區容錯性)是必須具有的,在知足P的前提下,很難同時知足A(可用性)和C(一致性),可是在以後,又有一篇文章:Harvest, yield, and scalable tolerant systems,這篇論文是基於上面那篇「CAP 12年後」的論文寫的,它主要提出了 Harvest 和 Yield 概念,並把上面那篇論文中所討論的東西講得更爲仔細了一些。簡單來講就是知足P以後,C和A在放寬約束後能夠獲得兼顧,並非非此即彼的關係,說遠了。
爲何P是必須的?
爲何CAP原則中分區容錯性是必須的呢,首先要理解什麼是分區容錯性,分區,這兒說的是網絡,網絡集羣設計到不少的服務器,某一瞬間網絡不穩定,那麼至關於將網絡分紅了不一樣的區,假設分紅了兩個區,這時候若是有一筆交易:
對分區一發出消息:A給B轉帳100元,對分區二發出消息:A給B轉帳200元
那麼對於兩個分區而言,有兩種狀況:
a)無可用性,即這兩筆交易至少會有一筆交易不會被接受;
b)無一致性,一半看到的是 A給B轉帳100元而另外一半則看到 A給B轉帳200元。
因此,分區容忍性必需要知足,解決策略是一個數據項複製到多個節點上,那麼出現分區以後,這一數據項就可能分佈到各個區裏。容忍性就提升了。
在不少時候,咱們並不須要強一致性的系統,因此後來,人們爭論關於數據一致性和可用性時,主要是集中在強一致性的 ACID 或最終一致性的 BASE中, BASE是對CAP中一致性和可用性權衡的結果,其來源於對大規模互聯網分佈式系統實踐的總結,是基於CAP定律逐步演化而來。其核心思想是即便沒法作到強一致性,但每一個應用均可以根據自身業務特色,才用適當的方式來使系統打到最終一致性。
BASE理論是Basically Available(基本可用),Soft State(軟狀態)和Eventually Consistent(最終一致性)三個短語的縮寫。
基本可用
假設系統,出現了不可預知的故障,但仍是能用,相比較正常的系統而言:
這就叫基本可用
軟狀態
相對於原子性而言,要求多個節點的數據副本都是一致的,這是一種「硬狀態」。軟狀態指的是:容許系統中的數據存在中間狀態,並認爲該狀態不影響系統的總體可用性,即容許系統在多個不一樣節點的數據副本存在數據延時。
最終一致性
上面說軟狀態,而後不可能一直是軟狀態,必須有個時間期限。在期限事後,應當保證全部副本保持數據一致性,從而達到數據的最終一致性。這個時間期限取決於網絡延時、系統負載、數據複製方案設計等等因素。
Base其核心思想是:
既然沒法作到強一致性(Strong consistency),但每一個應用均可以根據自身的業務特色,採用適當的方式來使系統達到最終一致性(Eventual consistency)。有了Base理論就能夠開始講述分佈式事務的處理思路了。
二階段提交(2PC:Two-Phase Commit),顧名思義,該協議將一個分佈式的事務過程拆分紅兩個階段: 投票 和 事務提交 。爲了讓整個數據庫集羣可以正常的運行,該協議指定了一個 協調者 單點,用於協調整個數據庫集羣各節點的運行。爲了簡化描述,咱們將數據庫集羣中的各個節點稱爲 參與者 ,三階段提交協議中一樣包含協調者和參與者這兩個角色定義,後面再說。
該階段的主要目的在於打探數據庫集羣中的各個參與者是否可以正常的執行事務,具體步驟以下:
在通過第一階段協調者的詢盤以後,各個參與者會回覆本身事務的執行狀況,這時候存在 3 種可能性:
對於第 1 種狀況,協調者將向全部的參與者發出提交事務的通知,具體步驟以下:
對於第 2 和第 3 種狀況,協調者均認爲參與者沒法成功執行事務,爲了整個集羣數據的一致性,因此要向各個參與者發送事務回滾通知,具體步驟以下:
兩階段提交協議解決的是分佈式數據庫數據強一致性問題,實際應用中更多的是用來解決事務操做的原子性,下圖描繪了協調者與參與者的狀態轉換。
站在協調者的角度,在發起投票以後就進入了 WAIT 等待狀態,等待全部參與者回覆各自事務執行狀態,並在收到全部參與者的回覆後決策下一步是發送 commit提交 或 rollback回滾信息。
站在參與者的角度,當回覆完協調者的投票請求以後便進入 READY 狀態(可以正常執行事務),接下去就是等待協調者最終的決策通知,一旦收到通知即可依據決策執行 commit 或 rollback 操做。
兩階段提交協議原理簡單、易於實現,可是缺點也是顯而易見的,包含以下:
協調者在整個兩階段提交過程當中扮演着舉足輕重的做用,一旦協調者所在服務器宕機,就會影響整個數據庫集羣的正常運行。好比在第二階段中,若是協調者由於故障不能正常發送事務提交或回滾通知,那麼參與者們將一直處於阻塞狀態,整個數據庫集羣將沒法提供服務。
兩階段提交執行過程當中,全部的參與者都須要遵從協調者的統一調度,期間處於阻塞狀態而不能從事其餘操做,這樣效率極其低下。
兩階段提交協議雖然是分佈式數據強一致性所設計,但仍然存在數據不一致性的可能性。好比在第二階段中,假設協調者發出了事務 commit 通知,可是由於網絡問題該通知僅被一部分參與者所收到並執行了commit 操做,其他的參與者則由於沒有收到通知一直處於阻塞狀態,這時候就產生了數據的不一致性。
針對上述問題能夠引入 超時機制 和 互詢機制在很大程度上予以解決。
超時機制
對於協調者來講若是在指定時間內沒有收到全部參與者的應答,則能夠自動退出 WAIT 狀態,並向全部參與者發送 rollback 通知。對於參與者來講若是位於 READY 狀態,可是在指定時間內沒有收到協調者的第二階段通知,則不能武斷地執行 rollback 操做,由於協調者可能發送的是 commit 通知,這個時候執行 rollback 就會致使數據不一致。
互詢機制
此時,咱們能夠介入互詢機制,讓參與者 A 去詢問其餘參與者 B 的執行狀況。若是 B 執行了 rollback 或 commit 操做,則 A 能夠大膽的與 B 執行相同的操做;若是 B 此時尚未到達 READY 狀態,則能夠推斷出協調者發出的確定是 rollback 通知;若是 B 一樣位於 READY 狀態,則 A 能夠繼續詢問另外的參與者。只有當全部的參與者都位於 READY 狀態時,此時兩階段提交協議沒法處理,將陷入長時間的阻塞狀態。
三階段提交協議(3PC:Three-Phase Commit), 針對兩階段提交存在的問題,三階段提交協議經過引入一個 預詢盤 階段,以及超時策略來減小整個集羣的阻塞時間,提高系統性能。三階段提交的三個階段分別爲:預詢盤(can_commit)、預提交(pre_commit),以及事務提交(do_commit)。
該階段協調者會去詢問各個參與者是否可以正常執行事務,參與者根據自身狀況回覆一個預估值,相對於真正的執行事務,這個過程是輕量的,具體步驟以下:
本階段協調者會根據第一階段的詢盤結果採起相應操做,詢盤結果主要有 3 種:
針對第 1 種狀況,協調者會向全部參與者發送事務執行請求,具體步驟以下:
在上述步驟中,若是參與者等待超時,則會中斷事務。 針對第 2 和第 3 種狀況,協調者認爲事務沒法正常執行,因而向各個參與者發出 abort 通知,請求退出預備狀態,具體步驟以下:
若是第二階段事務未中斷,那麼本階段協調者將會依據事務執行返回的結果來決定提交或回滾事務,分爲 3 種狀況:
針對第 1 種狀況,協調者向各個參與者發起事務提交請求,具體步驟以下:
針對第 2 和第 3 種狀況,協調者認爲事務沒法成功執行,因而向各個參與者發送事務回滾請求,具體步驟以下:
在本階段若是由於協調者或網絡問題,致使參與者遲遲不能收到來自協調者的 commit 或 rollback 請求,那麼參與者將不會如兩階段提交中那樣陷入阻塞,而是等待超時後繼續 commit,相對於兩階段提交雖然下降了同步阻塞,但仍然沒法徹底避免數據的不一致。兩階段提交協議中所存在的長時間阻塞狀態發生的概率仍是很是低的,因此雖然三階段提交協議相對於兩階段提交協議對於數據強一致性更有保障,可是由於效率問題,兩階段提交協議在實際系統中反而更加受寵。
TCC是Try、Confirm 和 Cancel三個單詞首字母縮寫,它們分別的職責是:
Try:負責預留資源(好比新建一條狀態=PENDING的訂單);
作業務檢查,簡單來講就是不能預留已經被佔用的資源;
隔離預留資源。
Confirm:負責落地所預留的資源
真正的執行業務使用try階段預留的資源,冪等。
Cancel: 負責撤銷所預留的資源
須要用戶根據本身的業務場景實現 Try、Confirm 和 Cancel 三個操做;事務發起方在一階段執行 Try 方式,在二階段提交執行 Confirm 方法,二階段回滾執行 Cancel 方法。
關於預留資源要多說兩句,資源都是有限的,所以預留資源都是有時效的,若是當預留資源遲遲得不到Confirm——咱們將這種狀況稱爲timeout——參與方會自行將其Cancel。也就是說參與方對於資源具備自我管理能力,這樣能夠避免因發起方的問題致使資源被長期佔用。
TCC增長了業務檢查和撤銷事務的功能。同時,TCC將2PC數據庫層面的動做提高到了服務層面,不一樣的是TCC的全部動做都是一個本地事務,每一個本地事務都在動做完成後commit到數據庫:
流程步驟:
流程和兩階段提交很是相似。