分布式事务-Seata入门指南

Seata入门指南

为什么要使用分布式事务

  • 问题点-当账户余额0时,还是可以下单成功,而且扣减库存
  • 新的需求-下单逻辑需要保证数据一致性,当帐户余额不够时,库存回滚,下单失败
  • 解决方案
    • 采用spring事务能解决问题上面的问题嘛,是不能
    • 使用分布式事务解决方案Seata(官方推荐)

Seata是什么

官方文档地址,https://seata.apache.org/zh-cn/docs/overview/what-is-seata/

Seata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata将为用户提供了AT、TCC、SAGA和XA事务模式,为用户打造 一站式的分布式解决方案。

首选方案是AT模式,文档地址https://seata.apache.org/zh-cn/docs/dev/mode/at-mode/,可以做到业务无侵入性。

Seata AT模式的工作流程

1. 非常重要的三个概念

  • TC(Transaction Coordinator)-事务协调者:维护全局和分支事务的状态、驱动全局事务提交或回滚。
  • TM(Transaction Manager)-事务管理器:开始全局事务、提交或回滚全局事务。
  • RM(Resource Manager)-资源管理器:管理分支事务处理的资源,与TC交互以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

2.比如,当前订单服务下单,调用库存服务扣减库存,调用帐户服务扣减帐户余额

3.订单服务要接入TM组件

  • 单操作需要开启全局事务(向TC申请一个全局事务XID),进入下单逻辑
  • 如果下单正常,需要通知TC提交全局事务
  • 如果下单异常,比如余额不足,需要通知TC回滚全局事务

4.订单服务、库存服务、帐户服务都要接入RM组件

  • 提交本地事务的同时,需要向TC注册分支事务信息
  • 接收TC的通知,提交或回滚分支事务

5.TC是独立的服务

  • 维护TM申请的全局事务信息和RM的分支事务信息
  • TM通知TC全局事务提交或回滚,TM要通知RM分支事务提交或回滚

AT模式工作流程

两阶段提交协议的演变:

前提

基于支持本地ACID事务的关系型数据库。

Java应用,通过JDBC访问数据库。

整体机制

一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。

二阶段

  • 提交异步化,非常快速地完成。
  • 回滚通过一阶段的回滚日志进行反向补偿。

写隔离

  • 一阶段本地事务提交前,需要确保先拿到全局锁。
  • 拿不到全局锁,不能提交本地事务。
  • 拿全局锁的尝试被限制在一定范围内,超出范围将放弃,并且回滚本地事务,释放本地锁。

读隔离

在数据库本地事务隔离级别读已提交(Read Comnmitted)或以上的基础上,Seata(AT模式)的默认全局隔离级别是读取未提交(Read Uncommitted)。

问题:如何知道调用的是同一个分布式事务?

开启全局事务会分配一个全局事务XID,微服务链路会传递XID,注册每个分支事务都会带XID。

Seata Server(TC)安装部署

1. 官网参考资料

采用版本:2.0.0,下载地址:https://seata.apache.org/zh-cn/unversioned/release-history/seata-server/.

官网参考资料,Seata新手部署指南:https://seata.apache.org/zh-cn/docs/ops/deploy-guide-beginner/

Seata参数配置官网参考,https://seata.apache.org/zh-cn/docs/user/configurations/

2. TC端存储模式

全局事务分支信息存储到哪儿?

  • Seata1.x支持的模式

    • file:单机模式,全局事务会话信息内存中读写并持久化本地文件root.data,性能较高,但是只支持单机模式部署,生产环境不考虑。
    • db:高可用模式,全局事务传话信息通过db共享,相应性能差些
    • redis:1.3以上版本支持,性能较高,存在事务信息丢失风险,请提交配置适合当前场景的redis持久化配置
  • Seta2.x新增的Raft模式

    • 利用Raft算法实现多个TC之间数据的同步
    • raft模式是最理想的方案,但是当前并不成熟,所以不用考虑

基于从稳定性角度考虑,最终选择采用db模式-创建seata数据库,sql脚本在seata-server-2.0.0\seata\script\db\mysql.sql

3.RM和TM如何找到TC服务

可以将TC服务注册到Nacos,RM和TC通过Nacos注册中心实现TC服务的发现。
注意Seata的注册中心是作用于seata自身的,和微服务自身配置的注册中心无关,但可以共用注册中心。
可以创建一个seata的命名空间,区分seata的TC服务和业务微服务

4.TC的配置是不是也可以交给Nacos配置中心管理?

5.最终方案:db存储模式+Nacos(注册&配置中心)方式部署

前置环境准备
  • db模式准备好seata的数据库
  • 准备好Nacos环境
配置Nacos配置中心地址,修改conf/application.yml文件
seata:
  config:
    # support: nacos, consul, apollo, zk, etcd3
    type: nacos
    nacos:
      server-addr: tlmall-nacos-server:8848
      namespace: seata
      group: SEATA_GROUP
      data-id: seataServer.properties
将seata server的配置上传配置到Nacos配置中心
获取/seta/script/config-center/config.txt,修改为db存储模式,并修改mysql连接配置
store.mode=db
store.lock.mode=db
store.session.mode=db
# mysql5.x
#store.db.driverClassName=com.mysql.jdbc.Driver
#store.db.url=jdbc:mysql://tlmall-mysql:3306/seata2.0.0?useUnicode=true&rewriteBatchedStatements=true
# mysql8.x
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://tlmall-mysql:3306/seata2.0.0?useUnicode=true&rewriteBatchedStatements=true&useSSL=false&characterEncoding=utf8&allowPublicKeyRetrieval=true
store.db.user=root
store.db.password=root
配置事务分组,TC要与client(RM TM)配置的事务分组一致
  • 事务分组:seata的资源逻辑,可以按微服务的需要,在应用程序(客户端)对自行定义事务分组,每组取一个名字。
  • 集群:seata-server服务端一个或多个节点组成的集群cluster。 应用程序(客户端)使用时需要指定事务逻辑分组与Seata服务端集群的映射关系。
service.vgroupMapping.default_tx_group=default
在nacos配置中心中新建dataId为seataServer.properties的配置,配置内容为上面修改后的config.txt中的配置信息

删除了无关配置后,seataServer.properties完整信息如下:

client.metadataMaxAgeMs=30000
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.lock.retryTimes=30
client.rm.reportRetryCount=5
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
client.rm.sqlParserType=druid
client.rm.tableMetaCheckEnable=true
client.rm.tableMetaCheckerInterval=60000
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
client.tm.rollbackRetryCount=5
client.undo.compress.enable=true
client.undo.compress.threshold=64k
client.undo.compress.type=zip
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.logTable=undo_log
client.undo.onlyCareUpdateColumns=true
log.exceptionRate=100
metrics.enabled=false
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
metrics.registryType=compact
server.distributedLockExpireTime=10000
server.enableParallelHandleBranch=false
server.enableParallelRequestHandle=true
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.committingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.rollbackRetryTimeoutUnlockEnable=false
server.session.branchAsyncQueueSize=5000
server.session.enableBranchAsyncRemove=false
server.undo.logDeletePeriod=86400000
server.undo.logSaveDays=7
service.vgroupMapping.default_tx_group=default
store.db.branchTable=branch_table
store.db.datasource=druid
store.db.dbType=mysql
store.db.distributedLockTable=distributed_lock
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.globalTable=global_table
store.db.lockTable=lock_table
store.db.maxConn=30
store.db.maxWait=5000
store.db.minConn=5
store.db.password=root
store.db.queryLimit=100
store.db.url=jdbc:mysql://tlmall-mysql:3306/seata2.0.0?useUnicode=true&rewriteBatchedStatements=true&useSSL=false&characterEncoding=utf8&allowPublicKeyRetrieval=true
store.db.user=root
store.lock.mode=db
store.mode=db
store.publicKey=
store.session.mode=db
tcc.contextJsonParserType=fastjson
tcc.fence.cleanPeriod=1h
tcc.fence.logTableName=tcc_fence_log
transport.compressor=none
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.enableTmClientBatchSendRequest=false
transport.heartbeat=true
transport.rpcRmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.serialization=seata
transport.server=NIO
transport.shutdown.wait=3
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.bossThreadSize=1
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.workerThreadSize=default
transport.type=TCP
启动Seta Server
  • window点击bin目录下seata-server.bat直接启动
  • 启动成功,查看控制台http://127.0.0.1:7091,帐号密码都是seata.
  • 在Nacos注册中可以查看到seata-server注册成功

