SpringCloudAlibaba分散式事務解決方案Seata實戰與原始碼分析-上

itxiaoshen發表於2022-07-14

概述

定義

Spring Cloud Alibaba Seata 官網地址 https://seata.io/zh-cn/ 最新版本1.5.2

Spring Cloud Alibaba Seata 文件地址 https://seata.io/zh-cn/docs/overview/what-is-seata.html

Spring Cloud Alibaba Seata GitHub原始碼地址 https://github.com/seata/seata

Spring Cloud Alibaba Seata 是一款開源的分散式事務解決方案,致力於提供高效能和簡單易用的分散式事務服務。Seata 將為使用者提供了 AT、TCC、SAGA 和 XA 事務模式,為使用者打造一站式的分散式解決方案。

  • TC (Transaction Coordinator) - 事務協調者:維護全域性和分支事務的狀態,驅動全域性事務提交或回滾。
  • TM (Transaction Manager) - 事務管理器:定義全域性事務的範圍:開始全域性事務、提交或回滾全域性事務。
  • RM (Resource Manager) - 資源管理器:管理分支事務處理的資源,與TC交談以註冊分支事務和報告分支事務的狀態,並驅動分支事務提交或回滾。

image-20220711144621918

事務概念

資料庫事務(簡稱:事務,Transaction)是指資料庫執行過程中的一個邏輯單位,由一個有限的資料庫操作序列構成。事務擁有以下四個特性,習慣上被稱為ACID特性,也是指資料庫事務正確執行的四個基本要素的縮寫包括原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、永續性(Durability)。一個支援事務(Transaction)的資料庫,必須要具有這四種特性,否則在事務過程(Transaction processing)當中無法保證資料的正確性,交易過程極可能達不到交易方的要求。

  • 原子性(Atomicity)
    • 原子性是指事務是一個不可分割的工作單位,事務中的操作要麼都發生,要麼都不發生。
  • 一致性(Consistency)
    • 事務前後資料的完整性必須保持一致。轉賬 A:1000 B:1000 轉 200 事務成功 A:800 B:1200
  • 隔離性(Isolation)
    • 事務的隔離性是多個使用者併發訪問資料庫時,資料庫為每一個使用者開啟的事務,一個事務在執行過程中不能看到其他事務執行過程的中間狀態,不能被其他事務的運算元據所干擾,多個併發事務之間要相互隔離,通過配置事務隔離級別可以避免髒讀、重複讀等問題。
  • 永續性(Durability)
    • 永續性是指一個事務一旦被提交,它對資料庫中資料的改變就是永久性的,接下來即使資料庫發生故障也不應該對其有任何影響。

具有ACID的特性的資料庫支援強一致性,強一致性代表資料庫本身不會出現不一致,每個事務是原子的,或者成功或者失敗,事物間是隔離的,互相完全不影響,而且最終狀態是持久落盤的,因此,資料庫會從一個明確的狀態到另外一個明確的狀態,中間的臨時狀態是不會出現的,如果出現也會及時的自動的修復,因此是強一致的。

本地事務與分散式事務

  • 本地事務:簡單理解就是整個服務(不是整個專案,而是該微服務)只操作單一資料來源如同一伺服器同一MySQL資料庫。事務僅限於對單一資料庫資源的訪問控制,架構服務化以後,事務的概念延伸到了服務中。倘若將一個單一的服務操作作為一個事務,那麼整個服務操作只能涉及一個單一的資料庫資源,這類基於單個服務單一資料庫資源訪問的事務,被稱為本地事務(Local Transaction)。

image-20220711140358720

  • 分散式事務:分散式事務指事務的參與者、支援事務的伺服器、資源伺服器以及事務管理器分別位於不同的分散式系統的不同節點之上,且屬於不同的應用,分散式事務需要保證這些操作要麼全部成功,要麼全部失敗。
    • 隨著網際網路的發展,從之前的單一專案逐漸向分散式服務做轉換,現如今微服務在各個公司已經普遍存在,而當時的本地事務已經無法滿足分散式應用的要求,因此分散式服務之間事務的協調就產生了問題,如果做到多個服務之間事務的操作,能夠像本地事務一樣遵循ACID原則也成為難題。
    • 本質上來說,分散式事務就是為了保證不同資料庫的資料一致性;較之基於單一資料庫資源訪問的本地事務,分散式事務的應用架構更為複雜。在不同的分散式應用架構下,實現一個分散式事務要考慮的問題並不完全一樣,比如對多資源的協調、事務的跨服務傳播等,實現機制也是複雜多變。

image-20220711140330212

分散式事務理論基礎

