分布式事務解決方案及實現


一、事務的ACID原則

  數據庫事務的幾個特性:原子性(Atomicity )、一致性( Consistency )、隔離性或獨立性( Isolation)和持久性(Durabilily),簡稱就是ACID。

  • 原子性:操作這些指令時,要么全部執行成功,要么全部不執行。只要其中一個指令執行失敗,所有的指令都執行失敗,數據進行回滾,回到執行指令前的數據狀態。
  • 一致性:事務的執行使數據從一個狀態轉換為另一個狀態,但是對於整個數據的完整性保持穩定。
  • 隔離性:在該事務執行的過程中,無論發生的任何數據的改變都應該只存在於該事務之中,對外界不存在任何影響。只有在事務確定正確提交之后,才會顯示該事務對數據的改變。其他事務才能獲取到這些改變后的數據。
  • 持久性:當事務正確完成后,它對於數據的改變是永久性的。

二、什么是分布式事務

  事務在單系統中的表現:多次數據庫操作用事務進行管理,來保證ACID原則。

        

   但是如果各個模塊都是單獨獨立出來的微服務,進行了分布式部署,單系統里的事務將不能保證各個數據庫操作的一致性,因此就需要分布式事務來進行統一管理。

        

 三、分布式事務實現方案

  現在的分布式事務實現方案有多種,有些已經被淘汰,如基於XA的兩段式提交、TCC解決方案,還有本地消息表、MQ事務消息,還有一些開源的事務中間件,如LCN、GTS。

  1、基於XA的兩階段提交方案

  XA 它包含兩個部分:事務管理器和本地資源管理器。其中本地資源管理器往往由數據庫實現,比如 Oracle、DB2 這些商業數據庫都實現了 XA 接口,而事務管理器作為全局的協調者,負責各個本地資源的提交和回滾。

  兩階段提交方案應用非常廣泛,幾乎所有商業OLTP (On-Line Transaction Processing)數據庫都支持XA協議。但是兩階段提交方案開發復雜、鎖定資源時間長,對性能影響很大,基本不適合解決微服務事務問題。

        

  2、TCC解決方案

  TCC方案在電商、金融領域落地較多。TCC方案其實是兩階段提交的一種改進。其將整個業務邏輯的每個分支顯式的分成了Try、Confirm、Cancel三個操作。

  • Try 階段主要是對業務系統做檢測及資源預留,完成業務的准備工作。

  • Confirm 階段主要是對業務系統做確認提交,Try階段執行成功並開始執行 Confirm階段時,默認 Confirm階段是不會出錯的。即:只要Try成功,Confirm一定成功。

  • Cancel 階段主要是在業務執行錯誤,需要回滾的狀態下執行的業務取消,預留資源釋放。

  基本原理如下圖所示:

        

  事務開始時,業務應用會向事務協調器注冊啟動事務。之后業務應用會調用所有服務的try接口,完成一階段准備。之后事務協調器會根據try接口返回情況,決定調用confirm接口或者cancel接口。如果接口調用失敗,會進行重試。

  微服務倡導服務的輕量化、易部署,而TCC方案中很多事務的處理邏輯需要應用自己編碼實現,復雜且開發量大。

  3、本地消息表 (異步確保)

  本地消息表是國外的 ebay 搞出來的一套方案,如圖所示:

          

  我們首先需要在本地數據新建一張本地消息表,然后我們必須還要一個MQ(不一定是mq,但必須是類似的中間件)。

  消息表怎么創建呢?這個表應該包括這些字段: id, biz_id, biz_type, msg, msg_result, msg_desc,atime,try_count。分別表示uuid,業務id,業務類型,消息內容,消息結果(成功或失敗),消息描述,創建時間,重試次數, 其中biz_id,msg_desc字段是可選的。

  實現思路為:

  • A 系統在自己本地一個事務里操作同時,插入一條數據到消息表;
  • 接着 A 系統將這個消息發送到 MQ 中去;
  • B 系統接收到消息之后,在一個事務里,往自己本地消息表里插入一條數據,同時執行其他的業務操作,如果這個消息已經被處理過了,那么此時這個事務會回滾,這樣保證不會重復處理消息
  • B 系統執行成功之后,就會更新自己本地消息表的狀態以及 A 系統消息表的狀態;
  • 如果 B 系統處理失敗了,那么就不會更新消息表狀態,那么此時 A 系統會定時掃描自己的消息表,如果有未處理的消息,會再次發送到 MQ 中去,讓 B 再次處理;
  • 這個方案保證了最終一致性,哪怕 B 事務失敗了,但是 A 會不斷重發消息,直到 B 那邊成功為止。

  這個方案嚴重依賴於數據庫的消息表來管理事務,這樣在高並發的情況下難以擴展,同時要在數據庫中額外添加一個與實際業務無關的消息表來實現分布式事務,繁瑣。

  4、MQ事務消息

  直接基於 MQ 來實現事務,不再用本地的消息表。有一些第三方的MQ是支持事務消息的,比如RocketMQ,他們支持事務消息的方式也是類似於采用的二階段提交,但是市面上一些主流的MQ都是不支持事務消息的,比如 RabbitMQ 和 Kafka 都不支持。

             

  實現思想為:

  • A 系統先發送一個 prepared 消息到 mq,如果這個 prepared 消息發送失敗那么就直接取消操作別執行了;
  • 如果這個消息發送成功過了,那么接着執行本地事務,如果成功就告訴 mq 發送確認消息,如果失敗就告訴 mq 回滾消息;
  • 如果發送了確認消息,那么此時 B 系統會接收到確認消息,然后執行本地的事務;
  • mq 會自動定時輪詢所有 prepared 消息回調你的接口,問你,這個消息是不是本地事務處理失敗了,所有沒發送確認的消息,是繼續重試還是回滾?一般來說這里你就可以查下數據庫看之前本地事務是否執行,如果回滾了,那么這里也回滾吧。這個就是避免可能本地事務執行成功了,而確認消息卻發送失敗了。
  • 這個方案里,要是系統 B 的事務失敗了咋辦?重試咯,自動不斷重試直到成功,如果實在是不行,要么就是針對重要的資金類業務進行回滾,比如 B 系統本地回滾后,想辦法通知系統 A 也回滾;或者是發送報警由人工來手工回滾和補償。

  這種方案缺點就是實現難度大,而且主流MQ不支持。

  5、分布式事務中間件解決方案

  分布式事務中間件其本身並不創建事務,而是基於對本地事務的協調從而達到事務一致性的效果。典型代表有:阿里的GTS(https://www.aliyun.com/aliware/txc)、開源應用LCN。

  其實現原理如下:

          

四、LCN分布式事務框架

  1、LCN框架的由來

  在設計框架之初的1.0 ~ 2.0的版本時,框架設計的步驟是如下的,各取其首字母得來的LCN命名。

  鎖定事務單元(lock)、確認事務模塊狀態(confirm)、通知事務(notify)

  2、LCN框架相關資料

tx-lcn官方地址:https://www.txlcn.org/
tx-lcn Github地址:https://github.com/codingapi/tx-lcn
tx-lcn服務下載地址:https://pan.baidu.com/s/1cLKAeE#list/path=%2F
tx-lcn服務源碼地址:https://github.com/codingapi/tx-lcn/tree/master/tx-manager

  3、LCN框架核心執行步驟

創建事務組:是指在事務發起方開始執行業務代碼之前先調用TxManager創建事務組對象,然后拿到事務標示GroupId的過程。

添加事務組:添加事務組是指參與方在執行完業務方法以后,將該模塊的事務信息添加通知給TxManager的操作。

關閉事務組:是指在發起方執行完業務代碼以后,將發起方執行結果狀態通知給TxManager的動作。當執行完關閉事務組的方法以后,TxManager將根據事務組信息來通知相應的參與模塊提交或回滾事務。

  如圖所示,如果調用順序為A{業務A,B{業務B}}這種A包含B的方式,那么正常調用時序圖為:

          

   如果B發生異常,那么時序圖為:

          

  如果調用順序為A{業務A},B{業務B}這種A先於B的方式,那么B發生異常時的時序圖為:

          

  4、LCN應用

  4.1、搭建tx-manager服務

  LCN是通過一個獨立的微服務tx-manager作為分布式事務控制服務端(事務協調器)。需要執行分布式事務控制的微服務應用都通過遠程服務調用的方式,在tx-manager上標記事務組,在執行事務處理后,將本地事務狀態發送到tx-manager中對應的事務組上,tx-manager會根據具體的狀態來通知相應的微服務應用提交或回滾。

  tx-manager也是使用Spring Cloud開發的一個微服務應用,在搭建過程上是非常簡單的。下載tx-manager事務協調器zip壓縮包:https://pan.baidu.com/s/1cLKAeE#list/path=%2F
壓縮包解壓后內容如下:

          

  修改application.properties配置文件,提供本地微服務應用的Eureka注冊中心配置、redis配置。其中redis是事務協調器在處理事務組時使用的臨時存儲。

##########################txmanager-start####################### #服務端口
server.port=8899

#tx-manager不得修改
spring.application.name=tx-manager

spring.mvc.static-path-pattern=/**
spring.resources.static-locations=classpath:/static/ ###########################txmanager-end####################### # eureka地址:由於txManager也是一個SpringCloud的微服務,因此也要注冊服務,交由Eureka Server管理 eureka.client.service-url.defaultZone=http://127.0.0.1:8761/eureka/
eureka.instance.prefer-ip-address=true
 #############################redis-start######################### ## redis 單點環境配置:LCN采用Redis記錄事務狀態組狀態,並基於事務組狀態管理分布式事務 # redis
spring.redis.database=0
spring.redis.timeout=0
spring.redis.host=192.168.1.136
spring.redis.port=6379
spring.redis.pool.max-active=100
spring.redis.pool.max-wait=3000
spring.redis.pool.max-idle=200
spring.redis.pool.min-idle=50
spring.redis.pool.timeout=600
##############################redis-end########################## ###############################LCN-start########################
tm.transaction.netty.delaytime = 5
tm.transaction.netty.hearttime = 15
tm.redis.savemaxtime=30
tm.socket.port=9999
tm.socket.maxconnection=100
tm.compensate.auto=false
tm.compensate.notifyUrl=http://ip:port/path
tm.compensate.tryTime=30
tm.compensate.maxWaitTime=5000

logging.level.com.codingapi=debug

  將修改后的application.properties配置文件打包到tx-manager-x.x.x.jar中,替代jar中原有的默認配置文件。

  使用命令: java -jar tx-manager-x.x.x.jar啟動微服務。

  測試tx-manager事務協調器是否啟動成功可以訪問http://ip:8899/。如下結果代表事務協調器啟動成功:

  4.2、在微服務中使用LCN實現分布式事務管理

  在所有需要處理分布式事務的微服務中增加下述依賴:為統一資源版本,使用properties統一管理版本信息。

<properties>
    <lcn.last.version>4.1.0</lcn.last.version>
</properties>
<dependency>
    <groupId>com.codingapi</groupId>
    <!-- 此例采用SpringCloud,如果是dubbo需要引用另一個包 -->
    <artifactId>transaction-springcloud</artifactId>
    <version>${lcn.last.version}</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>*</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>com.codingapi</groupId>
    <artifactId>tx-plugins-db</artifactId>
    <version>${lcn.last.version}</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>*</artifactId>
        </exclusion>
    </exclusions>
</dependency>

  在全局配置文件中增加下述配置:

# 定義事務協調器所在位置。根據具體環境定義其中的IP地址和端口。
tm.manager.url=http://127.0.0.1:8899/tx/manager/

  使用LCN做分布式事務管理時,微服務應用內必須提供一個用於獲取txUrl(txUrl就是全局配置文件中定義的tm.manager.url)的類型實現,這個類可以使用獨立應用定義,在微服務應用中引入。具體如下:

@Service public class TxManagerTxUrlServiceImpl implements TxManagerTxUrlService {

    @Value("${tm.manager.url}")
    private String url;

    @Override
    public String getTxUrl() {
        return url;
    }
}

  在分布式事務管理代碼中增加注解@TxTransaction。在業務調用方增加的注解需要屬性isStart=true。而被調用方則不需要定義任何的注解屬性。如:

  A服務調用了B服務,那么A服務中代碼:

@TxTransaction(isStart=true)
@Transactional public void trade() {
    //本地調用
    tradeDao.save();
    //遠程調用方
    orderService.order();
}

  B服務中代碼:

@Transactional
@TxTransaction public void order() {
    //本地調用
    orderDao.save();
}

  其中@Transactional用於管理本地事務,而@TxTransaction管理分布式事務,當需要回滾時,調用本地自帶的事務管理器進行回滾。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM