分布式事务解决方案及Seata 1.6.1案例

一、分布式事务

分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。

二、什么时候需要用到分布式事务

就是指不是单个服务或者单个数据库架构下产生的事务,例如:

  • 跨数据源的分布式事务
  • 跨服务的分布式事务

三、分布式理论

CAP定理

在一个分布式系统中,以下三点特性无法同时满足,「鱼与熊掌不可兼得」

  • 一致性(C):每次读取都会收到最新的信息
  • 可用性(A):系统能够在任何时候处理请求并返回非错误响应,即系统对于用户请求是可用的。
  • 分区容错性(P):系统能够容忍网络中的分区故障,即网络中的节点之间的通信可能被延迟或丢失。

具体地讲在分布式系统中,在任何数据库设计中,一个Web应用「至多只能同时支持上面的两个属性」 。显然,任何横向扩展策略都要依赖于数据分区。因此,设计人员必须在一致性与可用性之间做出选择。

BASE理论

BASE理论是对CAP中AP的一个扩展,通过牺牲强一致性来获得可用性,当出现故障允许部分不可用但要保证 核心功能可用,允许数据在一段时间内是不一致的,但最终达到一致状态。满足BASE理论的事务,我们称之为 “柔性事务”。

  • 基本可用「Basically Available(基本可用)」:分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。如,电商网站交易付款出 现问题了,商品依然可以正常浏览。
  • 软状态「Soft state(软状态)」:由于不要求强一致性,所以BASE允许系统中存在中间状态(也叫软状态),这个状态不影响系统可用 性,如订单的"支付中"、“数据同步中”等状态,待数据最终一致后状态改为“成功”状态。
  • 最终一致「Eventually consistent(最终一致性)」:最终一致是指经过一段时间后,所有节点数据都将会达到一致。如订单的"支付中"状态,最终会变 为“支付成功”或者"支付失败",使订单状态与实际交易结果达成一致,但需要一定时间的延迟、等待。

事务有刚性事务和柔性事务之分。

刚性事务(如单数据库中的本地事务)完全遵循 ACID 规范,即数据库事务正确执行的四个基本要素:

  • 原子性(Atomicity)
  • 一致性(Consistency)
  • 隔离性(Isolation)
  • 持久性(Durability)

柔性事务,主要就是只分布式事务了,柔性事务为了满足可用性、性能与降级服务的需要,降低一致性(Consistency)与隔离性(Isolation)的要求,遵守 BASE 理论:

  • 基本业务可用性(Basic Availability)
  • 柔性状态(Soft state)
  • 最终一致性(Eventual consistency)

四、分布式事务解决方案

分布式事务解决方案大致分为如下3种:

  • 刚性事务
    • 2PC
    • 3PC
  • 柔性事务
    • TCC
  • 基于消息队列的最终一致性
    • 最大努力通知
    • 本地消息表
    • 消息事务

刚性事务

2PC

2PC也就是两阶段提交:

  • 第一阶段:准备阶段
  • 第二阶段:提交阶段

第一阶段:

  • 协调者 向所有的 参与者 发送事务预处理请求,称之为Prepare,并开始等待各 参与者 的响应。
  • 各个 参与者 节点执行本地事务操作,但在执行完成后并不会commit数据库本地事务,而是先向 协调者 报告说:我准备好提交了Yes或者我没准备好弄no

第一阶段执行完后,会有两种可能。1、所有都返回Yes. 2、有一个或者多个返回No。

第二阶段:

  • 如果所有参与者都返回yes,那么协调者向所有参与者发送commit命令,参与者都本地commit,在完成提交之后释放整个事务执行期间占用的事务资源。
  • 如果其中有参与者返回no或者超时没有返回,则协调者向所有参与者发送rollback请求,也就是撤销,将本地事务回滚,不commit。

3PC

3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。

第一阶段(CanCommit):

  • 事务询问 协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。
  • 响应反馈 参与者接到CanCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态。否则反馈No

第二阶段(PreCommit):

根据参与者返回的请求,来决定是否进入PreCommit

  • 假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行。
    1.发送预提交请求 发送一个prepare-commit请求,进入PreCommit状态
    2.事务预提交 参与者接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。
    3.响应反馈 如果参与者成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。

  • 假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。
    1.发送中断请求 协调者向所有参与者发送abort请求。
    2.中断事务 参与者收到来自协调者的abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断。

