Java詳細分析LCN框架分佈式事務

2PC兩階段提交協議

分佈式事務通常采用2PC協議,全稱Two Phase Commitment Protocol。該協議主要為瞭解決在分佈式數據庫場景下,所有節點間數據一致性的問題。分佈式事務通過2PC協議將提交分成兩個階段:

  • 階段一為準備(prepare)階段。即所有的參與者準備執行事務並鎖住需要的資源。參與者ready時,向transaction manager報告已準備就緒。
  • 階段二為提交階段(commit)。當transaction manager確認所有參與者都ready後,向所有參與者發送commit命令。

2PC和3PC的區別就是解決參與者超時的問題和多加瞭一層詢問,保證數據的傳輸可靠性。

LCN

LCN並不生產事務,LCN隻是本地事務的協調工,TX-LCN定位於一款事務協調性框架,框架其本身並不操作事務,而是基於對事務的協調從而達到事務一致性的效果。

參考文檔:https://www.codingapi.com/docs/txlcn-preface/

LCN基本實現原理

  • 發起方與參與方都與我們的 LCN 管理器一直保持長連接;
  • 發起方在調用接口之前,先向 LCN 管理器申請一個全局的事務分組id;
  • 發起方調用接口的時候在請求頭中傳遞事務分組id;
  • 參與方獲取到請求頭中有事務分組的id的,則當前業務邏輯執行完實現假關閉,不會提交或者回滾當前的事務。
  • 發起方調用完接口後,如果出現異常的情況下,在通知給事務協調者回滾事務,這時候事務協調則告訴給參與方回滾當前的事務。

搭建全局協調者

1、在github上面下載 Lcn 源代碼

倉庫地址:https://github.com/codingapi/tx-lcn,註意下載版本。

2、將項目導入idea中,啟動對應的項目

修改對應的配置文件:

spring.application.name=TransactionManager
server.port=7970
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://www.kaicostudy.com:3306/transaction_lcn?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=123456

mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.use-generated-keys=true

# TxManager Host Ip
tx-lcn.manager.host=127.0.0.1
# TxClient連接請求端口
tx-lcn.manager.port=8070
# 心跳檢測時間(ms)
tx-lcn.manager.heart-time=15000
# 分佈式事務執行總時間
tx-lcn.manager.dtx-time=30000
#參數延遲刪除時間單位ms
tx-lcn.message.netty.attr-delay-time=10000
tx-lcn.manager.concurrent-level=128
# 開啟日志
tx-lcn.logger.enabled=true
logging.level.com.codingapi=debug

#redis 連接信息
spring.redis.host=www.kaicostudy.com
spring.redis.port=6379
redis\u5BC6\u7801
#spring.redis.password=

將項目中提供的SQL語句在數據庫中執行,創建對應的表。

請求路徑:http://127.0.0.1:7970/admin/index.html

默認登錄密碼:codingapi

登錄成功頁面

端口介紹:

8070:TM事務消息端口

7970:後臺管理頁面登錄頁面

使用LCN解決分佈式事務問題

在分佈式系統A系統調用B系統服務接口的時候時候,兩個服務的都需要使用 LCN 來控制分佈式事務。

使用步驟:

1、引入lcn 相關的maven依賴

<dependency>
    <groupId>com.codingapi.txlcn</groupId>
    <artifactId>txlcn-tc</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.codingapi.txlcn</groupId>
    <artifactId>txlcn-txmsg-netty</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>

2、yml 配置文件增加 lcn 的配置

tx-lcn:
  client:
    manager-address: www.kaicostudy.com:8070
  logger:
    enabled: true

3、使用

springboot項目主類上加上註解:@EnableDistributedTransaction

參與方與發起方都要加上該註解

@LcnTransaction

@Transactional

調用案例:

A服務的方法,需要去調用B服務方法

@Service
public class ServiceA {
    @Autowired
    private ValueDao valueDao; //本地db操作
    @Autowired
    private ServiceB serviceB;//遠程B模塊業務
    @LcnTransaction //分佈式事務註解
    @Transactional //本地事務註解
    public String execute(String value) throws BusinessException {
        // step1. call remote service B
        String result = serviceB.rpc(value);  // (1)
        // step2. local store operate. DTX commit if save success, rollback if not.
        valueDao.save(value);  // (2)
        valueDao.saveBackup(value);  // (3)
        return result + " > " + "ok-A";
    }
}

B服務的方法,被A服務的方法調用

@Service
public class ServiceB {
    @Autowired
    private ValueDao valueDao; //本地db操作
    @LcnTransaction //分佈式事務註解
    @Transactional  //本地事務註解
    public String rpc(String value) throws BusinessException {
        valueDao.save(value);  // (4)
        valueDao.saveBackup(value);  // (5)
        return "ok-B";
    }
}

源碼分析

一個請求一個線程

代碼執行邏輯:

1、判斷方法是否有加上@L cnTransaction, 如果有加上該註解則直接會走 切面類 TransactionAspect

2、判斷當前線程緩存中是否有事務分組id,如果沒有緩存則是為發起方,如果有緩存則是為參與方

3、隨機的創建分組的id,將該分組id註冊到協調者中。

4、本地 threadLock 緩存該事務分組id

5、A服務(發起方)調用B(參與方)服務的接口,重寫瞭 RequestInterceptor(該接口是feign框架提供的攔截器,基本上每個rpc框架都會提供類似的攔截器) feign 客戶端,將該事務分組id設置到請求中

6、執行到B服務接口,Spring TracingApplier實現,在請求之前攔截,從請求頭中獲取事務分組id,放入到當前線程緩存中

7、B服務接口走到aop裡面代碼時,會先判斷是發起方還是參與方。

8、從緩存中獲取該事務分組id,當前派單服務則是為參與方,在告訴給協調者加入該事務分組。.

Lcn 如何判斷自己是發起方還是參與方?

根據當前的線程threadlocal 中獲取事務分組id, 如果能夠成功獲取到則是為參與方,沒有能夠獲取到就是為發起方。

參與方如何加入LCN全局協調者?

發起方會把事務id註冊到協調者裡面去,參與方根據請求頭裡面的事務分組id加入該事務。

發起方如何通知全局回滾還是提交?

發起方的方法執行完成之後,會修改事務狀態,再根據全局協調者通知其他參與者事務執行完成。反之,如果發起方的方法執行方法異常,事務狀態改為錯誤狀態,再通過全局協調者發送給其他參與者,參與者再回滾事務即可。

A調用B,B調用C 到底會生產幾次事務id?

每次原遠程調用接口都會生成一個事務id,但是一條調用鏈上隻有一個事務分組id(全局id)。隻有A是發起方,B和C都是參與方。可以從請求頭中獲取到事務分組id就是參與方,表示加入到這個分組裡面去的。

入口:@LcnTransaction,TransactionAspect 切面類。

feign 重寫的攔截器,給請求頭添加信息,事務分組id

到此這篇關於Java詳細分析LCN框架分佈式事務的文章就介紹到這瞭,更多相關Java LCN框架內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: