一篇带你快速上手 Seata

数据库 其他数据库
事务通常由高级数据库操作语言或编程语言书写的用户程序所引起,并用形如 begin transaction 或 end transaction 语句(或函数调用)来界定。

1.事务

事务(Transaction),在数据库操作中,指的是一个原子性的操作序列。这个序列中的所有操作要么全部成功,要么全部失败,绝不会出现部分成功的情况。你可以将事务想象成一个数据库操作的“整体”,要么一起完成,要么一起取消。

事务通常由高级数据库操作语言或编程语言书写的用户程序所引起,并用形如 begin transaction 或 end transaction 语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。

事务通常具备以下四个特性,简称为 ACID

  • 原子性(Atomicity):事务是不可分割的最小操作单位,事务中的所有操作要么全部成功,要么全部失败,不会停留在中间状态。
  • 一致性(Consistency):事务完成后,系统状态应该保持一致,即从一个一致的状态转换到另一个一致的状态。
  • 隔离性(Isolation):在事务未完成之前,不允许其他事务访问它的数据,保证事务之间相互独立,不受其他事务的干扰。
  • 持久性(Durability):一旦事务提交后,所做的更改将永久保存在数据库中,即使系统崩溃也不会丢失事务的结果。

(1)分布式事务

分布式事务:在多个独立的资源或服务之间保证一致性的事务操作。涉及多个系统(如多个数据库、微服务等),并确保所有参与的资源要么全部成功,要么全部失败,以保持数据一致性。分布式事务广泛应用于分布式系统、微服务架构等需要跨数据库或跨服务的场景。

(2)Java 分布式事务

 分布式事务:是指在多个数据库或系统中,通过技术手段,协调多个操作,使其作为一个整体来执行。也就是说,这些操作要么全部成功,要么全部失败,以保证整个系统的数据一致性。通常,分布式事务会采用两阶段提交(2PC)等协议,由事务管理器(如Atomikos、Bitronix)来协调各个参与者(如数据库)的行为。事务管理器会向所有参与者发送提交或回滚的请求,只有所有参与者都确认收到并执行了请求,事务才算完成。   

通过  技术实现的分布式事务控制,通常使用 () 进行管理。 允许  应用程序在多个不同的数据源之间管理全局事务,确保在分布式系统中保持数据一致性。 分布式事务通常使用事务管理器(如 Atomikos、Bitronix)协调多个资源,并通过协议(如 2PC)实现事务的提交或回滚。

(3)2PC(Two-Phase Commit)

2PC 协议简单、实现容易,但在网络不稳定的环境下,可能出现阻塞问题。例如,如果协调者在提交阶段宕机,所有参与者将被阻塞在等待状态,影响系统性能。此外,2PC缺少有效的容错机制,一旦协调者出现故障,事务状态可能难以恢复。

TCC(Try-Confirm-Cancel)

  • Try 阶段:资源预留。
  • Confirm 阶段:确认提交。
  • Cancel 阶段:取消操作。

TCC 具备灵活的错误处理和恢复机制,适用于异步、长事务和高并发场景。每个服务都可以实现自己的 Try、Confirm、Cancel 逻辑,从而避免传统 2PC 带来的阻塞和性能问题,但实现难度相对较高,因为需要开发人员手动编写事务的补偿逻辑。

CAP 理论:CAP 理论由计算机科学家 Eric Brewer 在 2000 年提出,后来被广泛接受并成为分布式系统设计中的重要指导原则。分布式系统中的基本定理,指出在一个分布式数据存储系统中,无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition Tolerance)这三个特性。

BASE 理论:一种相对 CAP 理论的分布式系统设计理论,提出一种弱一致性的替代方案,用于满足分布式系统的高可用性需求。核心思想是弱化强一致性的要求,以提高系统的可用性和性能。BASE 适用于那些不需要强一致性、但需要高响应速度和高可用性的分布式系统,比如电商平台和社交媒体等。

XA

