Introduction of seata
Seata is alibaba’s open source distributed transaction solution in 2019, which is committed to providing high-performance and easy-to-use distributed transaction services under the micro-service architecture. Before the open source of Seata, the internal version of Seata has been playing the role of distributed consistency middleware in Ali, helping Ali to go through the Double 11 of each year and providing strong support for each business. After years of precipitation and accumulation, Seata officially announced open source in January 2019. Seata 1.0 is now GA.
Distributed transactions in microservices
Let’s imagine a traditional monolithic application whose business consists of three modules that use a single local data source. Naturally, local transactions guarantee data consistency.
The microservices architecture has changed. The three modules mentioned above are designed for three services. Local transactions naturally ensure data consistency within each service. But what about the overall scope of business logic?
What about Seata?
We say that a distributed transaction is a global transaction consisting of a number of branch transactions, which are usually just local transactions.
Seata has three basic components:
- Transaction Coordinator (TC) : Maintains the state of global and branch transactions and drives global commit or rollback.
- Transaction manager TM: Defines the scope of a global transaction: starts a global transaction, commits or rolls back a global transaction.
- Resource Manager (RM) : Manages the resources of branch transactions being processed, talks to TCS to register branch transactions and report the status of branch transactions, and drives commit or rollback of branch transactions.
A typical lifecycle for distributed transactions managed by Seata:
- TM asks TC to start a new global transaction. TC generates xids that represent global transactions.
- Xids propagate through the invocation chain of microservices.
- RM registers the local transaction as a branch of the corresponding global transaction from XID to TC.
- TM requires TCS to commit or roll back the corresponding XID global transaction.
- TC drives all branch transactions under the corresponding global transaction of the XID to complete the branch commit or rollback.
Quick start
Use cases
Business logic for users to purchase goods. The entire business logic is supported by three microservices:
- Storage service: deduct the storage quantity for a given item.
- Order service: Creates orders based on purchasing requirements.
- Account service: deducts balances from user accounts.
Environment to prepare
Step 1: Create a database
# db_seata
DROP SCHEMA IF EXISTS db_seata;
CREATE SCHEMA db_seata;
USE db_seata;
# Account
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;
INSERT INTO account_tbl (id, user_id, money)
VALUES (1, '1001', 10000);
INSERT INTO account_tbl (id, user_id, money)
VALUES (2, '1002', 10000);
# Order
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;
# Storage
CREATE TABLE `storage_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` (`commodity_code`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
INSERT INTO storage_tbl (id, commodity_code, count)
VALUES (1, '2001', 1000);
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;
Copy the code
The SEata AT mode requires the UNdo_log table, and the other three are business tables.
Step 2: Start Seata Server
There are two storage modes on the Server (Store. mode) : File and DB (RAFT will be introduced later). You can directly start the file mode without changing it. The DB mode requires the import of three tables for storing global transaction callback information.
Note: The file mode is the single-machine mode, and the global transaction session information is read and written in the memory and the local file root.data is persisted, which provides high performance. Db mode is high availability mode, global transaction session information is shared through DB, corresponding performance is poor
Seata Server can be directly started by using bash script or Docker image. However, Docker only supports file mode and does not support registering Seata-Server with registries such as Eureka or Nacos.
Start from a script
In github.com/seata/seata… Download the Seata Server of the corresponding version, decompress it, and run the following command to start it
Start with Docker
docker run --name seata-server -p 8091:8091 seataio/seata-server:latest
Copy the code
Project introduction
The project name | address | instructions |
---|---|---|
sbm-account-service | 127.0.0.1:8081 | Account service |
sbm-order-service | 127.0.0.1:8082 | Order service |
sbm-storage-service | 127.0.0.1:8083 | Warehousing services |
sbm-business-service | 127.0.0.1:8084 | The main business |
seata-server | 172.16.2.101:8091 | seata-server |
The core code
In order not to be too long, only part of the code is given here, and the source address will be given at the end of the detailed code
Maven introduces seATA’s dependency, EATa-spring-boot-starter
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
Copy the code
Warehousing services
application.properties
spring.application.name=account-service server.port=8081 Spring. The datasource. Url = JDBC: mysql: / / 172.16.2.101:3306 / db_seata? UseSSL = false&serverTimezone = UTC spring.datasource.username=root spring.datasource.password=123456 seata.tx-service-group=my_test_tx_group Mybatis mapper - locations = classpath * : mapper / * mapper XML seata. Service. Grouplist = 172.16.2.101:8091 logging.level.io.seata=info logging.level.io.seata.samples.account.persistence.AccountMapper=debugCopy the code
StorageService
public interface StorageService {
/** * deduct the number of storage */
void deduct(String commodityCode, int count);
}
Copy the code
Order service
public interface OrderService {
/** * create order */
Order create(String userId, String commodityCode, int orderCount);
}
Copy the code
Account service
public interface AccountService {
/** * lends */ from the user account
void debit(String userId, int money);
}
Copy the code
Main business logic
Only one @GlobalTransactional annotation is required on the business method.
@GlobalTransactional
public void purchase(String userId, String commodityCode, int orderCount) {
LOGGER.info("purchase begin ... xid: " + RootContext.getXID());
storageClient.deduct(commodityCode, orderCount);
orderClient.create(userId, commodityCode, orderCount);
}
Copy the code
The transmission of XID
The cross-service transfer of global transaction IDS requires our own implementation, in this case via interceptors. The following two classes need to be added for each service.
SeataFilter
@Component
public class SeataFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {}@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
String xid = req.getHeader(RootContext.KEY_XID.toLowerCase());
boolean isBind = false;
if (StringUtils.isNotBlank(xid)) {
RootContext.bind(xid);
isBind = true;
}
try {
filterChain.doFilter(servletRequest, servletResponse);
} finally {
if(isBind) { RootContext.unbind(); }}}@Override
public void destroy(a) {}}Copy the code
SeataRestTemplateAutoConfiguration
@Configuration
public class SeataRestTemplateAutoConfiguration {
@Autowired(
required = false
)
private Collection<RestTemplate> restTemplates;
@Autowired
private SeataRestTemplateInterceptor seataRestTemplateInterceptor;
public SeataRestTemplateAutoConfiguration(a) {}@Bean
public SeataRestTemplateInterceptor seataRestTemplateInterceptor(a) {
return new SeataRestTemplateInterceptor();
}
@PostConstruct
public void init(a) {
if (this.restTemplates ! =null) {
Iterator var1 = this.restTemplates.iterator();
while (var1.hasNext()) {
RestTemplate restTemplate = (RestTemplate) var1.next();
List<ClientHttpRequestInterceptor> interceptors = new ArrayList(restTemplate.getInterceptors());
interceptors.add(this.seataRestTemplateInterceptor); restTemplate.setInterceptors(interceptors); }}}}Copy the code
test
Test success scenario:
The curl -x POST at http://127.0.0.1:8084/api/business/purchase/commitCopy the code
The result is true
Test failure scenario:
Sbm-account-service will throw an exception and the transaction will be rolled back
http://127.0.0.1:8084/api/business/purchase/rollback
Copy the code
The result is false
If you look at the undo_log log or primary key, you can see that data is saved during execution. For example, the value of the self-increased primary key changes before and after the command is executed. The value is 1 before and 7 after the command is executed.
The source address
Github.com/gf-huanchup…
reference
Seata. IO/useful – cn/docs /…