1. The background

This section introduces Ali’s distributed transaction framework, SeATA, and integration examples.

2. Knowledge

Seata is an open source distributed transaction solution dedicated to providing high performance and easy to use distributed transaction services.

Seata users provide a variety of transaction modes:

  • AT mode
  • TCC mode
  • SAGA mode
  • XA mode

2.1 the AT mode

Suitable scene:

  • Based on a relational database that supports local ACID transactions.
  • Java applications, access the database through JDBC.

The AT pattern is an evolution based on the 2PC pattern (two-phase commit protocol) :

  • Phase one: When a business operation is performed, an operation log record is recorded, committed together in the same local transaction, and local locks and connection resources are released.
  • Phase two: Asynchronization completes quickly. Or roll back logs for rollback.

2.2 TCC mode

TCC mode, independent of transaction support for underlying data resources:

  • One-stage Prepare behavior: The customized prepare logic is invoked.
  • Two-stage COMMIT behavior: Custom commit logic is invoked.
  • Two-stage ROLLBACK behavior: Invoke custom ROLLBACK logic.

This article discusses only the AT pattern

2.3 concept

Transaction Coordinator (TC) – The Transaction Coordinator maintains the status of global and branch transactions and drives global transactions to be committed or rolled back.

TM (Transaction Manager) – The Transaction Manager defines the scope of a global Transaction: start, commit, or roll back the global Transaction.

Resource Manager (RM) – The Resource Manager manages the resources for branch transaction processing, talks with TCS to register branch transactions and report the status of branch transactions, and drives branch transactions to commit or roll back.

The relationship between them is shown as follows:

3. An example of integrating Springboot with Seate

3.1 Overall structure composition

Consists of these:

  • One SEata-Server, as the TC transaction coordinator
  • Multiple microservices and service methods as RM resource providers

You can think of the transaction manager as the starting method for initiating distributed transaction invocations.

3.2 Step 1: Configure the Server

Seata-server is an official server provided by SEATA and is used as a coordinator.

Seata-server storage mode which seata-server storage transaction ID, each branch transaction and other content, it can support a variety of storage modes (store.mode), existing

  • File Text mode storage
  • Db Database storage
  • Redis is stored in Redis

File storage is a single-server mode that requires no additional configuration and can be directly started. Db storage needs to build the database table, and configure the parameters. Redis stores faster, but there is a risk of transaction information loss.

It is also possible to use DB mode in case a single service fails under high availability requirements.

I’m going to use file mode here.

  • From github.com/seata/seata… To download the server software package and decompress it.
Usage: sh seata-server.sh(for linux and mac) or cmd seata-server.bat(for windows) [options] Options: --host, -h The host to bind. Default: 0.0.0.0 --port, -p The port to listen. 8091 --storeMode, -m log store mode: file, db Default: file --helpCopy the code

Activation:

Sh -p 8091 -h 127.0.0.1 -m fileCopy the code

-h is the binding IP address, -p is the port, and -m specifies the storage mode file text mode.

3.3 Step 2: Configure microservices

1. Add dependencies

        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
        </dependency>
Copy the code

2. Configure an ADDRESS for accessing the SEATA service

Modify the application.yml file

seata: enabled: true application-id: business-service tx-service-group: my_test_tx_group service: vgroup-mapping: Degrade: false #disable-global-transaction: false my_test_tx_group: default grouplist: default: 127.0.0.1:8091 #enable-degrade: falseCopy the code

3. Configure the data source for data access

I use IBatis as the storage layer and configure the data source

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        return druidDataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
            .getResources("classpath*:/mapper/*.xml"));
        return factoryBean.getObject();
    }
Copy the code

4. Start a transaction ona method Using the @GlobalTransactional annotation to identify the way to start a transaction

@globalTransactional public void payMoney(int id, int money) {logger. info("# transactional start... xid: " + RootContext.getXID()); Money money1 = moneyMapper.selectById(id); If (money1.getMoney() + money < 0) {throw new RuntimeException(" not enough money "); } money1.setMoney(money1.getMoney() + money); moneyMapper.updateById(money1); storageClient.changeMoney(id, money * 100); }Copy the code