XA是一种两阶段提交协议规范,由X/Open组织提出。它定义了资源管理器(如数据库)和事务管理器之间交互的接口,用于协调分布式事务。

  • XA的两个阶段:

a.准备阶段(Prepare):事务管理器向所有参与者发送准备提交的请求。如果所有参与者都准备好了,则进入提交阶段;否则,进入回滚阶段。

b.提交(或回滚)阶段:事务管理器向所有参与者发送提交或回滚的请求。所有参与者根据收到的指令执行提交或回滚操作。

  • XA的优点:

a.实现了强一致性,保证了数据的一致性。

  • XA的缺点:

a.性能问题:2PC协议涉及到多个网络交互,性能较低。

b.单点故障:事务管理器是单点,一旦故障,整个系统可能无法正常工作。

c.阻塞问题:参与者在准备阶段需要一直持有锁,影响系统的并发性能。

AT(补偿事务)

AT是一种基于补偿的分布式事务解决方案。它在业务操作之前,会先记录操作的补偿逻辑,如果业务操作失败,则执行补偿逻辑来撤销之前的操作。

  • AT的三个阶段:

a.Try阶段:执行业务操作,并记录补偿信息。

b.Confirm阶段:如果Try阶段成功,则提交事务。

c.Cancel阶段:如果Try阶段失败,或者Confirm阶段失败,则执行补偿操作。

  • AT的优点:

a.性能较好:避免了长时间的锁等待。

b.灵活度高:可以自定义补偿逻辑。

  • AT的缺点:

a.实现复杂:需要开发人员手动编写补偿逻辑。

b.补偿逻辑的正确性:补偿逻辑的编写难度较大,如果编写不当,可能导致数据不一致。

SAGA

SAGA是一种长事务解决方案,它将长事务拆分成多个本地事务,通过补偿机制来保证最终一致性。

  • SAGA的执行过程:

a.正向操作:按照顺序执行一系列本地事务。

b.补偿操作:如果某个本地事务执行失败,则按照逆序执行相应的补偿事务。

  • SAGA的优点:

a.灵活度高:可以根据业务需求灵活调整。

b.性能较好:避免了长事务带来的性能问题。

  • SAGA的缺点:

a.实现复杂:需要仔细设计补偿逻辑,保证最终一致性。

b.错误处理复杂:需要考虑各种异常情况,并保证补偿操作的幂等性。

2.Seata

https://seata.apache.org/zh-cn/

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

(1)AT 模式

一阶段 - 执行业务操作并记录回滚日志: 会在多个服务中执行业务操作并记录回滚日志。

二阶段 - 根据事务协调器的指令提交或回滚: 的事务协调器会决定最终是提交还是回滚整个分布式事务。

写隔离

例:两个并发的分布式事务 T1 和 T2,初始值为 1000, 通过本地锁和全局锁的组合确保数据的一致性和隔离性。

读隔离

在  的 AT 模式中,默认的全局读隔离级别是读未提交(Read Uncommitted),这意味着事务在读取数据时可能会读到尚未提交的变更数据。这是为了提升性能,因为读未提交可以减少锁的竞争。但在某些场景中,系统可能需要更高的隔离级别,即 读已提交(Read Committed),这可以通过在查询时使用 SELECT FOR UPDATE 语句来实现。

3.案例

https://github.com/apache/incubator-seata-samples

用户购买商品的业务逻辑。

整个业务逻辑由 3 个微服务提供支持

  • 仓储服务(stock service):对给定的商品扣除仓储数量。
  • 订单服务(order service):根据采购需求创建订单。
  • 帐户服务(account service):从用户账户中扣除余额。

图片图片

(1)数据库

创建 MySQL 数据库:

$ docker run -d -p 3306:3306 --name seata -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7
$ docker container ls
CONTAINER ID   IMAGE       COMMAND                   CREATED          STATUS          PORTS                               NAMES
0fb9c8e50a82   mysql:5.7   "docker-entrypoint.s…"   50 seconds ago   Up 49 seconds   0.0.0.0:3306->3306/tcp, 33060/tcp   seata