微服务整合Seata AT模式实战

业务场景

  • 用户下单,订单服务调用库存扣减库存,调用帐户服务扣减服务余额
  • 事务发起者:订单服务
  • 事务参与者:库存服务,帐户服务

订单服务(事务发起者)整合Seata

1.引入seata的依赖
<!-- seata 依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
2.订单服务对应数据库中添加undo_log表(仅AT模式)
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT       NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(128) NOT NULL COMMENT 'global transaction id',
    `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);
3.订单服务application.yml中添加seata配置
  seata:
# seata 服务分组,要与服务端配置service.vgroup_mapping的后缀对应
tx-service-group: default_tx_group
registry:
  # 指定nacos作为注册中心
  type: nacos
  nacos:
    application: seata-server
    server-addr: tlmall-nacos-server:8848
    namespace: seata
    group: SEATA_GROUP

config:
  # 指定nacos作为配置中心
  type: nacos
  nacos:
    server-addr: tlmall-nacos-server:8848
    namespace: seata
    group: SEATA_GROUP
    data-id: seataServer.properties
  • 需要在nacos的控制台中创建一个seata的命名空间,用于和业务微服务隔离
  • 注意:请确保client与server的注册中心和配置中心namespace和group一致
  • 优化:可以将seata配置移动配置中心中
  1. Nacos配置中心创建一个dataId为seata-client.yml的配置,导入seata的配置
  2. 微服务的application.yml引入seata-client.yml
spring:
  config:
    import:
      - optional:nacos:${spring.application.name}.yml
      - optional:nacos:db-common.yml    #数据库公共配置
      - nacos:nacos-discovery.yml
      - optional:nacos:seata-client.yml
4.订单服务作为全局事务发起者,在下单方法添加@GlobalTransactional注解
 @GlobalTransactional(name="createOrder",rollbackFor=Exception.class)
    public Result<?> createOrder(String userId, String commodityCode, Integer count) {

库存服务(事务参与者)整合Seata

  • 和整合订单服务前三步一样
  • 库存服务只需要在扣减库存方法上添加Spring事务@Transactional注解
 @GlobalTransactional(name="createOrder",rollbackFor=Exception.class)
    public Result<?> createOrder(String userId, String commodityCode, Integer count) {

帐户服务(事务参与者)整合Seata

配置同库存服务一样

Seata2.x常见问题

微服务启动报错:io.seata.config.exception.ConfigNotFoundException:service.vgropuMappin.default_tx_group configuration item is required

产生原因&解决思路
原因:无法拉取到service.vgroupMapping.default_tx_group这个配置,也就找不到集群名为default的seata service服务
思路1:检查下微服务端seata配置是否未配置事务分组seata.tx-service-group=default_tx_group
思路2:检查下namespace和group配置server端和client端是否对应,特别注意seataServer.properties是否是SETA_GROUP组的配置

seata server报错:com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure

产生原因&解决思路
原因:无法连上数据库
思路:检查下seataServer.properties中jdbc配置是否正确,检查jdbc版本和mysql版本是否匹配

重启所有服务,测试分布式事务是否生效

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值