seata introduction
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 microservice architecture. Before the open source of Seata, the corresponding internal version of Seata played a role of distributed consistency Middleware in Ali, helping Ali to survive the double 11 of the past years and providing strong support for various businesses. After years of precipitation and accumulation, 2019.1 Seata officially announced open source. Currently, Seata 1.0 has GA.
Distributed transaction 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 will ensure data consistency.
The microservice architecture has changed. The three modules mentioned above are designed as three services. Local transactions naturally guarantee data consistency in each service. But what about the whole business logic?
What about Seata?
We say that a distributed transaction is a global transaction composed of a batch of branch transactions, which are usually only local transactions.
Seata has three basic components:
- Transaction Coordinator (TC): maintains the state of global and branch transactions, driving global commit or rollback.
- Transaction manager TM: define the scope of global transaction: start global transaction, commit or roll back global transaction.
- Resource Manager (RM): manage the resources of the branch transaction being processed, talk with TC to register the branch transaction and report the status of the branch transaction, and drive the commit or rollback of the branch transaction.
Typical lifecycle of distributed transactions managed by Seata:
- TM requires TC to start a new global transaction. TC generates xids that represent global transactions.
- XID is propagated through the call chain of microservices.
- RM registers the local transaction as a branch of the corresponding global transaction from XID to TC.
- TM requires TC to commit or fallback the corresponding XID global transaction.
- TC drives all branch transactions under the corresponding global transaction of XID to complete branch commit or rollback.
Quick start
Use case
Business logic for users to purchase goods. The whole business logic is supported by three microservices:
- Warehousing service: deduct the warehousing quantity from the given goods.
- Order service: create an order according to the purchase demand.
- Account service: deduct balance from user account.
Environmental preparation
Step 1: establish 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;
In the sea at mode, the undo log table is required, and the other three are business tables.
Step 2: start the Seata Server
The Server-side storage mode (store.mode) has two types: file and db (raft will be introduced later). The file mode does not need to be changed and can be started directly. The db mode needs to import three tables for storing global transaction call back information.
*Note: the file mode is stand-alone mode, and the global transaction session information is read and written in memory and the local file root.data is persisted, with high performance;
db mode is high availability mode, and global transaction session information is shared through db, so the corresponding performance is poor*
You can start Seata Server directly through bash script or through Docker image. However, Docker only supports file mode at present, and does not support registering Seata Server in Eureka or Nacos registration center.
Start by script
stay https://github.com/seata/seata/releases Download the corresponding version of Seata Server, decompress it and execute the following command to start. Here, use file configuration
Start with Docker
docker run --name seata-server -p 8091:8091 seataio/seata-server:latest
Project introduction
Project name | address | Explain |
---|---|---|
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 service |
sbm-business-service | 127.0.0.1:8084 | Main business |
seata-server | 172.16.2.101:8091 | seata-server |
Core code
In order not to make the space 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 sea TA's dependency eata spring boot starter
<dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <version>1.0.0</version> </dependency>
Warehousing service
application.properties
spring.application.name=account-service server.port=8081 spring.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=debug
StorageService
public interface StorageService { /** * Deduct storage quantity */ void deduct(String commodityCode, int count); }
Order service
public interface OrderService { /** * Create order */ Order create(String userId, String commodityCode, int orderCount); }
Account service
public interface AccountService { /** * Loan from user account */ void debit(String userId, int money); }
Main business logic
Only one @ GlobalTransactional annotation is needed 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); }
XID transfer
Cross service delivery of global transaction ID needs to be implemented by ourselves. Here we use interceptors. Each service needs to add the following two classes.
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() { } }
SeataRestTemplateAutoConfiguration
@Configuration public class SeataRestTemplateAutoConfiguration { @Autowired( required = false ) private Collection<RestTemplate> restTemplates; @Autowired private SeataRestTemplateInterceptor seataRestTemplateInterceptor; public SeataRestTemplateAutoConfiguration() { } @Bean public SeataRestTemplateInterceptor seataRestTemplateInterceptor() { return new SeataRestTemplateInterceptor(); } @PostConstruct public void init() { 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); } } } }
test
Test success scenario:
curl -X POST http://127.0.0.1:8084/api/business/purchase/commit
The returned result is: true
Test failure scenario:
When a user with UserId 1002 places an order, SBM account service will throw an exception and the transaction will be rolled back
http://127.0.0.1:8084/api/business/purchase/rollback
The returned result is: false
View the log or primary key of undo log, and you can see that there are saved data during execution.
For example, if you view the value of the auto increment of the primary key, the value will change before and after execution, which is 1 before execution and 7 after execution.
Source address
https://github.com/gf-huanchupk/SpringBootLearning/tree/master/springboot-seata