分散式事務存在兩大理論依據: CAP定律和BASE理論;在前面文章《Apache ZooKeeper原理剖析及分散式理論名企高頻面試》也有介紹過。BASE 理論是對 CAP 中一致性和可用性權衡的結果,其來源於對大型網際網路分散式實踐的總結,是基於 CAP 定理逐步演化而來的。其核心思想既然無法做到強一致性(Strong consistency),但每個應用都可以根據自身的業務特點,採用適當的方式來使系統達到最終一致性(Eventual consistency)。

  • Basically Available(基本可用):保證核心業務是可以使用,至於其他業務可以適當降低響應時間甚至是服務降級。假設系統,出現了不可預知的故障,但還是能用,相比較正常的系統而言:
    • 響應時間上的損失:正常情況下的搜尋引擎 0.5 秒即返回給使用者結果,而基本可用的搜尋引擎可以在 1 秒作用返回結果。
    • 功能上的損失:在一個電商網站上,正常情況下,使用者可以順利完成每一筆訂單,但是到了大促期間,為了保護購物系統的穩定性,部分消費者可能會被引導到一個降級頁面。

image-20220711141737535

  • Soft state(軟狀態):相對於原子性而言,要求多個節點的資料副本都是一致的,這是一種 “硬狀態”。軟狀態指的是:允許系統中的資料存在中間狀態,並認為該狀態不影響系統的整體可用性,即允許系統在多個不同節點的資料副本存在資料延時。
  • Eventually consistent(最終一致性):系統能夠保證在沒有其他新的更新操作的情況下,資料最終一定能夠達到一致的狀態,因此所有客戶端對系統的資料訪問最終都能夠獲取到最新的值。

兩階段提交(2PC)

2PC即兩階段提交協議,是將整個事務流程分為兩個階段,P是指準備階段,C是指提交階段。

  • 準備階段(Prepare phase):事務管理器給每個參與者傳送 Prepare訊息,每個資料庫參與者在本地執行事務,並寫本地的 Undo/Redo日誌,此時事務沒有提交。

    • undo日誌是記錄修改前的資料,用於資料庫回滾。
    • Redo日誌是記錄修改後的資料,用於提交事務寫入資料檔案。
  • 提交階段(commit phase):如果事務管理器收到了參與者的執行失敗或者超時訊息時,直接給每個參與者傳送(Rollback) 訊息,如果收到參與者都成功,傳送(Commit) 參與者根據事務管理器的指令執行提交或者回滾操作,並釋放事務處理過程中使用的資源。

    • 執行事務成功提交

    image-20220711143556229

    • 執行事務失敗回滾

image-20220711143607537

三階段提交(3PC)

3PC 主要是為了解決兩階段提交協議的單點故障問題和縮小參與者阻塞範圍。 是二階段提交(2PC)的改進版本,引入參與節點的超時機制之外,3PC把2PC的準備階段分成事務詢問(該階段不會阻塞)和事務預提交,則三個階段分別為 CanCommit、PreCommit、DoCommit

  • 3PC是2PC的升級版,引入了超時機制,解決了單點故障引起的事務阻塞問題,但是3PC依然不能解決事務一致性的問題,因為在DoCommit階段,如果由於網路或者超時等原則導致參與者收不到協調者傳送過來的 中斷事務訊息(abort) ,過了這個時間後,參與者會提交事務,本來是應該進行回滾,提交事務後,會導致資料不一致的問題出現
  • 2PC雖然在網路故障情況下存在強一致性被破壞的問題,但是故障恢復以後能保證最終一致性,3PC雖然有超時時間,解決了阻塞,提高了可用性,但是犧牲了一致性,如果針對網路波動問題導致資料問題這一點上,2PC是優於3PC的。

安裝

版本下載

Seata Server作為分散式事務的TC,也即是事務協調者,也是一個Server服務,需要獨立部署。Seata Server官網支援多種方式部署:直接部署,使用 Docker, 使用 Docker-Compose, 使用 Kubernetes, 使用 Helm、高可用部署,官網最新版本為1.5.2。我們這裡選擇直接部署+高可用,先下載官網提供的二進位制包。

# 下載最新版本
wget https://github.com/seata/seata/releases/download/v1.5.2/seata-server-1.5.2.tar.gz
# 解壓
tar -xvf seata-server-1.5.2.tar.gz
# 進入目錄 
cd seata

建議部署結構

官方推薦生產使用的部署結構首先基於Nacos配置中心來管理Seata的配置(事務模式、儲存模式如基於DB資料庫配置等)、將使用Nacos註冊中心將Seata自己註冊到Nacos,分散式事務所關聯微服務可以通過註冊中心感知Seata,實現服務發現和負載均衡等功能,最後將全域性協調資訊寫入資料庫,以便多個Seata之間資料共享。

image-20220713110334849

配置資料庫

先下載1.5.2的原始碼 https://github.com/seata/seata/archive/refs/tags/v1.5.2.zip 解壓後seata根目錄下script\server\db檔案下有三類資料庫sql指令碼。我們選擇MySQL的作為資料庫因此使用mysql.sql指令碼。

image-20220713110635704

建立seata資料庫,執行指令碼,最新版本1.5.2比之前1.4.x版本很明顯多了一張distributed_lock表,原來只有3張

image-20220713110947117

Nacos 配置

為了後面可以結合示例說明,這裡使用前面使用simple_ecommerce命令空間,建立seataServer.properties,組為ecom-group

image-20220713111518394

seataServer.properties的配置內容如下,主要是配置db模式及其連線資訊

transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableTmClientBatchSendRequest=false
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.rpcRmRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
transport.serialization=seata
transport.compressor=none

service.vgroupMapping.default_tx_group=default
service.default.grouplist=192.168.50.94:8091
service.enableDegrade=false
service.disableGlobalTransaction=false

client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=true
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h

log.exceptionRate=100

store.mode=db
store.lock.mode=db
store.session.mode=db
store.publicKey=

store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://192.168.50.95:3308/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=123456
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000

server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
server.xaerNotaRetryTimeout=60000
server.session.branchAsyncQueueSize=5000
server.session.enableBranchAsyncRemove=false
server.enableParallelRequestHandle=false

metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898

上面seataServer.properties的內容直接從官網複製過來修改,來自在原始碼根目錄下script\config-center\config.txt

image-20220713113038165

Seata配置

在Seata的conf目錄下有application.yml和application.example.yml,application.example.yml是配置樣例參考,修改application.yml中config和registry為nacos,詳細內容如下:

server:
  port: 7091

spring:
  application:
    name: seata-server

logging:
  config: classpath:logback-spring.xml
  file:
    path: ${user.home}/logs/seata
  extend:
    logstash-appender:
      destination: 127.0.0.1:4560
    kafka-appender:
      bootstrap-servers: 127.0.0.1:9092
      topic: logback_to_logstash

console:
  user:
    username: seata
    password: seata


seata:
  config:
    # support: nacos 、 consul 、 apollo 、 zk  、 etcd3
    type: nacos
    nacos:
      server-addr: 192.168.50.95:8848
      namespace: a2b1a5b7-d0bc-48e8-ab65-04695e61db01
      group: ecom-group
      username: nacos
      password: nacos
      ##if use MSE Nacos with auth, mutex with username/password attribute
      #access-key: ""
      #secret-key: ""
      data-id: seataServer.properties
  registry:
    # support: nacos 、 eureka 、 redis 、 zk  、 consul 、 etcd3 、 sofa
    type: nacos
    preferred-networks: 30.240.*
    nacos:
      application: seata-server
      server-addr: 192.168.50.95:8848
      group: ecom-group
      namespace: a2b1a5b7-d0bc-48e8-ab65-04695e61db01
      cluster: default
      username: nacos
      password: nacos
      ##if use MSE Nacos with auth, mutex with username/password attribute
      #access-key: ""
      #secret-key: ""

  server:
    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
    max-commit-retry-timeout: -1
    max-rollback-retry-timeout: -1
    rollback-retry-timeout-unlock-enable: false
    enableCheckAuth: true
    retryDeadThreshold: 130000
    xaerNotaRetryTimeout: 60000
    recovery:
      handle-all-session-period: 1000
    undo:
      log-save-days: 7
      log-delete-period: 86400000
    session:
      branch-async-queue-size: 5000 #branch async remove queue size
      enable-branch-async-remove: false #enable to asynchronous remove branchSession
  metrics:
    enabled: false
    registry-type: compact
    exporter-list: prometheus
    exporter-prometheus-port: 9898
  transport:
    rpc-tc-request-timeout: 30000
    enable-tc-server-batch-send-response: false
    shutdown:
      wait: 3
    thread-factory:
      boss-thread-prefix: NettyBoss
      worker-thread-prefix: NettyServerNIOWorker
      boss-thread-size: 1
  security:
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login

由於前面配置的資料庫為MySQL8的版本,而官方打包在target目錄下seata-server.jar中包含的mysql-connector-java-5.1.35.jar驅動

image-20220713134840421

因此這裡我們拷貝mysql-connector-java-8.0.28.jar到target目錄下,並修改bin/seata-server.sh的啟動指令碼

image-20220713135619330

# 在圖中位置增加
JAVA_OPT="${JAVA_OPT} -Xbootclasspath/a:${BASEDIR}/target/mysql-connector-java-8.0.28.jar"
# 啟動seata
sh ./bin/seata-server.sh

成功啟動,日誌中可以看到Seata啟動控制檯埠7091和Seata提供服務的埠8091(為控制檯埠+1000)

image-20220713135515045

訪問http://192.168.5.52:7091/埠,使用者名稱密碼為seata/seata(在application.yml中配置),登入成功後頁面如下

image-20220713135923394

此外Seata的服務也已經註冊到Nacos中

image-20220713140236360

**本人部落格網站 **IT小神 www.itxiaoshen.com

相關文章