This completes the configuration of the microservice, which is responsible for starting a distributed transaction. In the process, it also invokes services in other microservices that are part of the overall service. So we also need to pass the “transaction ID”, and we use the RestTemplate as the client tool for the HTTP request, so we can write an interceptor that carries the “transaction ID” in each HTTP request.

3.4 Step 4: Carry the Transaction ID in the RestTemplate

With SpringBoot, we can add an initialization method when injecting the RestTemplate:

/ * RestTemplate injection * / @ 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); }}}} the realization of the interceptor class / * * / public class SeataRestTemplateInterceptor implements ClientHttpRequestInterceptor {private static  final Logger LOGGER = LoggerFactory.getLogger(SeataRestTemplateInterceptor.class); public SeataRestTemplateInterceptor() { } public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution ClientHttpRequestExecution) throws IOException {LOGGER. The info (" # into the RestTemplate blocker "); HttpRequestWrapper requestWrapper = new HttpRequestWrapper(httpRequest); String xid = RootContext.getXID(); if (StringUtils.isNotEmpty(xid)) { requestWrapper.getHeaders().add(RootContext.KEY_XID, xid); Logger. info("# add global transaction xID to RestTemplate request =" + xid); } return clientHttpRequestExecution.execute(requestWrapper, bytes); }}Copy the code

The transaction ID is passed in the invocation chain of the microservice and bound to the Seata context so that a complete transaction is concatenated by the transaction ID.

At this point, the RestTemplate sends the HTTP request with the transaction ID, while other microservice callers need to bind the transaction ID to the Context object of Seata after receiving the “transaction ID”.

3.5 Step 4: Bind the transaction ID to the context.

The callee of the microservice, the recipient of the transaction ID, needs to bind the transaction ID to the context. The context object of Seata can call rootContext.bind (xID); The transaction ID is bound.

We write a filter that retrieves the transaction ID from the request and completes binding the transaction ID to the context.

@Component public class SeataFilter implements Filter { private static final Logger LOGGER = LoggerFactory.getLogger(SeataFilter.class); @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)) {logger. info("# bind xid=" + xid); RootContext.bind(xid); isBind = true; } try { filterChain.doFilter(servletRequest, servletResponse); } finally { if (isBind) { RootContext.unbind(); } } } @Override public void destroy() { } }Copy the code

3.6 Step 5: Create UNDO_LOG table (Rollback log table)

The UNDO_LOG table is a rollback log table. It keeps a record of how the data changes from time to time, so it can be used as a rollback record. When rollback is needed, the data from the table is read and written back.

Create this table in the database used by each of your microservices as follows:

Rollback log Table UNDO_LOG Table: Different databases have slightly different types.

Take MySQL as an example:

Field Type branch_id bigint PK xid varchar(100) context varchar(128) rollback_info longblob log_status tinyint Context CREATE TABLE 'undo_log' (' id 'BIGint (20) NOT NULL context 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

These are the key steps for integrating SEATA.

3.7 summarize

How to use rollback log table?

Seata is the “two-step commit mechanism”, or 2PC, enhanced in two-step commit mode:

The first step:

  • — 1.1 Before update: According to the condition information obtained through parsing, generate query statements to obtain “data before modification”.

  • — 1.2 Running services SQL: Update the name of this record to ‘GTS’.

  • — 1.3 After Update: Locate data by primary key and obtain “Modified data”

  • — 1.4 Inserting a Rollback Log: The rollback log records mirror data and service SQL information into the UNDO_LOG table.

Step 2: According to the business operation, judge the result of the branch transaction and decide whether to roll back or commit:

  • If committed: Deletes records from the UNDO_LOG table in each branch transaction.
  • If rollback: read UNDO_LOG table record content, rewrite “original data” can be.

As you can see, SEATA implements rollback through the UNDO_LOG table. It does not rely on the rollback mechanism of the local transaction of the data. It does not lock “resources”, resulting in higher throughput of data access.

5. Extension

Examples of my code can be found at: github.com/vir56k/java…

5. Reference:

IO /zh-cn/docs/… Seata. IO/useful – cn/docs /… Github.com/seata/seata…