第三阶段(DoCommit):

进行真正的事务提交

  • 执行提交
    1.发送提交请求 协调接收到参与者发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。
    2.事务提交 参与者接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。
    3.响应反馈 事务提交完之后,向协调者发送Ack响应。
    4.完成事务 协调者接收到所有参与者的ack响应之后,完成事务。

  • 中断事务 协调者没有接收到参与者发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务。
    1.发送中断请求 协调者向所有参与者发送abort请求
    2.事务回滚 参与者接收到abort请求之后,利用其在阶段二记录的undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源。
    3.反馈结果 参与者完成事务回滚之后,向协调者发送ACK消息
    4.中断事务 协调者接收到参与者反馈ACK消息之后,执行事务的中断。

2PC和3PC对比

  • 阻塞问题:2PC在第二阶段(提交阶段)可能会出现阻塞,因为协调者需要等待所有参与者的响应才能决定是否提交事务。这种阻塞可能导致整个系统的性能下降。3PC通过引入预提交阶段,可以在某些情况下减少阻塞问题,使得参与者可以在预提交阶段确认是否可以提交事务。

  • 可靠性和一致性:2PC和3PC都是刚性事务解决方案,强调事务的强一致性和可靠性。在2PC中,所有参与者在接收到准备请求后都将执行事务,并在接收到提交请求时进行事务的提交。在3PC中,预提交阶段可以使得参与者在准备阶段时就能够拒绝事务,从而增加了可靠性和灵活性。

  • 单点故障:2PC中存在单点故障问题,即协调者的故障可能导致整个事务无法完成。3PC通过引入预提交阶段来减少单点故障的影响,当协调者故障时,参与者可以根据预提交消息自行决策是否提交事务。

总的来说,3PC相对于2PC引入了预提交阶段,以减少阻塞问题和单点故障的影响,增加了系统的可靠性和灵活性。然而,3PC也带来了额外的复杂性和通信开销。在实际应用中,需要根据具体的场景和需求来选择2PC还是3PC作为分布式事务解决方案。

TCC

TCC分别是Try、Confirm、Cancle的简称。大致包含2个节点,一是Try,二是Confirm/Cancle。

  • Try 阶段(一阶段):尝试执行,完成所有业务检查(一致性), 预留必须业务资源(占库存)。
  • Confirm 阶段(二阶段):确认执行真正执行业务,不作任何业务检查,只使用 Try 阶段预留的业务资源,Confirm 操作满足需要满足幂等性,Confirm 执行失败后需要进行重试。
  • Cancel 阶段:取消执行,释放 Try 阶段预留的业务资源,Cancel 操作也需要满足幂等性。Cancel 阶段的异常和 Confirm 阶段异常处理方案基本上一致。

TCC的优缺点:
优点:

  • 灵活性:TCC允许开发人员在业务逻辑中显式地定义事务的尝试(Try)、确认(Confirm)和取消(Cancel)阶段。这种显式的编程模型使得开发人员可以更加精确地控制事务的行为,适应各种复杂的业务场景。

  • 高并发性:TCC的尝试阶段通常是在本地执行,不涉及资源锁定或网络通信。这使得尝试阶段可以以高并发的方式执行,提高了系统的吞吐量和性能。

  • 可扩展性:TCC对于分布式系统的扩展性较好。每个参与者负责自己的事务逻辑,可以独立地进行水平扩展,减少了对中心化事务协调器的依赖。

  • 容错性:TCC可以通过取消阶段来处理各种异常情况,如参与者失败、网络故障或超时。在出现异常时,可以执行取消操作,回滚之前的尝试操作,确保数据的一致性。

缺点:TCC 的 Try、Confirm 和 Cancel 操作功能要按具体业务来实现,业务耦合度较高,提高了开发成本。

基于消息队列的最终一致性

最大努力通知

所谓最大努力通知,换句话说就是并不保证100%通知到。这种分布式事务的方案,通常也是借助异步消息进行通知的。

发送者将消息发送给消息队列,接收者从消息队列中消费消息。在这个过程中,如果出现了网络通信故障或者消息队列发生了故障,就有可能导致消息传递失败,即消息被丢失。因此,最大努力通知无法保证每个接收者都能成功接收到消息,但是可以尽最大努力去通知。

