什麼是事務?
事務從本質上講就是:邏輯上的一組操作,組成這組操作的各個邏輯單元在不同的服務甚至伺服器上,保證它們要成功就都成功,要失敗就都失敗。
事務的四大特性
提到事務就不得不提事務的四大特性(基本特征) ACID:
- 原子性(atomicity):“原子”的本意是“不可再分”,事務的原子性表現為一個事務中涉及到的多個操作在邏輯上缺一不可。事務的原子性要求事務中的所有操作要麼都執行,要麼都不執行。
- 一緻性(consistency):“一緻”指的是資料的一緻,具體是指:所有資料都處于滿足業務規則的一緻性狀态。一緻性原則要求:一個事務中不管涉及到多少個操作,都必須保證事務執行之前資料是正确的,事務執行之後資料仍然是正确的。如果一個事務在執行的過程中,其中某一個或某幾個操作失敗了,則必須将其他所有操作撤銷,将資料恢複到事務執行之前的狀态,這就是復原。
- 隔離性(isolation):在應用程式實際運作過程中,事務往往是并發執行的,是以很有可能有許多事務同時處理相同的資料,是以每個事務都應該與其他事務隔離開來,防止資料損壞。隔離性原則要求多個事務在并發執行過程中不會互相幹擾。
- 持久性(durability):持久性原則要求事務執行完成後,對資料的修改永久的儲存下來,不會因各種系統錯誤或其他意外情況而受到影響。通常情況下,事務對資料的修改應該被寫入到持久化存儲器中。
并發事務可能會帶來的問題
- 髒讀:一個事務可以讀取另一個事務未送出的資料
- 不可重複讀:一個事務可以讀取另一個事務已送出的資料 單條記錄前後不比對
- 虛讀(幻讀):一個事務可以讀取另一個事務已送出的資料 讀取的資料前後多了或者少了
我們實踐出真知:舉個例子,mysql(5.6.16)
首先建立一張表:
CREATE TABLE `test_account` (
`id` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`money` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `test_account` VALUES (1, '張三', 1000);
INSERT INTO `test_account` VALUES (2, '李四', 1000);
INSERT INTO `test_account` VALUES (3, '王五', 1000);
檢視目前資料庫隔離級别(詳見下文):
select @@tx_isolation;
# 結果:READ-COMMITTED
# 設定事務的隔離級别
# set tx_isolation='隔離級别';
# 我們簡單做下髒讀的複現,開啟倆個事務,第一個視窗做資料修改,但不送出:
start transaction;
UPDATE test_account set money = money - 100 WHERE id = 1;
# 第二個視窗做資料查詢,利用查詢到的值去做資料處理:
start transaction;
SELECT money from test_account WHERE id = 1;
結果:1000
那麼這時讀到的資料是不準确的,這就是髒讀
我們解決并發讀的問題可以設定隔離級别解決問題:
隔離級别 | 髒讀 | 不可重複讀 | 幻讀 |
讀未送出 (Read uncommitted) | √ | √ | √ |
讀已送出 (Read committed) | × | √ | √ |
可重複讀 (Repeatable read) | × | × | √ |
可串行化 (Serializable ) | × | × | × |
Spring傳播行為
Spring傳播行為 | 介紹 |
REQUIRED | 支援目前事務,如果不存在,就建立一個 |
SUPPORTS | 支援目前事務,如果不存在,就不使用事務 |
MANDATORY | 支援目前事務,如果不存在,抛出異常 |
REQUIRES_NEW | 如果有事務存在,挂起目前事務,建立一個新的事務 |
NOT_SUPPORTED | 以非事務方式運作,如果有事務存在,挂起目前事務 |
NEVER | 以非事務方式運作,如果有事務存在,抛出異常 |
NESTED | 如果目前事務存在,則嵌套事務執行(嵌套式事務) |
事務的傳播行為不是jdbc規範中的定義。傳播行為主要針對實際開發中的問題
分布式事務
為什麼要有分布式事務?
本地事務隻能解決同一工程中的事務問題,而現在的場景更加複雜,關系到多個服務,怎麼保證要麼都成功,要麼都失敗?
分布式系統異常除了本地事務那些異常之外,還有:機器當機、網絡異常、消息丢失、消息亂序、資料錯誤、不可靠的TCP、存儲資料丢失等,這時候就需要引入分布式事務。
分布式事務出現的場景
- 不同的服務,不同資料庫
- 相同的服務,不同資料庫
- 不同的服務,相同資料庫
分布式事務基礎
資料庫的 ACID 四大特性,已經無法滿足我們分布式事務,這個時候有很多大佬提出一些新的理論。
CAP:
分布式存儲系統的CAP原理(分布式系統的三個名額):
- Consistency(一緻性):在分布式系統中的所有資料備份,在同一時刻是否同樣的值。
- 對于資料分布在不同節點上的資料來說,如果在某個節點更新了資料,那麼在其他節點如果都能讀取到這個最新的資料,那麼就稱為強一緻,如果有某個節點沒有讀取到,那就是分布式不一緻。
- Availability(可用性):在叢集中一部分節點故障後,叢集整體是否還能響應用戶端的讀寫請求。(要求資料需要備份)
- Partition tolerance(分區容忍性):大多數分布式系統都分布在多個子網絡。每個子網絡就叫做一個區(partition)。分區容錯的意思是,區間通信可能失敗。
CAP理論就是說在分布式存儲系統中,最多隻能實作上面的兩點。而由于目前的網絡硬體肯定會出現延遲丢包等問題,是以分區容忍性是我們無法避免的。是以我們隻能在一緻性和可用性之間進行權衡,沒有系統能同時保證這三點。要麼選擇CP、要麼選擇AP。
BASE:
BASE是對CAP中一緻性和可用性權衡的結果,其來源于對大規模網際網路系統分布式實踐的結論,是基于CAP定理逐漸演化而來的,其核心思想是即使無法做到強一緻性(Strong consistency),但每個應用都可以根據自身的業務特點,采用适當的方式來使系統達到最終一緻性(Eventual consistency)。接下來看看BASE中的三要素:
- Basically Available(基本可用)
- 基本可用是指分布式系統在出現故障的時候,允許損失部分可用性,即保證核心可用。 電商大促時,為了應對通路量激增,部分使用者可能會被引導到降級頁面,服務層也可能隻提供降級服務。這就是損失部分可用性的展現。
- Soft state(軟狀态)
- 軟狀态是指允許系統存在中間狀态,而該中間狀态不會影響系統整體可用性。分布式存儲中一般一份資料至少會有三個副本,允許不同節點間副本同步的延時就是軟狀态的展現。mysql replication的異步複制也是一種展現。
- Eventually consistent(最終一緻性)
- 最終一緻性是指系統中的所有資料副本經過一定時間後,最終能夠達到一緻的狀态。弱一緻性和強一緻性相反,最終一緻性是弱一緻性的一種特殊情況。
BASE模型是傳統ACID模型的反面,不同于ACID,BASE強調犧牲高一緻性,進而獲得可用性,資料允許在一段時間内的不一緻,隻要保證最終一緻就可以了。
分布式事務解決方案
主流的解決方案如下:
- 基于XA協定的兩階段送出(2PC)
- TCC程式設計模式
- 消息事務+最終一緻性
兩階段送出(2PC):
2PC即兩階段送出協定,是将整個事務流程分為兩個階段,準備階段(Prepare phase)、送出階段(commit phase),2是指兩個階段,P是指準備階段,C是指送出階段。
第一階段:事務協調器要求每個涉及到事務的資料庫預送出(precommit)此操作,并反映是否可以送出. 第二階段:事務協調器要求每個資料庫送出資料。 其中,如果有任何一個資料庫否決此次送出,那麼所有資料庫都會被要求復原它們在此事務中的那部分資訊。
XA 是一個兩階段送出協定,又叫做 XA Transactions。 目前主流資料庫均支援2PC,XA協定。
XA目前在商業資料庫支援的比較理想,在mysql資料庫中支援的不太理想,mysql的XA實作,沒有記錄prepare階段日志,主備切換會導緻主庫與備庫資料不一緻。許多nosql也沒有支援XA,這讓XA的應用場景變得非常狹隘。
TCC補償式事務:
TCC補償式事務是一種程式設計式分布式事務。
TCC 其實就是采用的補償機制,其核心思想是:針對每個操作,都要注冊一個與其對應的确認和補償(撤銷)操作。TCC模式要求從服務提供三個接口:Try、Confirm、Cancel。
- Try:主要是對業務系統做檢測及資源預留
- Confirm:真正執行業務,不作任何業務檢查;隻使用Try階段預留的業務資源;Confirm操作滿足幂等性。
- Cancel:釋放Try階段預留的業務資源;Cancel操作滿足幂等性。
整個TCC業務分成兩個階段完成:
第一階段:主業務服務分别調用所有從業務的try操作,并在活動管理器中登記所有從業務服務。當所有從業務服務的try操作都調用成功或者某個從業務服務的try操作失敗,進入第二階段。
第二階段:活動管理器根據第一階段的執行結果來執行confirm或cancel操作。如果第一階段所有try操作都成功,則活動管理器調用所有從業務活動的confirm操作。否則調用所有從業務服務的cancel操作。
消息事務+最終一緻性:
基于消息中間件的兩階段送出往往用在高并發場景下,将一個分布式事務拆成一個消息事務(A系統的本地操作+發消息)+B系統的本地操作,其中B系統的操作由消息驅動,隻要消息事務成功,那麼A操作一定成功,消息也一定發出來了,這時候B會收到消息去執行本地操作,如果本地操作失敗,消息會重投,直到B操作成功,這樣就變相地實作了A與B的分布式事務。
但是A和B并不是嚴格一緻的,而是最終一緻的,我們在這裡犧牲了一緻性,換來了性能的大幅度提升。當然,這種玩法也是有風險的,如果B一直執行不成功,那麼一緻性會被破壞,具體業務具體分析
總結:
- 高并發最終一緻:消息事務+最終一緻性
- 低并發基本一緻:二階段送出
- 高并發強一緻:沒有解決方案
分布式事務架構-seata
Seata 是一款開源的分布式事務解決方案,緻力于在微服務架構下提供高性能和簡單易用的分布式事務服務。
結構
Seata有3個基本元件:
- Transaction Coordinator(TC):事務協調器,維護全局事務的運作狀态,負責協調并驅動全局事務的送出或復原。
- Transaction Manager(TM):事務管理器,控制全局事務的邊界,負責開啟一個全局事務,并最終發起全局送出或全局復原的決議。
- Resource Manager(RM):資料總管,控制分支事務,負責分支注冊、狀态彙報,并接收事務協調器的指令,驅動分支(本地)事務的送出和復原。
生命周期(重點)
- TM 向 TC 申請開啟一個全局事務,全局事務建立成功并生成一個全局唯一的 XID。
- XID 在微服務調用鍊路的上下文中傳播。
- RM 向 TC 注冊分支事務,将其納入 XID 對應全局事務的管轄。
- TM 向 TC 發起針對 XID 的全局送出或復原決議。
- TC 排程 XID 下管轄的全部分支事務完成送出或復原請求。
引入seata并使用
- 把undo_log表導入資料庫中
- 部署seata-server啟動
- 在項目中添加seata依賴:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.2.0</version>
</dependency>
- 配置檔案中添加配置:
spring.cloud.alibaba.seata.tx-service-group=my_test_tx_group
- 引入steata配置檔案:registry.conf和file.conf
java代碼
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}
/**
* 需要将 DataSourceProxy 設定為主資料源,否則事務無法復原
*
* @param druidDataSource The DruidDataSource
* @return The default datasource
*/
@Primary
@Bean("dataSource")
public DataSource dataSource(DruidDataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}
}
使用
使用stata架構非常的簡單,隻需要引入注解就好了
- 分支事務方法上:@Transactional
- 全局事務方法上:@GlobalTransactional
作者:去見小熊
連結:https://juejin.cn/post/7260904509199417381
來源:稀土掘金