Spring Cloud integrated seata distributed transaction-AT mode

This article will introduce the AT mode (version 1.0.0) based on springcloud using the Alibaba distributed transaction framework seata. The AT mode can basically meet 80% of our needs for distributed transactions (non-relational database and middleware operations, cross- The invocation of company services requires a combination of SEATA-TCC mode for cross-language application invocation). For the introduction of seata, please click here to enter the official website of seata.

seata-at mode effect

1. Introduce the seata framework, configure the basic configuration of seata, and establish the undo_log table

2. Consumers introduce global transaction annotation @GlobalTransactional

3. The producer introduces the global transaction annotation @GlobalTransactional

carry out! !

1. Basic terminology of SEATA distributed transaction

  • TC-Transaction Coordinator: Maintains the state of global and branch transactions, and drives global transaction commit or rollback.
  • TM-Transaction Manager: Define the scope of global transactions: start global transactions, commit or roll back global transactions.
  • RM-Resource Manager: Manage resources for branch transaction processing, talk to TC to register branch transactions and report the status of branch transactions, and drive branch transaction commit or rollback.
  • AT mode-Automatic (Branch) Transaction Mode automatic branch transaction
  • TCC Try-(Confirm/Cancel) manual branch transaction of seata

2. Prerequisite preparation

Third, the premise of SEATA's AT mode

AT mode refers to the Automatic (Branch) Transaction Mode automatic branch transaction, the premise of using AT mode is

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

Fourth, the construction of TM and RM

Based on the above two points, we build two microservices, service-tm (transaction initiator) and service-at (resource manager/AT mode distributed transaction).

4.1 Build seata server

4.1.1 Build a seata server (exist as a TC)

4.1.2 mysql run script.sql

4.2 Create maven parent project seata-demo (source code here)

It is recommended to download the source code and view it. In addition to introducing SpringCloud related dependencies, the parent project

It is also necessary to introduce the spring-cloud-alibaba-seata package, which contains seata-all and springboot-seata-starter

<dependency>
   <groupId>com.alibaba.cloud</groupId>
   <artifactId>spring-cloud-alibaba-seata</artifactId>
   <version>2.2.0.RELEASE</version>
</dependency>

openFeign

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

druid (for data source agent) 

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.21</version>
</dependency>

4.3 Create sub-project seata-at (exist as RM, click here for source code )

4.3.1 Service part (here, in order to facilitate understanding, the controller is merged, the actual code is not)

Here @GlobalTransactional annotations to the method to open the global transaction

  • The @Transactional annotation and @GlobalTransactional annotation are used together for the purpose: Since a local transaction commit is a branch transaction, a branch transaction will interact with tc, @Transactional can merge multiple sql into a local transaction, reducing tc interaction (request) promotion performance.
  • XID represents the transaction id of this global transaction, that is, under the request link of A->B->C through Feign, the XID is consistent, so it is convenient for seata to process the branch transaction. And here print the verification result.
@Slf4j
@RestController
public class AtServiceImpl implements AtService {

    @Autowired
    AtDAO atDAO;

    /**
     * rm-at的服务端业务逻辑方法
     * 这里使用Transactional注解的目的是将多个sql合并成一个本地事务
     * 减少tc交互,提升性能
     * 为了方便理解,此处把controller合并
     * @param params - name 入参
     * @return String
     */
    @Override
    @GlobalTransactional(timeoutMills = 60000 * 2)
    @Transactional(rollbackFor = Exception.class ,propagation = Propagation.REQUIRED)
    @PostMapping("/at-insert")
    public String insert(@RequestBody Map<String, String> params) {
        log.info("------------------> xid = " + RootContext.getXID());
        atDAO.insert(params);
        return "success";
    }
}

The way to obtain XID is: 

RootContext.getXID()

The rest of the code is not important, just look at the source code 

4.3.2 application.yml configuration file

server:
  port: 7900
  tomcat.uri-encoding: utf-8

spring:
  application:
    name: service-at
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/seata_test?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: root
    #一定要配置druid
    type: com.alibaba.druid.pool.DruidDataSource
  cloud:
    nacos:
      discovery: 
        server-addr: 127.0.0.1:8848

#使用file作为存储
seata:
  enabled: true
  #此处applicationId与serviceGroup保持实例一致便可
  application-id: ${spring.application.name}
  tx-service-group: seataGroup-${seata.application-id}
  service:
    vgroup-mapping: default
    grouplist: 127.0.0.1:8091
  config:
    type: file
    file:
      name: file.conf
  registry:
    type: file
    file:
      name: file.conf

4.4 Create subproject seata-tm

4.4.1 Part of the source code

@Slf4j
@RestController
public class TmServiceImpl implements TmService {

    @Autowired
    private TmDAO tmDAO;
    @Autowired
    private ServiceATFeign atFeign;

    /**
     * 请求本地事务插入一条记录
     * 再请求at服务插入一条记录
     *
     * @param params - name
     * @return String
     */
    @Override
    @Transactional
    @GlobalTransactional(timeoutMills = 60000 * 2)
    @GetMapping("/insert-at")
    public String insertAt(Map<String, String> params) {
        log.info("------------------> xid = " + RootContext.getXID());
        //本地事务插入一条数据
        tmDAO.insert(params);
        //远程at事务插入一条数据
        atFeign.insertAT(params);
        //强行抛出异常回滚
        throw new RuntimeException("AT服务测试回滚");
        //return "success";
    }

}

It is worth noting that here, like local transactions, you cannot use catch to capture exceptions. Even if you use @ExceptionHandler to capture, the transaction will be swallowed. If you must use exception capture, you can use manual global transaction rollback: 

try{
   …………
}catch(Exception e){
   log.error(e);
   //调用此段代码进行手工事务回滚
   GlobalTransactionContext.reload(RootContext.getXID()).rollback();
}

Feign calls are no different

@FeignClient(value = "service-at")
public interface ServiceATFeign {

    /**
     * 调用at服务插入记录
     *
     * @param params - name
     * @return String
     */
    @PostMapping(value = "/at-insert")
    String insertAT(Map<String, String> params);
}

So far, the more important part of the AT mode project code has been completed.

Five, SEATA distributed transaction verification

5.1 Start seata-server

This article uses the file stand-alone mode to start seata-server. Seeing this step shows that the startup is successful

5.2 First comment out the @Transactional of service under service-tm, otherwise you will not see undo_log

      @Override
//    @Transactional
    @GlobalTransactional(timeoutMills = 60000 * 2)
    public String insertAt(Map<String, String> params) {
        log.info("------------------> xid = " + RootContext.getXID());
        tmDAO.insert(params);
        atFeign.insertAT(params);
        throw new RuntimeException("AT服务测试回滚");
//        return "success";
    }

5.3 Start seata-tm, seata-at in debug mode

If seata-server sees the following log, the registration is successful

Perform a breakpoint here in service-tm

Browser request

Execute to the first breakpoint, which is tmDAO.insert(params)

At this time, check the log table service_tm has been inserted into a log tanzj

View data sheet undo_log

See a log, we will bejson

rollback_info: The mirror before and after modification of the field is recorded here, which is the core of seata rollback

{
	"@class": "io.seata.rm.datasource.undo.BranchUndoLog",
	"xid": "192.168.0.103:8091:2035687749",
	"branchId": 2035687750,
	"sqlUndoLogs": ["java.util.ArrayList", [{
		"@class": "io.seata.rm.datasource.undo.SQLUndoLog",
		"sqlType": "INSERT",
		"tableName": "service_tm",
		"beforeImage": {
			"@class": "io.seata.rm.datasource.sql.struct.TableRecords$EmptyTableRecords",
			"tableName": "service_tm",
			"rows": ["java.util.ArrayList", []]
		},
		"afterImage": {
			"@class": "io.seata.rm.datasource.sql.struct.TableRecords",
			"tableName": "service_tm",
			"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": 4,
					"value": 93
				}, {
					"@class": "io.seata.rm.datasource.sql.struct.Field",
					"name": "aName",
					"keyType": "NULL",
					"type": 12,
					"value": "tanzj"
				}]]
			}]]
		}
	}]]
}

Check the xid of service-tm and service-at:

After the execution continues, after tm throws an exception, the undo_log log disappears, and the inserted data is empty, indicating that the distributed transaction execution is complete.

So far, the establishment of SEATA-AT mode is completed

 

Guess you like

Origin blog.csdn.net/bbcckkl/article/details/104362049