事務簡介
事務的核心是鎖和并發
采用同步控制的方式保證并發的情況下性能盡可能高,且容易理解。
優勢是方便理解;它的劣勢是性能比較低。
盡管看起來計算機可以并行處理很多事情,但實際上每個CPU單位時間內只能做一件事。
而且計算機就做三件事,讀、寫、算來回重復做這三件事,所有的任務都可以看成這三件事的集合。
計算機的這種特性引出了一個問題:當多個人去讀、算、寫操作時,如果不加訪問控制,系統勢必會產生沖突。
而事務相當于在讀、算、寫操作之外增加了同步的模塊,進而保證只有一個線程進入事務當中,而其他線程不會進入。
事務與并發控制相通
單個事務單元
事物就三個指令
beginTransaction
commit
rollback
begin和commit之間的過程就是一個事務單元
事務的四大特性分別是:原子性、一致性、隔離性和持久性
- 原子性,指的是事務中包含的所有操作要么全做,要么全不做;
- 一致性,是指在事務開始以前,數據庫處于一致性的狀態,事務結束后,數據庫也必須處于一致性的狀態;
- 隔離性要求系統必須保證事務不受其他并發執行的事務的影響;
-
持久性是指一個事務一旦成功完成,它對數據庫的改變必須是永久的,即使是在系統遇到故障的情況下也不會丟失,數據的重要性決定了事務的持久性的重要。
單個事務單元
事務單元是通過Begin-Traction,然后Commit(Begin-Traction、Commit和Rollback之間所有針對數據的寫入、讀取的操作都應該添加同步訪問),Begin和Commit之間就是一個同步的事務單元。例如,Bob給Smith 100塊錢就是一個事務單元,這個過程中有很多步操作,具體如上圖所示;但對業務來說,僅是一個轉賬的操作。
一組事務單元
當三個賬戶都在進行轉賬操作時,每個操作都涉及Smith賬戶,所有的事務都會排隊,形成一組事務單元。
事務單元之間的Happen-Before關系中的四種可能性:讀寫、寫讀、讀讀、寫寫。所有事務之間的關系都可以抽象成這四種之一,來對應現在所有的業務邏輯處理。
在此基礎之上,需要用最快的速度處理多個事務單元之間的關系,同時還能保障這四種操作的邏輯順序。
單個事務單元的其他例子
除了轉賬操作是事務單元外,諸如商品要建立一個基于GMT_Modified的索引、從數據庫中讀取一行記錄、向數據庫中寫入一行記錄,同時更新這行記錄的所有索引、刪除整張表等都是一個事務單元。
事務單元的實現方式
Two Phase Lock(2PL)是數據庫中非常重要的一個概念。
數據庫操作Insert、Update、Delete都是先讀再寫的操作。
例如Insert操作是先讀取數據,讀取之后判斷數據是否存在,如果不存在,則寫入該數據,如果數據存在,則返回錯誤。
假設在該場景下沒有讀操作,只是單純寫入數據,則數據本身并沒有事務操作,Delete、Update操作與之類似。
數據庫利用這些操作的特性,在每一次查詢過程中,只要查到數據,就會在該數據上加鎖。
理論上,所有被讀取的數據都已加鎖,不會再被其他人讀到,也就是說對數據進行的中間操作狀態對所有人都不可見,當所有中間狀態完成后,提交操作時,解開鎖,此時數據對所有系統可見。
例如在轉賬過程中,所有人只能看到兩種狀態:
- 開始時,A有錢,B沒錢;
- 結束時,B有錢,A沒錢,
中間A減掉錢,B尚未加上錢的狀態被鎖隱藏掉了,這個操作就是數據庫中處理事務的最標準的方式。
如上圖所示:
- Trx2(JoeLock)與其他事務不相關,因此可以并行執行;
- Trx1需要Lock兩個數據Boblock和Smithlock,
- Trx3同樣需要Lock這兩個數據,因此Trx3必須等待,且等待在 Boblock上,Trx3會等到Trx1完成后才會開始;
- Joe事務會先結束。
單機事務的典型異常應對策略
處理事務的常見方法
處理事務的常見方法有排隊法、排他鎖、讀寫鎖、MVCC等方式。
排隊法
事務處理中最重要也是最簡單的方案是排隊法,單線程地處理一堆數據。在Redis中,如果數據全部在內存中,則單線程處理所有Put、Get操作效率最高。
這是因為多線程本質是CPU模擬多個線程,這種模擬是以上下文切換為代價,而對于內存的數據庫來說,沒有上下文切換時效率最高。因此,單個CPU綁定一塊內存的數據,針對這塊數據做多次讀寫操作時都是在單個CPU上完成的,單線程處理方式在內存的情況是效率是最優的。
下層所使用的存儲決定了是否需要用到多線程。
如果是內存操作,則可以動態地申請和銷毀內存塊;
而磁盤的IOPS很低(100--150),但吞吐量很高。
IOPS: 每秒進行讀寫(I/O)操作的次數,多用于數據庫等場合,衡量隨機訪問的性能,
由于磁盤的IOPS僅為內存的幾千分之一,意味著必須將大量的讀寫操作聚合成一個Batch后再提交時才能達到較好的性能。而將大量請求攢到一起的方式就是異步,也就是請求本身和線程不綁定,線程可以不Block(本質來說還是一種多線程的方式),處理完一個線程后再處理其他線程。這種做法的核心是將大量不同的請求提交到一個Buffer中,再由該Buffer統一讀取或者寫入磁盤,從而提高效率。
在慢速設備中,多線程或異步非常常見,在設計系統時,面對磁盤、網絡、SSD等慢速設備必須考慮使用多線程。
排他鎖
多線程場景,可以利用排他鎖快速隔離并發讀寫事務。
數據庫中有一些事務單元是共享的,
上圖中的事務單元1是共享的,事務單元2/3共享數據;
針對事務單元2/3共享數據的所有讀寫Block住,事務單元1單獨用一個鎖來控制,用這種方式完成系統的訪問控制。
讀寫鎖
只讀事務過程中,數據一定不被修改,因此多個查詢操作可以并行執行,針對讀讀場景的優化自然而然產生——讀寫鎖。
讀寫鎖的核心是在多次讀的操作中,同時允許多個讀者來訪問共享資源,提高并發性。
MVCC
本質是Copy On Write,也就是每次寫都是以重新開始一個新的版本的方式寫入數據,數據庫包含了之前的所有版本。在數據讀的過程中,先申請一個版本號,如果該版本號小于正在寫入的版本號,則數據一定可以查詢到,無需等到新版本完全寫完即可返回查詢結果。
讀讀不阻塞,讀寫/寫讀不阻塞,只有寫寫操作串行。
事務的調優原則
事務的調優的思路是在不影響業務應用的前提下:
- 第一,盡可能減少鎖的覆蓋范圍,例如Myisam表鎖到Innodb的行鎖就是一個減少鎖覆蓋范圍的過程;對于原位鎖(排他鎖、讀寫鎖等)可變為MVCC多版本(本質仍然是減少鎖的范圍)。
- 第二,增加鎖上可并行的線程數,例如讀鎖和寫鎖的分離,允許并行讀取數據。
- 第三,選擇正確鎖類型,其中悲觀鎖適合并發爭搶比較嚴重的場景; 樂觀鎖適合并發爭搶不太嚴重
分布式事務
二段式提交
分為準備階段和提交階段
準備階段
- 協調者節點向所有參與者節點詢問是否可以執行提交操作(vote),并開始等待各參與者節點的響應。
- 參與者節點執行詢問發起為止的所有事務操作,并將Undo信息和Redo信息寫入日志。(注意:若成功這里其實每個參與者已經執行了事務操作)
- 各參與者節點響應協調者節點發起的詢問。如果參與者節點的事務操作實際執行成功,則它返回一個”同意”消息;如果參與者節點的事務操作實際執行失敗,則它返回一個”中止”消息。
提交階段
- 如果協調者收到了參與者的失敗消息或者超時,直接給每個參與者發送回滾(Rollback)消息;
- 否則,發送提交(Commit)消息;
- 參與者根據協調者的指令執行提交或者回滾操作,釋放所有事務處理過程中使用的鎖資源。(注意:必須在最后階段釋放鎖資源)
當協調者節點從所有參與者節點獲得的相應消息都為”同意”時
- 協調者節點向所有參與者節點發出”正式提交(commit)”的請求。
- 參與者節點正式完成操作,并釋放在整個事務期間內占用的資源。
- 參與者節點向協調者節點發送”完成”消息。
- 協調者節點受到所有參與者節點反饋的”完成”消息后,完成事務。
如果任一參與者節點在第一階段返回的響應消息為”中止”,或者 協調者節點在第一階段的詢問超時之前無法獲取所有參與者節點的響應消息時
- 協調者節點向所有參與者節點發出”回滾操作(rollback)”的請求。
- 參與者節點利用之前寫入的Undo信息執行回滾,并釋放在整個事務期間內占用的資源。
- 參與者節點向協調者節點發送”回滾完成”消息。
- 協調者節點受到所有參與者節點反饋的”回滾完成”消息后,取消事務。
三段式提交
三階段提交有兩個改動點
- 引入超時機制。同時在協調者和參與者中都引入超時機制。
- 在第一階段和第二階段中插入一個準備階段。保證了在最后提交階段之前各參與節點的狀態是一致的。
三階段提交就有CanCommit、PreCommit、DoCommit三個階段。
CanCommit階段
3PC的CanCommit階段其實和2PC的準備階段很像。協調者向參與者發送commit請求,參與者如果可以提交就返回Yes響應,否則返回No響應。
- 事務詢問 協調者向參與者發送CanCommit請求。詢問是否可以執行事務提交操作。然后開始等待參與者的響應。
- 響應反饋 參與者接到CanCommit請求之后,正常情況下,如果其自身認為可以順利執行事務,則返回Yes響應,并進入預備狀態。否則反饋No
PreCommit階段
協調者根據參與者的反應情況來決定是否可以事務的PreCommit操作。
根據響應情況,有以下兩種可能。
假如協調者從所有的參與者獲得的反饋都是Yes響應,那么就會執行事務的預執行。
- 發送預提交請求 協調者向參與者發送PreCommit請求,并進入Prepared階段。
- 事務預提交 參與者接收到PreCommit請求后,會執行事務操作,并將undo和redo信息記錄到事務日志中。
- 響應反饋 如果參與者成功的執行了事務操作,則返回ACK響應,同時開始等待最終指令。
假如有任何一個參與者向協調者發送了No響應,或者等待超時之后,協調者都沒有接到參與者的響應,那么就執行事務的中斷。
- 發送中斷請求 協調者向所有參與者發送abort請求。
- 中斷事務 參與者收到來自協調者的abort請求之后(或超時之后,仍未收到協調者的請求),執行事務的中斷。
doCommit階段
該階段進行真正的事務提交,也可以分為以下兩種情況。
執行提交
- 發送提交請求 協調接收到參與者發送的ACK響應,那么他將從預提交狀態進入到提交狀態。并向所有參與者發送doCommit請求。
- 事務提交 參與者接收到doCommit請求之后,執行正式的事務提交。并在完成事務提交之后釋放所有事務資源。
3.響應反饋 事務提交完之后,向協調者發送Ack響應。
4.完成事務 協調者接收到所有參與者的ack響應之后,完成事務。
中斷事務
協調者沒有接收到參與者發送的ACK響應(可能是接受者發送的不是ACK響應,也可能響應超時),那么就會執行中斷事務。
- 發送中斷請求 協調者向所有參與者發送abort請求
- 事務回滾 參與者接收到abort請求之后,利用其在階段二記錄的undo信息來執行事務的回滾操作,并在完成回滾之后釋放所有的事務資源。
3.反饋結果 參與者完成事務回滾之后,向協調者發送ACK消息
4.中斷事務 協調者接收到參與者反饋的ACK消息之后,執行事務的中斷。
在doCommit階段,如果參與者無法及時接收到來自協調者的doCommit或者abort請求時,會在等待超時之后,會繼續進行事務的提交。
這個應該是基于概率來決定的,當進入第三階段時,說明參與者在第二階段已經收到了PreCommit請求,那么協調者產生PreCommit請求的前提條件是他在第二階段開始之前,收到所有參與者的CanCommit響應都是Yes。
一旦參與者收到了PreCommit,意味他知道大家其實都同意修改了
所以,一句話概括就是,當進入第三階段時,由于網絡超時等原因,雖然參與者沒有收到commit或者abort響應,但是他有理由相信:成功提交的幾率很大。
一致性
分布式事務控制的最終目標是實現一致性,方案大體分為實時一致性和最終一致性兩種:
兩階段提交是比較典型的實時一致性方案
提供補償事務和基于消息隊列的異步處理方案是最終一致性方案。
目前的主流數據庫在本地事務提交后都不能回滾。
事務隔離性的本質就是如何正確處理讀寫沖突和寫寫沖突,這在分布式事務中又是一個難點
分布式事務最初起源于處理多個數據庫之間的數據一致性問題,但隨著IT技術的高速發展,大型系統中逐漸使用SOA服務化接口替換直接對數據庫操作,所以如何保證各個SOA服務之間的數據一致性也被劃分到分布式事務的范疇。
兩段式事務
兩段式事務也就是著名的XA事務,XA是由X/Open組織提出的分布式事務的規范,也是使用最為廣泛的多數據庫分布式事務規范,目前市面上主流的數據庫MySQL,Oralce,SQLServer等都支持XA事務。
基于SOA接口的分布式事務
目前比較流行的SOA分布式事務解決方案是TCC事務,TCC事務的全稱為:Try-Confirm/Cancel,翻譯成中文即:嘗試、確定、取消。
TCC模式的扣除金幣操作,接口提供者針對扣除金幣這一操作需要提供三個SOA接口:
- 扣除金幣Try接口,嘗試扣除金幣,這里只是鎖定玩家賬戶中需要被扣除的金幣,并沒有真正扣除金幣,類似于信用卡的預授權;假設玩家賬戶中100金幣,調用該接口鎖定60金幣后,鎖定的金幣不能再被使用,玩家賬戶中還有40金幣可用
- 扣除金幣Confirm接口,確定扣除金幣,這里將真正扣除玩家賬戶中被鎖定的金幣,類似于信用卡的確定預授權完成刷卡
- 扣除金幣Cancel接口,取消扣除金幣,被鎖定的金幣將返還到玩家的賬戶中,類似于信用卡的撤銷預授權取消刷卡
SOA接口調用者如何使用這三個接口呢?
調用者先執行扣除金幣Try接口,再去執行其他任務(比如添加道具),當其他任務執行成功,調用者執行扣除金幣Confirm接口確認扣除金幣,而當其他任務執行異常,調用者則執行扣除金幣Cancel接口取消扣除金幣。
即使我們使用了TCC事務,也無法完美的保證各個SOA服務之間的數據一致性。
但TCC事務為我們屏蔽了大多數異常導致的數據不一致,同時一般情況下,進行Confirm或Cancel操作時產生異常的概率極小極小,所以對于一些強一致性系統,我們還是會使用TCC事務來保證多個SOA服務之間的數據一致性。
TCC事務存在不小的性能問題
其實我們使用的基于后置提交的多數據庫事務與TCC事務都屬于強一致性事務,使用強一致性事務能保證事務的實時性,但卻很難在高并發環境中保證性能。
最終一致性事務這幾個字看起來很牛逼,但說白了就是異步數據補償,即在核心流程我們只保證核心數據的實時數據一致性,對于非核心數據,我們通過異步程序來保證數據一致性。
異步數據補償
目前主流觸發異步數據補償的方式有兩種:
- 使用消息隊列實時觸發數據補償,核心流程在保證核心數據的一致性后,使用消息隊列的方式通知異步程序進行數據補償,這種方式能近乎實時的使數據達到最終一致性,但如果消息隊列或異步程序出現異常,數據一致性也將不能保證
- 使用定時任務周期性觸發數據補償,核心流程在保證核心數據的一致性后直接返回,由定時任務周期性觸發數據補償程序,這種方式雖不能像消息隊列那樣能近乎實時的使數據達到最終一致性,但數據補償程序出現異常時,我們能比較容易在下個周期對數據進行修復,能最大限度的保證數據的一致性
上面兩種異步數據補償的方式各有利弊,消息隊列方式實時性強,但在異常情況下一致性弱,而定時任務方式實時性弱,但在異常情況下一致性強。
注:消息系統還是很穩定的,一般選擇第一種
其實最優的策略是同時使用消息隊列與定時任務觸發數據補償。
正常情況下,我們使用消息隊列近乎實時的異步觸發數據補償,而針對那些極少發生的異常,我們使用定時任務周期性的修補數據。
大師談經驗
對原理的取舍
形成模式,產品
在理解了原理的情況下,做出更多取舍,同業務貼合更好的解決業務訴求
容易理解的模型往往性能都不好,性能好的模型往往不容易理解
《分布式系統原理與范型》
理解了數據庫原理,你才能知道我們在那里做了取舍
排它鎖是最高級鎖
其它鎖都是讀寫鎖的升級
單機事務——小結
原子性、一致性、隔離性(擴展MVCC/SNAPSHOT ISOLATION)、持久性
從單機事務到分布式事務
GTS(全局事務服務)柔性事務
事務就是保證代碼向你寫的一樣,真實的執行,這就是事務的核心
所有的時間戳分配器都會面臨單機的自增問題
分布式事務的主要難點
事務延遲變大問題
事務異常處理
日志記錄
MVCC的順序問題
分布式數據庫方案
共享磁盤方案
鎖延遲變大問題
采用遠程內存直接訪問RDMA (遠程直接內存讀寫)(FC/IB) infin band
Oracle RAC(非常有代表性)
事務的異常處理
放棄分布式事務
RAC
Raid的方案,Shared Disk
優勢:
兼容性好,SQL全兼容
能提升讀性能
劣勢:
寫性能巨大下降,原因要等待多臺Cache全寫入,這是一個同步等待的過程
高可用切換時間長,就是不可用時切換到備機時間長
擴展性有限,4臺基本是極限,否則寫入時間沒法要了
傳遞距離有限制。
從北京到深圳,光速需30ms
廣播算法,面包法算法(棄用)
一般談分布式DB,就會談2PC,就會談MVCC
標準的兩階段提交,只要可讀就說明數據是一致的,因為兩階段提交是可以保證一致性
事務要解決的是四種情況下,操作的沖突問題
WR
WW
RR
RW
讀寫鎖只能在RR的時候能夠并行
MVCC只有在WW時,不能并行
MVCC有個單點問題
PG-XL/PG-XC方案:發號機
存儲可以擴展
TrueTimeAPI
可以在多機房內做數據同步
在未來的一段時間內,因為表的誤差很小,那么很長時間就不用對表。
使用原子鐘+GPS來維持時間準確性
利用TrueTimeAPI來保證happen before,時間為2e
利用這個時間服務器,就可以當做全局SCN或Trx_id服務使用
代價:
每個事務的提交之間的延遲
山寨貨NTP服務
機器與機器之間時間誤差比較大(150ms)
無法保證happen-before
Spanner解決跨城數據高可用,自動容錯,標準的2PC+MVCC