下面是一个简单的例子来说明最大努力通知的过程。假设有一个在线商城系统,需要将订单状态更新的消息通知给相关的用户,让他们及时了解订单状态的变化。商城系统通过一个消息队列将消息发送给用户,用户从消息队列中消费消息。具体的流程如下:

  • 商城系统产生了一个订单状态变化的消息,将消息发送到消息队列中
  • 用户从消息队列中获取到该消息,更新订单状态,并显示最新的状态给用户.
  • 如果消息传递成功,就完成了整个通知过程。但是如果出现了消息传递失败的情况,比如消息队列发生了故障,那么就需要尽最大努力去通知。
  • 商城系统会定期检查消息队列中是否存在未被消费的消息,如果存在就重新发送该消息,直到消息被消费为

需要注意的是,在最大努力通知的过程中,可能会出现消息重复发送的情况,也可能会出现消息丢失的情况。因此,在设计最大努力通知系统时,需要根据实际业务需求和风险承受能力来确定最大努力通知的策略和重试次数以及对消息进行去重等处理。

最大努力通知这种事务实现方案,一般用在消息通知这种场景中,因为这种场景中如果存在一些不一致影响也不大

本地消息表

在这里插入图片描述

发送消息方:

  • 需要有一个消息表,记录着消息状态相关信息。

  • 业务数据和消息表在同一个数据库,要保证它俩在同一个本地事务。

  • 在本地事务中处理完业务数据和写消息表操作后,通过写消息到 MQ 消息队列。

  • 消息会发到消息消费方,如果发送失败,即进行重试。

消息消费方:

  • 处理消息队列中的消息,完成自己的业务逻辑。

  • 如果本地事务处理成功,则表明已经处理成功了。

  • 如果本地事务处理失败,那么就会重试执行。

  • 如果是业务层面的失败,给消息生产方发送一个业务补偿消息,通知进行回滚等操作。

开发中使用的消息中间件并不支持事务消息的功能,那么本地消息表是一种不错的最终一致性解决方案

消息事务

在这里插入图片描述
1.事务发起方首先发送半消息到MQ;

2.MQ通知发送方消息发送成功;

3.在发送半消息成功后执行本地事务;

4.根据本地事务执行结果返回commit或者是rollback;

5.如果消息是rollback, MQ将丢弃该消息不投递;如果是commit,MQ将会消息发送给消息订阅方;

6.订阅方根据消息执行本地事务;

7.订阅方执行本地事务成功后再从MQ中将该消息标记为已消费;

8.如果执行本地事务过程中,执行端挂掉,或者超时,MQ服务器端将不停的询问producer来获取事务状态;

9.Consumer端的消费成功机制有MQ保证;

最大努力通知和本地消息表对比

本地消息表相对于最大努力通知而言,引入了本地消息表,通过本地事务来保证消息可以发送成功。相对来说,具有更强的可靠性,可以在一定程度上保证消息的传递不丢失。但是,本地消息表也会带来额外的存储开销和网络通信成本。

而最大努力通知这种方案比较简单,但是可能存在丢消息的情况。其实,一般业务中,也会通过对账来解决的,并不会完全放任消息丢失,只不过对账的机制会有一定的延时,并且可能需要人工介入。

MQ事务消息和本地消息表对比

MQ事务消息:

需要MQ支持半消息机制或者类似特性,在重复投递上具有比较好的去重处理;

具有比较大的业务侵入性,需要业务方进行改造,提供对应的本地操作成功的回查功能;

DB本地消息表:

使用了数据库来存储事务消息,降低了对MQ的要求,但是增加了存储成本;

事务消息使用了异步投递,增大了消息重复投递的可能性;

各方案常见使用场景总结

  • 2PC/3PC:依赖于数据库,能够很好的提供强一致性和强事务性,但延迟比较高,比较适合传统的单体应用,在同一个方法中存在跨库操作的情况,不适合高并发和高性能要求的场景。
  • TCC:适用于执行时间确定且较短,实时性要求高,对数据一致性要求高,比如互联网金融企业最核心的三个服务:交易、支付、账务。
  • 最大努力通知/本地消息表/MQ 事务

最大努力通知:

  • 使用最大努力通知时,事务参与者在完成本地事务后尽力发送通知给其他参与者,但无法保证所有参与者都会成功接收到通知。
  • 适用于对一致性要求较低,可以容忍一定程度的通知丢失或延迟的场景。
  • 通常通过异步方式发送通知,具有较低的延迟和较高的吞吐量。