依次创建 stock、order 和 account 数据库:

$ docker exec -it seata mysql -uroot -p123456
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.44 MySQL Community Server (GPL)
Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> CREATE DATABASE `order`;
Query OK, 1 row affected (0.00 sec)

mysql> CREATE DATABASE stock;
Query OK, 1 row affected (0.00 sec)

mysql> CREATE DATABASE account;
Query OK, 1 row affected (0.00 sec)

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| account            |
| mysql              |
| order              |
| performance_schema |
| stock              |
| sys                |
+--------------------+
7 rows in set (0.00 sec)

(2)数据表

依次在 stock、order 和 account 数据库创建 stock_tbl、order_tbl 和 account_tbl 数据表,每个数据库附带 undo_log 数据表:

CREATE DATABASE IF NOT EXISTS `stock`; 
CREATE DATABASE IF NOT EXISTS `order`; 
CREATE DATABASE IF NOT EXISTS `account`;
USE `stock`;
DROP TABLE IF EXISTS `stock_tbl`;
CREATE TABLE `stock_tbl`
(
    `id`             int(11) NOT NULL AUTO_INCREMENT,
    `commodity_code` varchar(255) DEFAULT NULL,
    `count`          int(11) DEFAULT 0,
    PRIMARY KEY (`id`),
    UNIQUE KEY (`commodity_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `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,
    PRIMARY KEY (`id`),
    UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
USE `order`;
DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl`
(
    `id`             int(11) NOT NULL AUTO_INCREMENT,
    `user_id`        varchar(255) DEFAULT NULL,
    `commodity_code` varchar(255) DEFAULT NULL,
    `count`          int(11) DEFAULT 0,
    `money`          int(11) DEFAULT 0,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `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,
    PRIMARY KEY (`id`),
    UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
USE `account`;
DROP TABLE IF EXISTS `account_tbl`;
CREATE TABLE `account_tbl`
(
    `id`      int(11) NOT NULL AUTO_INCREMENT,
    `user_id` varchar(255) DEFAULT NULL,
    `money`   int(11) DEFAULT 0,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `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,
    PRIMARY KEY (`id`),
    UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

mysql> USE stock;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> SHOW TABLES;
+-----------------+
| Tables_in_stock |
+-----------------+
| stock_tbl       |
| undo_log        |
+-----------------+
2 rows in set (0.00 sec)

mysql> USE order;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> SHOW TABLES;
+-----------------+
| Tables_in_order |
+-----------------+
| order_tbl       |
| undo_log        |
+-----------------+
2 rows in set (0.00 sec)

mysql> USE account;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> SHOW TABLES;
+-------------------+
| Tables_in_account |
+-------------------+
| account_tbl       |
| undo_log          |
+-------------------+
2 rows in set (0.00 sec)

(3)启动服务

https://github.com/apache/incubator-seata/releases

选择下载的是:https://github.com/apache/incubator-seata/releases/download/v1.5.2/seata-server-1.5.2.zip

解压之后,进入 seata-server-1.5.2/seata/bin 目录,执行:

$ sh seata-server.sh -p 8091 -h 127.0.0.1 -m file
apm-skywalking not enabled
seata-server is starting, you can check the /d/projects/seata/seata-server-1.5.2/seata/logs/start.out

查看日志:

$ tail -300 /d/projects/seata/seata-server-1.5.2/seata/logs/start.out
D:\SDK\Java\jdk1.8.0_202/bin/java  -server -Dloader.path=.lib -Xmx2048m -Xms2048m -Xmn1024m -Xss512k -XX:SurvivorRatio=10 -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -XX:MaxDirectMemorySize=1024m -XX:-OmitStackTraceInFastThrow -XX:-UseAdaptiveSizePolicy -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/d/projects/seata/seata-server-1.5.2/seata/logs/java_heapdump.hprof -XX:+DisableExplicitGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFractinotallow=75 -Xloggc:/d/projects/seata/seata-server-1.5.2/seata/logs/seata_gc.log -verbose:gc -XX:+PrintGCDetails  -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M -Dio.netty.leakDetectinotallow=advanced -Dapp.name=seata-server -Dapp.pid=452 -Dapp.home=/d/projects/seata/seata-server-1.5.2/seata -Dbasedir=/d/projects/seata/seata-server-1.5.2/seata -Dspring.config.locatinotallow=/d/projects/seata/seata-server-1.5.2/seata/conf/application.yml -Dlogging.cnotallow=/d/projects/seata/seata-server-1.5.2/seata/conf/logback-spring.xml -jar /d/projects/seata/seata-server-1.5.2/seata/target/seata-server.jar -p 8091 -h 127.0.0.1 -m file
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒[▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒[ ▒▒▒▒▒▒▒▒▒▒▒[ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒[ ▒▒▒▒▒▒▒▒▒▒▒[
▒▒▒▒▒X▒T▒T▒T▒T▒a▒▒▒▒▒X▒T▒T▒T▒T▒a▒▒▒▒▒X▒T▒T▒▒▒▒▒[▒^▒T▒T▒▒▒▒▒X▒T▒T▒a▒▒▒▒▒X▒T▒T▒▒▒▒▒[
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒[▒▒▒▒▒▒▒▒▒▒▒[  ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒U   ▒▒▒▒▒U   ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒U
▒^▒T▒T▒T▒T▒▒▒▒▒U▒▒▒▒▒X▒T▒T▒a  ▒▒▒▒▒X▒T▒T▒▒▒▒▒U   ▒▒▒▒▒U   ▒▒▒▒▒X▒T▒T▒▒▒▒▒U
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒U▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒[▒▒▒▒▒U  ▒▒▒▒▒U   ▒▒▒▒▒U   ▒▒▒▒▒U  ▒▒▒▒▒U
▒^▒T▒T▒T▒T▒T▒T▒a▒^▒T▒T▒T▒T▒T▒T▒a▒^▒T▒a  ▒^▒T▒a   ▒^▒T▒a   ▒^▒T▒a  ▒^▒T▒a

:
16:33:03.227  INFO --- [                     main] io.seata.server.ServerApplication        : Starting ServerApplication v1.5.2 using Java 1.8.0_202 on LAPTOP-CJVNN4P6 with PID 12296 (D:\projects\seata\seata-server-1.5.2\seata\target\seata-server.jar started by solisamicus in D:\projects\seata\seata-server-1.5.2\seata\bin)
16:33:03.232  INFO --- [                     main] io.seata.server.ServerApplication        : No active profile set, falling back to default profiles: default
16:33:04.876  INFO --- [                     main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 7091 (http)
16:33:04.885  INFO --- [                     main] o.a.coyote.http11.Http11NioProtocol      : Initializing ProtocolHandler ["http-nio-7091"]
16:33:04.886  INFO --- [                     main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
16:33:04.886  INFO --- [                     main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.55]
16:33:04.948  INFO --- [                     main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
16:33:04.948  INFO --- [                     main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1659 ms
16:33:05.441  INFO --- [                     main] o.s.b.a.w.s.WelcomePageHandlerMapping    : Adding welcome page: class path resource [static/index.html]
16:33:05.597  INFO --- [                     main] o.s.s.web.DefaultSecurityFilterChain     : Will secure Ant [pattern='/'] with []
16:33:05.597  INFO --- [                     main] o.s.s.web.DefaultSecurityFilterChain     : Will secure Ant [pattern='/**/*.css'] with []
16:33:05.597  INFO --- [                     main] o.s.s.web.DefaultSecurityFilterChain     : Will secure Ant [pattern='/**/*.js'] with []
16:33:05.597  INFO --- [                     main] o.s.s.web.DefaultSecurityFilterChain     : Will secure Ant [pattern='/**/*.html'] with []
16:33:05.597  INFO --- [                     main] o.s.s.web.DefaultSecurityFilterChain     : Will secure Ant [pattern='/**/*.map'] with []
16:33:05.597  INFO --- [                     main] o.s.s.web.DefaultSecurityFilterChain     : Will secure Ant [pattern='/**/*.svg'] with []
16:33:05.598  INFO --- [                     main] o.s.s.web.DefaultSecurityFilterChain     : Will secure Ant [pattern='/**/*.png'] with []
16:33:05.598  INFO --- [                     main] o.s.s.web.DefaultSecurityFilterChain     : Will secure Ant [pattern='/**/*.ico'] with []
16:33:05.598  INFO --- [                     main] o.s.s.web.DefaultSecurityFilterChain     : Will secure Ant [pattern='/console-fe/public/**'] with []
16:33:05.598  INFO --- [                     main] o.s.s.web.DefaultSecurityFilterChain     : Will secure Ant [pattern='/api/v1/auth/login'] with []
16:33:05.615  INFO --- [                     main] o.s.s.web.DefaultSecurityFilterChain     : Will secure any request with [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@594d9f07, org.springframework.security.web.context.SecurityContextPersistenceFilter@118dcbbd, org.springframework.security.web.header.HeaderWriterFilter@2e26173, org.springframework.security.web.authentication.logout.LogoutFilter@2ecf5915, io.seata.console.filter.JwtAuthenticationTokenFilter@5befbac1, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@350ec690, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@34a2d6e0, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@5e9f73b, org.springframework.security.web.session.SessionManagementFilter@203d1d93, org.springframework.security.web.access.ExceptionTranslationFilter@2f74900b, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@3c6c4689]
16:33:05.639  INFO --- [                     main] o.a.coyote.http11.Http11NioProtocol      : Starting ProtocolHandler ["http-nio-7091"]
16:33:05.660  INFO --- [                     main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 7091 (http) with context path ''
16:33:05.670  INFO --- [                     main] io.seata.server.ServerApplication        : Started ServerApplication in 3.177 seconds (JVM running for 3.761)
16:33:07.372  INFO --- [                     main] i.s.core.rpc.netty.NettyServerBootstrap  : Server started, service listen port: 8091
16:33:07.384  INFO --- [                     main] io.seata.server.ServerRunner             : seata server started in 1713 millSeconds

(4)运行示例

依次启动:

  • DubboStockServiceStarter:初始化库存数据(添加库存记录)。
  • DubboAccountServiceStarter:初始化账户数据(添加用户账户记录)。
  • DubboOrderServiceStarter:启动订单服务(等待下单操作)。
  • DubboBusinessTester:执行测试场景(下单操作,触发分布式事务)。

启动账户服务。

初始化账户表数据:

  • 删除用户 U100001 的账户记录(若存在)。
  • 插入一条新记录,给用户 U100001 分配初始余额 999。
accountJdbcTemplate.update("delete from account_tbl where user_id = 'U100001'");
accountJdbcTemplate.update("insert into account_tbl(user_id, money) values ('U100001', 999)");

mysql> SELECT * FROM `account`.`account_tbl`;
+----+---------+-------+
| id | user_id | money |
+----+---------+-------+
|  1 | U100001 |   999 |
+----+---------+-------+
1 row in set (0.00 sec)

启动订单服务。

订单服务启动后,等待买家下单。

ClassPathXmlApplicationContext orderContext = new ClassPathXmlApplicationContext(
    new String[] {"spring/dubbo-order-service.xml"});
orderContext.getBean("service");
new ApplicationKeeper(orderContext).keep();

mysql> SELECT * FROM `order`.`order_tbl`;
Empty set (0.00 sec)

启动库存服务。

初始化库存表数据:

a.删除商品 C00321 的库存记录(若存在)。

b.为商品 C00321 添加库存 100。

stockJdbcTemplate.update("delete from stock_tbl where commodity_code = 'C00321'");
stockJdbcTemplate.update("insert into stock_tbl(commodity_code, count) values ('C00321', 100)");

mysql> SELECT * FROM `stock`.`stock_tbl`;
+----+----------------+-------+
| id | commodity_code | count |
+----+----------------+-------+
|  1 | C00321         |   100 |
+----+----------------+-------+
1 row in set (0.00 sec)

测试整个电商平台。

模拟业务场景:用户 U100001 购买商品 C00321,数量为 2。

final BusinessService business = (BusinessService)context.getBean("business");
business.purchase("U100001", "C00321", 2);

正常事务

选择注释 dubbo/src/main/java/io/seata/samples/dubbo/service/impl/BusinessServiceImpl.java

@Override
@GlobalTransactional(timeoutMills = 300000, name = "dubbo-demo-tx")
public void purchase(String userId, String commodityCode, int orderCount) {
    LOGGER.info("purchase begin ... xid: " + RootContext.getXID());
    stockService.deduct(commodityCode, orderCount);
    // just test batch update
    //stockService.batchDeduct(commodityCode, orderCount);
    orderService.create(userId, commodityCode, orderCount);
    //if (random.nextBoolean()) {
    //    throw new RuntimeException("random exception mock!");
    //}
}

mysql> USE account;
mysql> SELECT * FROM `account`.`account_tbl`;
+----+---------+-------+
| id | user_id | money |
+----+---------+-------+
|  1 | U100001 |   599 |
+----+---------+-------+
1 row in set (0.00 sec)
mysql> USE `order`;
mysql> SELECT * FROM `order`.`order_tbl`;
+----+---------+----------------+-------+-------+
| id | user_id | commodity_code | count | money |
+----+---------+----------------+-------+-------+
|  1 | U100001 | C00321         |     2 |   400 |
+----+---------+----------------+-------+-------+
1 row in set (0.00 sec)
mysql> USE `stock`;
mysql> SELECT * FROM `stock`.`stock_tbl`;
+----+----------------+-------+
| id | commodity_code | count |
+----+----------------+-------+
|  1 | C00321         |    98 |
+----+----------------+-------+
1 row in set (0.00 sec)

异常事务

选择保留 dubbo/src/main/java/io/seata/samples/dubbo/service/impl/BusinessServiceImpl.java

@Override
@GlobalTransactional(timeoutMills = 300000, name = "dubbo-demo-tx")
public void purchase(String userId, String commodityCode, int orderCount) {
    LOGGER.info("purchase begin ... xid: " + RootContext.getXID());
    stockService.deduct(commodityCode, orderCount);
    // just test batch update
    //stockService.batchDeduct(commodityCode, orderCount);
    orderService.create(userId, commodityCode, orderCount);
    if (random.nextBoolean()) {
        throw new RuntimeException("random exception mock!");
    }
}

图片图片

责任编辑:姜华 来源: dubbogo示土区
相关推荐

2022-02-24 07:56:42

开发Viteesbuild

2017-07-31 15:14:07

Glide项目缩放

2020-11-27 09:40:53

Rollup前端代码

2021-05-20 06:57:16

RabbitMQ开源消息

2023-04-20 08:00:00

ES搜索引擎MySQL

2021-06-16 08:28:25

unary 方法函数技术

2021-05-12 06:18:19

KubeBuilderOperatork8s

2022-03-10 08:31:51

REST接口规范设计Restful架构

2021-05-17 05:51:31

KubeBuilderOperator测试

2021-05-18 05:40:27

kubebuilderwebhook进阶

2021-07-28 10:02:54

建造者模式代码

2021-06-30 00:20:12

Hangfire.NET平台

2022-02-21 09:44:45

Git开源分布式

2021-07-14 08:24:23

TCPIP 通信协议

2021-05-16 10:52:58

kubebuilderstatus event

2022-04-08 08:32:40

mobx状态管理库redux

2023-05-12 08:19:12

Netty程序框架

2021-08-11 07:02:21

npm包管理器工具

2021-11-24 08:51:32

Node.js监听函数

2020-11-27 08:02:41

Promise
点赞
收藏

51CTO技术栈公众号