当前位置:网站首页>Microservices Seata distributed transactions
Microservices Seata distributed transactions
2022-07-03 15:35:00 【crysw】
1. Distributed transaction problems
recommend : Distributed transactions ( The illustration + Seconds understand + In the history of the most complete )
In the case of single machine and single library , There are local transactions to ensure data consistency ; But in a distributed architecture , The original single application is split into multiple microservice applications with independent data sources , The completion of a business scenario may require different micro service modules and different libraries . here The data consistency within each microservice module is guaranteed by local transactions , But there is no way to coordinate and guarantee the overall data consistency .
such as : The business logic for users to purchase goods , Need by 3 Micro services provide support .
- Warehousing services : Deduct the storage quantity from the given goods .
- Order service : Create an order based on purchase requirements .
- Account service : Deduct the balance from the user's account .
Architecture diagram :
In a word : A business operation needs to be called remotely across multiple data sources or systems , There will be distributed transaction problems .
2. Seata brief introduction
2.1 What is it?
Seata Is an open source distributed transaction solution , We are committed to providing high-performance and easy-to-use distributed transaction services under the microservice architecture .
→Seata Official website
2.2 Can do
A typical distributed transaction process : Global transaction ID + Three component model
- Transaction ID, Abbreviation XID. It is the globally unique transaction in the distribution ID.
- Transaction Coordinator , Abbreviation TC. Transaction coordinator , Maintain the running state of the global transaction , Responsible for coordinating and driving global transaction commit or rollback .
- Transaction Manager, Abbreviation TM. Transaction manager , Control the boundaries of global transactions , Responsible for opening a global transaction , And finally initiate the resolution of global commit or rollback .
- Resource Manager, Abbreviation RM. Explorer , Control branch transactions , Responsible for branch registration , Status report , And receive instructions from the transaction coordinator , Drive branch ( Local ) Transaction commit and rollback .
Model diagram :
Treatment process :
- TM towards TC Request to open a global transaction , The global transaction is created successfully and generates a globally unique XID;
- XID Propagate in the context of the microservice call link ;
- RM towards TC Register branch transactions , Be included in the XID Jurisdiction corresponding to global transaction ;
- TM towards TC Launch against XID The global commit or rollback resolution for ;
- TC Dispatch XID All branch transactions under the jurisdiction complete the commit or rollback request .
2.3 download
→Seata Download from the official website
→Seata stay GitHub Release notes for
→GitHub download Seata
2.4 How to use it?
We used to use Spring Notes provided @Transactionnal Turn on .
Seata It also provides us with the annotation to start the global transaction @GlobalTransactional.
We only need to use one on the business method @GlobalTransactional annotation .
3. Seata-Server install
The version I downloaded is seata-server-0.9.0 , You can choose an updated version .
take seata-server-0.9.0.zip Unzip to the specified directory and modify conf In the catalog file.conf
The configuration file .( Backup first file.conf Modify the file later )
Modify the content : Custom transaction group name + The transaction log storage mode is db + Database connection information .
stay service Modify in the module Custom transaction group name
service {
#vgroup->rgroup
# Custom transaction group name fsp_tx_group, Default default
vgroup_mapping.my_test_tx_group = "fsp_tx_group"
#only support single node
default.grouplist = "127.0.0.1:8091"
#degrade current not support
enableDegrade = false
#disable
disable = false
#unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
max.commit.retry.timeout = "-1"
max.rollback.retry.timeout = "-1"
}
store Module modification The transaction log storage mode is db
, And modify the database connection information .
store {
## store mode: file、db
# Transaction log storage mode
mode = "db"
## file store
file {
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
max-branch-session-size = 16384
# globe session size , if exceeded throws exceptions
max-global-session-size = 512
# file buffer size , if exceeded allocate new buffer
file-write-buffer-cache-size = 16384
# when recover batch read size
session.reload.read_size = 100
# async, sync
flush-disk-mode = async
}
## database store Database connection information
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
datasource = "dbcp"
## mysql/oracle/h2/oceanbase etc.
db-type = "mysql"
driver-class-name = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://127.0.0.1:3306/seata"
user = "root"
password = "root"
min-conn = 1
max-conn = 3
global.table = "global_table"
branch.table = "branch_table"
lock-table = "lock_table"
query-limit = 100
}
}
stay mysql Created in seata library , Then create global transaction tables respectively global_table, Branch transaction table branch_table And transaction lock table lock_table. These three tables don't need to be designed by yourself , In download seata In bag conf There is a db_store.sql
file , To mysql Just execute in the client .
-- the table to store GlobalSession data
drop table if exists `global_table`;
create table `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_gmt_modified_status` (`gmt_modified`, `status`),
key `idx_transaction_id` (`transaction_id`)
);
-- the table to store BranchSession data
drop table if exists `branch_table`;
create table `branch_table` (
`branch_id` bigint not null,
`xid` varchar(128) not null,
`transaction_id` bigint ,
`resource_group_id` varchar(32),
`resource_id` varchar(256) ,
`lock_key` varchar(128) ,
`branch_type` varchar(8) ,
`status` tinyint,
`client_id` varchar(64),
`application_data` varchar(2000),
`gmt_create` datetime,
`gmt_modified` datetime,
primary key (`branch_id`),
key `idx_xid` (`xid`)
);
-- the table to store lock data
drop table if exists `lock_table`;
create table `lock_table` (
`row_key` varchar(128) not null,
`xid` varchar(96),
`transaction_id` long ,
`branch_id` long,
`resource_id` varchar(256) ,
`table_name` varchar(32) ,
`pk` varchar(36) ,
`gmt_create` datetime ,
`gmt_modified` datetime,
primary key(`row_key`)
);
modify conf In the catalog registry.conf The configuration file ( Backup first ), Modify the registry configuration . Registry support nacos 、eureka、redis、zk、consuld etc. .
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
# The registry is amended to read nacos
type = "nacos"
nacos {
serverAddr = "localhost:8848"
namespace = ""
cluster = "default"
}
............ The complete configuration is omitted here
}
Start... First nacos, Restart seata-server, Otherwise the newspaper no available server to connect.
/nacos/bin/startup.cmd
/seata-0.9.0/bin/seata-server.bat
4. Order / stock / Account business database preparation
Distributed transaction description , We need to create three services , Order service , Inventory service , Account service .
When a user places an order , The order service will create an order , Then the inventory service is called remotely to deduct the inventory of the ordered goods , Then deduct the account balance by calling the account service remotely , Finally, modify the order status to completed in the order service . This operation spans three databases , There are two remote calls , There's obviously a problem with distributed transactions .
scene : Place the order -> Deducting the inventory -> Deduct account balance
Three databases : Order database (seata_order), Inventory database (seata_storage), Account database (seata_account).
Business database SQL
CREATE DATABASE seata_order;
CREATE DATABASE seata_storage;
CREATE DATABASE seata_account;
stay seata_order Create an order table in the Library t_order.
CREATE TABLE t_order (
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`user_id` BIGINT(11) DEFAULT NULL COMMENT ' user id',
`product_id` BIGINT(11) DEFAULT NULL COMMENT ' product id',
`count` INT(11) DEFAULT NULL COMMENT ' Number ',
`money` DECIMAL(11,0) DEFAULT NULL COMMENT ' amount of money ',
`status` INT(1) DEFAULT NULL COMMENT ' The order status :0: In the create ;1: It's over '
) ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
stay seata_storage Create an inventory table in the Library t_storage
CREATE TABLE t_storage (
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`product_id` BIGINT(11) DEFAULT NULL COMMENT ' product id',
`total` INT(11) DEFAULT NULL COMMENT ' Total inventory ',
`used` INT(11) DEFAULT NULL COMMENT ' Used stock ',
`residue` INT(11) DEFAULT NULL COMMENT ' Surplus stock '
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- Initial stock
INSERT INTO seata_storage.t_storage(`id`, `product_id`, `total`, `used`, `residue`)
VALUES ('1', '1', '100', '0', '100');
stay seata_account Create an account table in the Library t_account
CREATE TABLE t_account (
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'id',
`user_id` BIGINT(11) DEFAULT NULL COMMENT ' user id',
`total` DECIMAL(10,0) DEFAULT NULL COMMENT ' Total amount ',
`used` DECIMAL(10,0) DEFAULT NULL COMMENT ' Used balance ',
`residue` DECIMAL(10,0) DEFAULT '0' COMMENT ' The remaining amount available '
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- Initialize account information
INSERT INTO seata_account.t_account(`id`, `user_id`, `total`, `used`, `residue`) VALUES ('1', '1', '1000', '0', '1000');
In the above three business databases seata_order, seata_storage, seata_account Create their own transaction rollback log tables . perform seata In bag conf In the catalog db_undo_log.sql
file .
-- the table to store seata xid data
-- 0.7.0+ add context
-- you must to init this sql for you business databese. the seata server not need it.
-- This script must be initialized in your current business database , be used for AT Pattern XID Record . And server End independent ( notes : Business database )
-- Notice here 0.3.0+ Add unique index ux_undo_log
drop table `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;
After the database table of each module is prepared , Take a look at the structure .
5. Order / stock / Account / Business microservice preparation
Business needs , Place the order -> Reduce inventory -> Deduct the balance -> Change order status
5.1 New order module
Create order module seata-order-service-2001
, pom Dependency file .
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>atguigu-cloud-2020</artifactId>
<groupId>com.atguigu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>seata-order-service-2001</artifactId>
<dependencies>
<!-- Custom public dependencies -->
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<artifactId>seata-all</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>0.9.0</version>
</dependency>
<!--feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--web-actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--mysql-druid-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
yml To configure
server:
port: 2001
spring:
application:
name: seata-order-service
cloud:
alibaba:
seata:
# The custom transaction group name needs to match seata-server Corresponding to in
tx-service-group: fsp_tx_group
nacos:
discovery:
server-addr: localhost:8848
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_order?serverTimezone=Asia/Shanghai
username: root
password: root
feign:
hystrix:
enabled: false
logging:
level:
io:
seata: info
# Custom properties
mybatis:
mapperLocations: classpath:mapper/*.xml
take file.conf and registry.conf Copy to the project classpath .
Then make a change file.conf Transaction group name and seata-server The configuration in is consistent .
service {
#vgroup->rgroup
# Modify custom transaction group name , It's hard for me to change , Use the default default. Mapping to seata-server Of fsp_tx_group
vgroup_mapping.fsp_tx_group = "default"
#only support single node
default.grouplist = "127.0.0.1:8091"
#degrade current not support
enableDegrade = false
#disable
disable = false
#unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
max.commit.retry.timeout = "-1"
max.rollback.retry.timeout = "-1"
}
To write Order Order entity class
/** * Class description : Order */
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class Order {
private Long id;
private Long userId;
private Long productId;
private Integer count;
private BigDecimal money;
/** * The order status :0: In the create ;1: It's over */
private Integer status;
}
establish OrderDao Interface , Use @Mapper annotations .
@Mapper
public interface OrderDao {
/** * Create order */
void create(Order order);
/** * Modify order status */
void update(@Param("userId") Long userId, @Param("status") Integer status);
}
establish OrderDao Interface mapping file , OrderMapper.xml.
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.atguigu.springcloud.dao.OrderDao">
<resultMap id="BaseResultMap" type="com.atguigu.springcloud.domain.Order">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="user_id" property="userId" jdbcType="BIGINT"/>
<result column="product_id" property="productId" jdbcType="BIGINT"/>
<result column="count" property="count" jdbcType="INTEGER"/>
<result column="money" property="money" jdbcType="DECIMAL"/>
<result column="status" property="status" jdbcType="INTEGER"/>
</resultMap>
<insert id="create" parameterType="com.atguigu.springcloud.domain.Order">
INSERT INTO `t_order` (`id`, `user_id`, `product_id`, `count`, `money`, `status`)
VALUES (NULL, #{userId}, #{productId}, #{count}, #{money}, 0);
</insert>
<update id="update">
UPDATE `t_order`
SET status = 1
WHERE user_id = #{userId} AND status = #{status};
</update>
</mapper>
establish Service And Realization
public interface OrderService {
/** * Create order */
void create(Order order);
}
/** * Class description : Remotely call inventory service to deduct inventory */
@FeignClient(value = "seata-storage-service")
public interface StorageService {
@PostMapping(value = "/storage/decrease")
CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
}
/** * Class description : Remote call account service deduction balance */
@FeignClient(value = "seata-account-service")
public interface AccountService {
@PostMapping(value = "/account/decrease")
CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}
/** * Class description : Order service business logic class */
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Resource
private OrderDao orderDao;
@Resource
private StorageService storageService;
@Resource
private AccountService accountService;
/** * Create order -> Call inventory service to deduct inventory -> Call the account service to deduct the account balance -> Modify order status * In short : * Place the order -> Reduce inventory -> Less balance -> Change state */
@Override
// @GlobalTransactional(name = "fsp-create-order", rollbackFor = Exception.class)
public void create(Order order) {
log.info("-------> Order start ");
// This application creates orders
orderDao.create(order);
// Call inventory service remotely to deduct inventory
log.info("------->order-service The deduction of inventory starts ");
storageService.decrease(order.getProductId(), order.getCount());
log.info("------->order-service After deducting the inventory from the inventory, the end ");
// Remote call account service deduction balance
log.info("------->order-service The deduction balance starts ");
accountService.decrease(order.getUserId(), order.getMoney());
log.info("------->order-service End of deduction balance in ");
// Change order status to completed
log.info("------->order-service Start by modifying the order status in ");
orderDao.update(order.getProductId(), 0);
log.info("------->order-service End of modifying order status in ");
log.info("-------> End of order ");
}
}
To write controller
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
/** * Create order */
@GetMapping("/order/create")
public CommonResult create(Order order) {
orderService.create(order);
return new CommonResult(200, " Order created successfully !");
}
}
MybatisConfig Configure scan mapper Interface package .
@Configuration
@MapperScan(value = "com.atguigu.springcloud.dao")
public class MyBatisConfig {
}
Configure proxy data sources
/** * Class description : Use Seata Proxy data sources * io.seata.rm.datasource.DataSourceProxy */
@Configuration
public class DataSourceProxyConfig {
@Value("${mybatis.mapper-locations}")
private String mapperLocations;
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource() {
return new DruidDataSource();
}
@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
return sqlSessionFactoryBean.getObject();
}
}
Create the main startup class
// Exclude automatic configuration of data sources , Use a custom data source
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableFeignClients
public class SeataOrderMainApp2001 {
public static void main(String[] args) {
SpringApplication.run(SeataOrderMainApp2001.class);
}
}
5.2 Create a new inventory module
Follow the same steps above to create the inventory micro service module seata-storage-service-2002
Take out the different ones separately .
Inventory entity class Storage
@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
public class Storage {
private Long id;
/** * product id */
private Long productId;
/** * Total inventory */
private Integer total;
/** * Used stock */
private Integer used;
/** * Surplus stock */
private Integer residue;
}
Deduction inventory logic . StorageController
@RestController
public class StorageController {
@Autowired
private StorageService storageService;
/** * Deducting the inventory */
@RequestMapping("/storage/decrease")
public CommonResult decrease(Long productId, Integer count) {
storageService.decrease(productId, count);
return new CommonResult(200, " Inventory reduction succeeded !");
}
}
StorageServiceImpl
@Service
@Slf4j
public class StorageServiceImpl implements StorageService {
@Resource
private StorageDao storageDao;
/** * Deducting the inventory */
@Override
public void decrease(Long productId, Integer count) {
log.info("------->storage-service The deduction of inventory starts ");
storageDao.decrease(productId, count);
log.info("------->storage-service After deducting the inventory from the inventory, the end ");
}
}
StorageDao
@Mapper
public interface StorageDao {
/** * Deducting the inventory */
void decrease(@Param("productId") Long productId, @Param("count") Integer count);
}
StorageMapper.xml The mapping file
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.atguigu.springcloud.dao.StorageDao">
<resultMap id="BaseResultMap" type="com.atguigu.springcloud.domain.Storage">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="product_id" property="productId" jdbcType="BIGINT"/>
<result column="total" property="total" jdbcType="INTEGER"/>
<result column="used" property="used" jdbcType="INTEGER"/>
<result column="residue" property="residue" jdbcType="INTEGER"/>
</resultMap>
<update id="decrease">
UPDATE t_storage
SET used = used + #{count},
residue = residue - #{count}
WHERE product_id = #{productId}
</update>
</mapper>
5.3 Create a new account module
Create the account micro service module in the same steps seata-account-service-2003
The logic of deducting the account balance is taken out separately . AccountController
@RestController
public class AccountController {
@Resource
private AccountService accountService;
/** * Deduct account balance */
@RequestMapping("/account/decrease")
public CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money) {
accountService.decrease(userId, money);
return new CommonResult(200, " Deduct account balance successfully !");
}
}
AccountServiceImpl
@Service
@Slf4j
public class AccountServiceImpl implements AccountService {
@Resource
private AccountDao accountDao;
/** * Deduct account balance */
@Override
public void decrease(Long userId, BigDecimal money) {
log.info("------->account-service Start by deducting the account balance ");
accountDao.decrease(userId, money);
log.info("------->account-service The account balance is deducted from the account to end ");
}
}
AccountDao
@Mapper
public interface AccountDao {
/** * Deduct account balance */
void decrease(@Param("userId") Long userId, @Param("money") BigDecimal money);
}
AccountMapper.xml The mapping file
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.atguigu.springcloud.dao.AccountDao">
<resultMap id="BaseResultMap" type="com.atguigu.springcloud.domain.Account">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="user_id" property="userId" jdbcType="BIGINT"/>
<result column="total" property="total" jdbcType="DECIMAL"/>
<result column="used" property="used" jdbcType="DECIMAL"/>
<result column="residue" property="residue" jdbcType="DECIMAL"/>
</resultMap>
<update id="decrease">
UPDATE t_account
SET
residue = residue - #{money},used = used + #{money}
WHERE
user_id = #{userId};
</update>
</mapper>
6. Distributed transaction testing
6.1 Data initialization
First, query the initial data state of the database .
SELECT * FROM seata_order.t_order; There was no initial order
SELECT * FROM seata_storage.t_storage; stock 100, Use 0, The remaining 100.
SELECT * FROM seata_account.t_account; balance 1000, Use 0, The remaining 1000.
6.2 Forward test
First , start-up Nacos, start-up Seata-server, Start the service module .
Test a normal order . visit http://localhost:2001/order/create?userId=1&productId=1&count=10&money=10
. Order purchase 10 A product , common 10 element .
Without exception , The data results of each module are normal . An order data has been added to the order library and the order status has been completed , There is still left 90 Inventory , The balance of the account remains 990 element .
6.3 No distributed transaction reverse test
Order service#create Method without adding @GlobalTransactional
, There is no global transaction , Then deduct the balance in the account module service#decrease Method to add timeout simulation .
@Override
public void decrease(Long userId, BigDecimal money) {
log.info("------->account-service Start by deducting the account balance ");
accountDao.decrease(userId, money);
// Simulation timeout , 30s Far exceeds the default timeout for remote calls .
try {
TimeUnit.SECONDS.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("------->account-service The account balance is deducted from the account to end ");
}
Revisit http://localhost:2001/order/create?userId=1&productId=1&count=10&money=10
Because the deduction of account balance is timeout , Cause the deal to fail .
Let's look at db What are the data results of each module in . We can see from the following data , A new order has been added to the order Library , But the status is incomplete ( Because the deduction of balance failed ). But inventory and accounts have been successfully deducted , There is a problem of inconsistent data . It is necessary to coordinate the transaction atomicity of each branch module through distributed transactions .
6.3 Start distributed transaction reverse test
At the end of the order service#create Add a comment to the method @GlobalTransactional
, To start the global transaction .
@Override
@GlobalTransactional(name = "fsp-create-order", rollbackFor = Exception.class)
public void create(Order order) {
log.info("-------> Order start ");
// This application creates orders
orderDao.create(order);
// Call inventory service remotely to deduct inventory
log.info("------->order-service The deduction of inventory starts ");
storageService.decrease(order.getProductId(), order.getCount());
log.info("------->order-service After deducting the inventory from the inventory, the end ");
// Remote call account service deduction balance
log.info("------->order-service The deduction balance starts ");
accountService.decrease(order.getUserId(), order.getMoney());
log.info("------->order-service End of deduction balance in ");
// Change order status to completed
log.info("------->order-service Start by modifying the order status in ");
orderDao.update(order.getProductId(), 0);
log.info("------->order-service End of modifying order status in ");
log.info("-------> End of order ");
}
After restart, the order micro service application , Revisit http://localhost:2001//order/create?userId=1&productId=1&count=10&money=10
, The transaction fails because of timeout .
But check the database table data , It's the result of last time , No new orders have been generated , There is no deduction for inventory and account balance . Because of the coordination of global transactions , When an exception occurs , The originator of the business TM( Order module ) To the transaction coordinator TC Send the message of transaction rollback , A business coordinator TC Then send rollback notification to each branch transaction , TM Roll back local transactions .
below , We go through debug Breakpoint debugging to follow up the transaction rollback process .
Breakpoint location :
Now? , Let's trigger a transaction , Jump to the breakpoint .
then , Let's check the data of the library table and their respective States . According to the position of the breakpoint , At this point, a new order should be created ( Status not complete 0), stock (70) And account balance (970) Have been deducted 10. But at this time, the global transaction has not been committed .
Because the global transaction has not been committed , Let's see seata-server Transaction related table data .
SELECT * FROM seata.global_table; In the global transaction table , A global transaction is generated XID, The originator of the business seata-order-service
, Transaction groups are custom configured fsp_tx_group
SELECT * FROM seata.branch_table; In the branch transaction table , Global transaction XID Here are three branch transactions , Transactions involve resources of three modules . branch_type=AT Express seata The transaction mode of , The back can speak .
SELECT * FROM seata.lock_table; Transaction lock , You can see three business tables (t_order, t_storage, t_account) It's all locked up .
Let's take a look at the business database undo_log Table data that controls transactions .
SELECT * FROM seata_order.undo_log;
SELECT * FROM seata_storage.undo_log; overall situation XID 192.168.65.1:8091:2095810330 the .
SELECT * FROM seata_account.undo_log;
Of the above three business databases undo_log It's all recorded rollback_info data , It says db Update the previous snapshot and the updated data , It is convenient to initiate commit or rollback of global transactions . Once the global transaction initiates a commit or rollback notification , Each branch transaction will be based on rollback_info Commit or rollback local transactions with snapshot information of . After the global transaction is completed , Delete undo_log Log data .
// Snapshot information of account table
{
"@class": "io.seata.rm.datasource.undo.BranchUndoLog",
"xid": "192.168.65.1:8091:2095813945", // Global transaction XID
"branchId": 2095813953, // Branch business ID
"sqlUndoLogs": [
"java.util.ArrayList",
[
{
"@class": "io.seata.rm.datasource.undo.SQLUndoLog",
"sqlType": "UPDATE",
"tableName": "t_account",
"beforeImage": {
// Pre snapshot beforeImage
"@class": "io.seata.rm.datasource.sql.struct.TableRecords",
"tableName": "t_account", // Account form
"rows": [
"java.util.ArrayList",
[
{
"@class": "io.seata.rm.datasource.sql.struct.Row",
"fields": [
"java.util.ArrayList",
[
{
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "id",
"keyType": "PrimaryKey",
"type": -5,
"value": [
"java.lang.Long",
1
]
},
{
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "residue", // Balance field
"keyType": "NULL",
"type": 3,
"value": [
"java.math.BigDecimal",
980 // before value 980
]
},
{
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "used",
"keyType": "NULL",
"type": 3,
"value": [
"java.math.BigDecimal",
20
]
}
]
]
}
]
]
},
"afterImage": {
// Post snapshot afterImage
"@class": "io.seata.rm.datasource.sql.struct.TableRecords",
"tableName": "t_account",
"rows": [
"java.util.ArrayList",
[
{
"@class": "io.seata.rm.datasource.sql.struct.Row",
"fields": [
"java.util.ArrayList",
[
{
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "id",
"keyType": "PrimaryKey",
"type": -5,
"value": [
"java.lang.Long",
1
]
},
{
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "residue", // Balance field
"keyType": "NULL",
"type": 3,
"value": [
"java.math.BigDecimal",
970 // after value 970
]
},
{
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "used",
"keyType": "NULL",
"type": 3,
"value": [
"java.math.BigDecimal",
30
]
}
]
]
}
]
]
}
}
]
]
}
Let's now let go of the breakpoint , Let the program go through the process . Found that the transaction failed , The data has been rolled back , seata-server Global transaction table (global_table), Branch transaction table (branch_table), Table of transaction lock (lock_table), And for each business database undo_log The log data is deleted .
7. Add
7.1 AT Distributed transaction execution process
Look again TC/TM/RM/ Three components :
- TM Open distributed transaction , TM towards TC Register global transaction records ;
- By business scenario , Arrange the database , Services and other internal resources , RM towards TC Report resource readiness ;
- TM End distributed transaction , The end of the first phase of the business , TM notice TC Submit / Roll back distributed transactions ;
- TC Summarize transaction information , Decide whether a distributed transaction is committed or rolled back ;
- TC Inform all RM Submit / Roll back resources , The second phase of business is over .
7.2 AT Model details
AT Mode is how to achieve non-invasive business , There are two premises :
- Based on support for local ACID Relational database of transactions ;
- Java application , adopt JDBC Access database .
The overall mechanism of two-stage submission :
- A stage : Business data and rollback logging are committed in the same local transaction , Release local locks and connection resources .
- Two stages : (1) Submit asynchronized , It's done very quickly ; (2) Roll back , Reverse compensation through a phase of rollback log .
One stage submission
,Seata Will intercept “ Business SQL”, See the flow chart ( One stage submission )
- analysis SQL semantics , find “ Business SQL” Business data to update , Before the business data is updated , Save it as “before image”,
- perform
Business SQL
Update business data , After the business data is updated , Save as “after image”, Finally, a row lock is generated (lock surface ).
All the above operations are completed in a database transaction , This ensures the atomicity of a phase operation .Two stage submission
, because “ Business SQL” In one stage it has been submitted to the database , therefore Seata The framework only needs to delete the snapshot data and row lock saved in one stage , Complete the data cleaning . See the picture below ( Two stage submission )Two phase rollback
,Seata It is necessary to roll back what has been executed in one stage “ Business SQL”, Restore business data . The rollback method is to use “before image” Restore business data ; But before restoring, first check the dirty write , contrast “ Database current business data ” and “after image”, If the two data are identical, there is no dirty writing , Business data can be restored , If not, it means dirty writing , In case of dirty writing, it needs to be transferred to manual processing . See the picture below ( Two phase rollback )
The underlying the aop Realization :
Personal blog
Welcome to personal blog : https://www.crystalblog.xyz/
Alternate address : https://wang-qz.gitee.io/crystal-blog/
边栏推荐
- CString在多线程中的问题
- App移动端测试【4】apk的操纵
- Halcon and WinForm study section 2
- QT common sentence notes
- Popular understanding of decision tree ID3
- Jvm-02-class loading subsystem
- 如何使用 @NotNull等注解校验 并全局异常处理
- Go语言自学系列 | golang中的if else if语句
- Concurrency-01-create thread, sleep, yield, wait, join, interrupt, thread state, synchronized, park, reentrantlock
- 视觉上位系统设计开发(halcon-winform)-3.图像控件
猜你喜欢
Kubernetes advanced training camp pod Foundation
Chapter 04_ Logical architecture
[cloud native training camp] module 7 kubernetes control plane component: scheduler and controller
Popular understanding of linear regression (II)
Reading notes of "micro service design" (Part 2)
App移动端测试【5】文件的写入、读取
Microservice API gateway zuul
Detailed pointer advanced 2
Jvm-06-execution engine
软件逆向破解入门系列(1)—xdbg32/64的常见配置及功能窗口
随机推荐
Atlas atlas torque gun USB communication tutorial based on mtcom
App mobile terminal test [5] file writing and reading
C语言刷题~Leetcode与牛客网简单题
Secsha system 1- login function
Summary of concurrent full knowledge points
Digital image processing -- popular Canny edge detection
秒杀系统1-登录功能
Vs2017 is driven by IP debugging (dual machine debugging)
Stress test WebService with JMeter
Intelij idea efficient skills (III)
互斥对象与临界区的区别
Seckill system 3- product list and product details
使用AUR下载并安装常用程序
Backtracking method to solve batch job scheduling problem
软件逆向破解入门系列(1)—xdbg32/64的常见配置及功能窗口
Unityshader - materialcapture material capture effect (Emerald axe)
【云原生训练营】模块八 Kubernetes 生命周期管理和服务发现
VC下Unicode和ANSI互转,CStringW和std::string互转
Redis lock Optimization Practice issued by gaobingfa
Microservice - fuse hystrix