本地消息表(本地消息事务):

  • 在本地消息表模式中,事务参与者将通知信息写入本地数据库的消息表中,并将消息表的状态与本地事务绑定。
  • 在事务提交之后,消息会异步地被消费者读取和处理。如果消费者处理失败,可以进行重试。
  • 适用于对一致性要求较高,可以容忍较小的延迟的场景,因为消息的可靠性取决于本地数据库的持久性和可恢复性。

MQ事务消息(分布式消息事务):

  • 在MQ事务消息模式中,事务参与者将消息发送到消息中间件(如RabbitMQ、Kafka、RocketMQ等)中,并与本地事务绑定。
  • 消息中间件会将消息保存在事务日志中,并在事务提交后将消息发送给消费者。如果事务回滚,消息将被丢弃。
  • 适用于对一致性要求较高,同时对消息的可靠性和顺序性要求较高的场景。

五、Seata 1.6.1测试

参考:https://blog.csdn.net/weixin_40777510/article/details/129685726

1.配置seata

1.1下载seater-server 1.6.1版本

github地址:https://github.com/seata/seata/releases

1.2配置seater-server

conf目录下有一个application.example.yml,这是模板配置文件,还有一个application.yml,这是真正的配置文件,修改此文件,增加nacos的config和registry配置:

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: 127.0.0.1:8848
      namespace: 29abe988-e6d6-4ca3-85e4-188e3b7e41dd
      group: DEFAULT_GROUP
      username: nacos
      password: nacos
      data-id: seataServer.properties
  registry:
    # support: nacos, eureka, redis, zk, consul, etcd3, sofa
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group: DEFAULT_GROUP
      namespace: 29abe988-e6d6-4ca3-85e4-188e3b7e41dd
      cluster: default
      username: nacos
      password: nacos
  store:
    # support: file 、 db 、 redis
    mode: file
#  server:
#    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
  security:
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login

注意:

  • seata.config.type 与seata.registry.type 都要修改为nacos
  • 修改config与registry中nacos的配置,其中namespace与group须提前在nacos中进行配置

2.配置nacos

2.1 新建namespace

新建namespace命名空间,需与上述seata-server的application.yml中配置一致

在这里插入图片描述

2.1 新建seata的seataServer.properties配置文件

在上述namespace下新建application.yml中seata.config.nacos.data-id提到的配置文件:seataServer.properties
在这里插入图片描述

#For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html
#Transport configuration, for client and server
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

#Transaction routing rules configuration, only for the client
# 此处的mygroup名字可以自定义,只修改这个值即可
service.vgroup_mapping.stock-service-group=default
service.vgroup_mapping.order-service-group=default
#If you use a registry, you can ignore it
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false

#Transaction rule configuration, only for the client
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
#For TCC transaction mode
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h

#Log rule configuration, for client and server
log.exceptionRate=100

#Transaction storage configuration, only for the server. The file, db, and redis configuration values are optional.
# 默认为file,一定要改为db,我们自己的服务启动会连接不到seata
store.mode=db
store.lock.mode=db
store.session.mode=db
#Used for password encryption

#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
# 修改mysql的配置
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
# 指定seata的数据库,下面会提
store.db.url=jdbc:mysql://127.0.0.1:3306/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


#Transaction rule configuration, only for the server
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 configuration, only for the server
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898

注意:

  • 修改service.vgroupMapping.mygroup=default该值,其中mygroup可以自定义,后面我们自己的服务启动时,配置文件中需要指定该group。
  • 修改store.mode store.lock.mode store.session.mode这三个值为db,才能让seata连接到下面的数据库中。
  • 修改store.db配置项下的配置,连接到自己的数据库。
  • 该文件参数的具体作用参考官网:http://seata.io/zh-cn/docs/user/configurations.html
  • 该配置源文件存放在seata目录下:seata/script/config-center/config.txt

3.配置MYSQL

3.1 新建配置表

在seata数据库中新建查询,执行如下sql,sql文件存放在:seata/script/server/db/mysql.sql中

-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
    `xid`                       VARCHAR(128) NOT NULL,
    `transaction_id`            BIGINT,
    `status`                    TINYINT      NOT NULL,
    `application_id`            VARCHAR(32),
    `transaction_service_group` VARCHAR(32),
    `transaction_name`          VARCHAR(128),
    `timeout`                   INT,
    `begin_time`                BIGINT,
    `application_data`          VARCHAR(2000),
    `gmt_create`                DATETIME,
    `gmt_modified`              DATETIME,
    PRIMARY KEY (`xid`),
    KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
    `branch_id`         BIGINT       NOT NULL,
    `xid`               VARCHAR(128) NOT NULL,
    `transaction_id`    BIGINT,
    `resource_group_id` VARCHAR(32),
    `resource_id`       VARCHAR(256),
    `branch_type`       VARCHAR(8),
    `status`            TINYINT,
    `client_id`         VARCHAR(64),
    `application_data`  VARCHAR(2000),
    `gmt_create`        DATETIME(6),
    `gmt_modified`      DATETIME(6),
    PRIMARY KEY (`branch_id`),
    KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
    `row_key`        VARCHAR(128) NOT NULL,
    `xid`            VARCHAR(128),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAR(256),
    `table_name`     VARCHAR(32),
    `pk`             VARCHAR(36),
    `status`         TINYINT      NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_status` (`status`),
    KEY `idx_branch_id` (`branch_id`),
    KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

CREATE TABLE IF NOT EXISTS `distributed_lock`
(
    `lock_key`       CHAR(20) NOT NULL,
    `lock_value`     VARCHAR(20) NOT NULL,
    `expire`         BIGINT,
    primary key (`lock_key`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);

3.2 业务数据库添加undo_log表

-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

4.启动seata-server

双击:seata-server.bat 启动服务

查看两件事:
1.seata-server是否注册进nacos中
2.访问http://127.0.0.1:7091/#/login 能否进入seata的webui,用户名与密码默认为seata
在这里插入图片描述

5.测试seata的AT模式

项目结构如下:
在这里插入图片描述

参考官网的版本信息:https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E

我用的是

  • seata1.6.1
  • Spring Cloud Alibaba 2021.0.5.0
  • Spring Boot 2.6.13
  • Nacos 2.2.0

父pom.xml

<properties>
   <java.version>1.8</java.version>
   <spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
   <springboot.version>2.6.13</springboot.version>
   <lombok.version>1.18.8</lombok.version>

   <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
   <sikpTests>true</sikpTests>
   <maven-source-plugin.version>3.0.1</maven-source-plugin.version>
   <maven-surefire-plugin.version>2.22.1</maven-surefire-plugin.version>
   <seata.version>1.6.1</seata.version>
   <mybatis-plus-boot-starter.version>3.3.0</mybatis-plus-boot-starter.version>
</properties>

<dependencies>
   <dependency>
       <groupId>com.alibaba.cloud</groupId>
       <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
       <exclusions>
           <exclusion>
               <groupId>org.springframework.cloud</groupId>
               <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
           </exclusion>
       </exclusions>
   </dependency>

   <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-loadbalancer -->
   <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-loadbalancer</artifactId>
       <version>3.1.1</version>
   </dependency>

   <dependency>
       <groupId>com.alibaba.cloud</groupId>
       <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
   </dependency>

   <!-- Seata -->
   <dependency>
       <groupId>com.alibaba.cloud</groupId>
       <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
       <version>${spring-cloud-alibaba.version}</version>
       <exclusions>
           <exclusion>
               <groupId>io.seata</groupId>
               <artifactId>seata-spring-boot-starter</artifactId>
           </exclusion>
           <exclusion>
               <groupId>io.seata</groupId>
               <artifactId>seata-all</artifactId>
           </exclusion>
       </exclusions>
   </dependency>
   <dependency>
       <groupId>io.seata</groupId>
       <artifactId>seata-all</artifactId>
       <version>${seata.version}</version>
   </dependency>
   <!-- https://mvnrepository.com/artifact/io.seata/seata-spring-boot-starter -->
   <dependency>
       <groupId>io.seata</groupId>
       <artifactId>seata-spring-boot-starter</artifactId>
       <version>1.6.1</version>
   </dependency>

   <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-bootstrap</artifactId>
       <version>3.0.4</version>
   </dependency>

   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
   </dependency>

   <dependency>
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
   </dependency>

   <!-- mybatis -->
   <dependency>
       <groupId>com.baomidou</groupId>
       <artifactId>mybatis-plus-boot-starter</artifactId>
       <version>${mybatis-plus-boot-starter.version}</version>
   </dependency>
   <!-- mybatis -->

   <dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
   </dependency>

   <dependency>
       <groupId>io.github.openfeign</groupId>
       <artifactId>feign-slf4j</artifactId>
       <version>11.8</version>
   </dependency>

   <!--feign-->
   <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-openfeign</artifactId>
       <version>2.2.6.RELEASE</version>
   </dependency>
</dependencies>

<dependencyManagement>
   <dependencies>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-dependencies</artifactId>
           <version>${springboot.version}</version>
           <type>pom</type>
           <scope>import</scope>
       </dependency>

       <!--Spring Cloud Alibaba-->
       <dependency>
           <groupId>com.alibaba.cloud</groupId>
           <artifactId>spring-cloud-alibaba-dependencies</artifactId>
           <version>${spring-cloud-alibaba.version}</version>
           <type>pom</type>
           <scope>import</scope>
       </dependency>

       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <version>${lombok.version}</version>
       </dependency>

       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
           <version>8.0.28</version>
       </dependency>

   </dependencies>

</dependencyManagement>

<build>
   <plugins>
       <plugin>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-maven-plugin</artifactId>
       </plugin>
   </plugins>
</build>

stock和order的pom.xml一样

<properties>
	<java.version>1.8</java.version>
	<spring-cloud.version>2021.0.5</spring-cloud.version>
</properties>

<dependencies>
</dependencies>

<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-openfeign</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-dependencies</artifactId>
			<version>${spring-cloud.version}</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>

order的配置文件

spring.application.name=order-service
server.port=9091
# Nacos 注册中心地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.loadbalancer.ribbon.enabled: false
# seata 服务分组,要与服务端nacos-config.txt中service.vgroup_mapping的后缀对应
spring.cloud.alibaba.seata.tx-service-group=order-service-group
spring.seata.order-service-group=default
logging.level.io.seata=info
# 数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/seata_order?allowMultiQueries=true
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456

stock的配置文件

spring.application.name=stock-service
server.port=9092
# Nacos 注册中心地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
# seata 服务分组,要与服务端nacos-config.txt中service.vgroup_mapping的后缀对应
spring.cloud.alibaba.seata.tx-service-group=stock-service-group
spring.seata.stock-service-group=default
logging.level.io.seata=info
# 数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/seata_stock?allowMultiQueries=true
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456

参考官网的业务代码:https://github.com/seata/seata-samples/tree/master/springcloud-nacos-seata

启动项目测试:

浏览器输入:http://localhost:9091/order/placeOrder/commit,数据正常扣减

打上断点,可以查看seata往unlog中记录的数据
在这里插入图片描述

查看控制台日志,能看出是2PC模式

在这里插入图片描述

浏览器输入:http://localhost:9091/order/placeOrder/rollback,测试回滚

在这里插入图片描述

MQ分布式事务和feign加seata实现分布式事务有一些区别。 首先,MQ分布式事务是通过消息队列实现的。它的作用是解耦、异步、削峰,实现分布式事务的最终一致性。MQ分布式事务是一种柔性事务的解决方案,适用于高并发场景。在MQ分布式事务中,事务参与者将事务消息发送到消息队列,消息队列再将消息异步分发给事务的其他参与者,各个参与者根据消息处理结果来决定是否提交或回滚事务。 而feign加seata是另一种实现分布式事务的方式。Feign是一种轻量级的、声明式的HTTP客户端,可以方便地实现服务之间的远程调用。而seata是一个开源的分布式事务解决方案,它提供了一套完整的分布式事务管理功能。在使用feign加seata实现分布式事务时,可以使用seata提供的分布式事务管理器来保证各个服务之间的事务一致性。 总的来说,MQ分布式事务和feign加seata实现分布式事务都可以实现分布式事务的一致性,但是它们的实现方式和适用场景有所不同。MQ分布式事务适用于高并发场景,而feign加seata适用于服务之间的远程调用。具体使用哪种方式取决于实际的业务需求和场景。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [seata与MQ用分布式事务区别](https://blog.csdn.net/qq_39761320/article/details/109730112)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [分布式事务解决方案Seata 1.6.1案例](https://blog.csdn.net/qq_42665745/article/details/130805466)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值