当前位置:网站首页>Seata 1.3.0 four modes to solve distributed transactions (at, TCC, Saga, XA)

Seata 1.3.0 four modes to solve distributed transactions (at, TCC, Saga, XA)

2022-07-07 10:55:00 Clang clang

Preface

1、seata edition 1.3.0

2、 Infrastructure project structure , You just need to focus on Device modules device and Work order module order that will do .
 Insert picture description here -

project explain
api-gateway Gateway module
common Basic module
device Device modules
order Work order module
user User module

3、 Database description , Device modules device link gxm-301 database , Work order module order link gxm-300 database
 Insert picture description here

4、 Main business description , When generating work orders , We use order Service to gxm-300 Table of database work-order and notice_info Insert database , And call... Remotely device Service insertion gxm-301 Table of database work-problem And table work_order_problem_link

5、 Debugging instructions , We are using @GlobalTransactional When annotating ,seata The control transaction of is time limited, and the default is 1 minute , So in us debug If the time is too long ,seata Rollback by default , For the convenience of debugging , You can change this parameter .

 Insert picture description here

6、 Official == Newcomer documentation It must be seen ==

One 、AT Pattern

1、 about seata The default is AT Pattern , And if you rely on seata-spring-boot-starter when , Automatic proxy data source , There is no need for additional treatment

2、 about AT The mode will be found when rolling back undo_log Front and rear mirrors in , To recover .

But when recovering , It will compare whether the post image has been modified , That is to compare the data , If it's different , It indicates that the data has been modified by actions other than the current global transaction , This is the time AT When the mode performs data verification , Will fail to roll back , Because the verification fails , We can also turn off the verification process of the front and back images through configuration parameters , But this is not recommended , because , It was modified by other threads, which made it impossible to restore the scene , It really needs to be handled manually

3、 This is official AT Instructions for using the mode , These are all points that must be paid attention to .
 Insert picture description here

4、 One of the official Newcomer documentation It must be seen , In the old version , We want to proxy the data source , as follows , For specific mode, choose different data sources to proxy , For example, we are now simulating AT, That's it return new DataSourceProxy(druidDataSource);.

@Primary
@Bean("dataSource")
public DataSource dataSource(DataSource druidDataSource) {
    
    //AT  agent   A choice 
    return new DataSourceProxy(druidDataSource);
    //XA  agent 
    return new DataSourceProxyXA(druidDataSource)
}

5、 But how do you use a higher version or use seata-starter, There is no need to manually configure , Because I use seata-starter, and What I'm demonstrating now is AT Pattern , So don't change anything ( Follow up XA The mode will be modified )
 Insert picture description here
 Insert picture description here

6、 Common questions , The official has already given instructions and answers common problem , Before being used in production , It's best to read all these .

1.1、 Instructions

1.1.1、 Use

1、 We use annotations @GlobalTransactional Turn on seata Of AT Pattern of transaction management , And because it uses seata-starter, Then this annotation will automatically proxy AT Data sources for patterns , The specific code is as follows , You can see that the code is mainly divided into two parts , The first part is to call yourself order Service 2 Table mapper Insert data into gxm-300, The second part is remote call device Of the two mapper To insert data into gxm-301
 Insert picture description here

 Insert picture description here

@GlobalTransactional(name = "default", rollbackFor = Exception.class)
    @Override
    public R saveWithDetail(SaveWithDetailDTO saveWithDetailDTO) {
    
        log.info("create order begin ... xid: " + RootContext.getXID());

        String title = RandomUtil.randomString(20);
        saveWithDetailDTO.setWorkOrderTitle(title);
        saveWithDetailDTO.setWorkOrderNumber("asd");

        // 1、 Call your own service 
        // 1.1、 Insert job information 
        WorkOrder workOrder = new WorkOrder();
        BeanUtils.copyProperties(saveWithDetailDTO, workOrder);
        this.baseMapper.insert(workOrder);

        // 1.2、 Insert message notification table 
        NoticeInfo noticeInfo = new NoticeInfo();
        noticeInfo.setTitle("new work order 【" + title + "】has publish");
        noticeInfoMapper.insert(noticeInfo);

        //  When work order id Not for null when , Simulate an exception 
        if (saveWithDetailDTO.getId() != null) {
    
            int i = 1 / 0;
        }

        // 2、 The remote invocation  device  service 
        // 2.1、 Insert the question table   And problem association table 
        WorkProblemDTO workProblemDTO = new WorkProblemDTO();
        BeanUtils.copyProperties(saveWithDetailDTO.getSoftwareNotSolveProblemList().get(0), workProblemDTO);
        workProblemDTO.setOrderId(workOrder.getId());
        workProblemApi.insertWithLink(workProblemDTO);
        return R.ok();
    }

 Insert picture description here

@Override
    public R insertWithLink(WorkProblemDTO workProblemDTO) {
    
        WorkProblem workProblem = new WorkProblem();
        BeanUtils.copyProperties(workProblemDTO, workProblem);
        // 1、 Insert the question table 
        int insertProblem = workProblemMapper.insert(workProblem);

        // 2、 Insert the work order problem association table 
        WorkOrderProblemLink workOrderProblemLink = new WorkOrderProblemLink();
        workOrderProblemLink.setOrderId(workProblemDTO.getOrderId());
        workOrderProblemLink.setProblemId(workProblem.getId());
        int insertOrderProblemLink = workOrderProblemLinkMapper.insert(workOrderProblemLink);

        if (insertProblem > 0 && insertOrderProblemLink > 0) {
    
            return R.ok();
        }
        throw new RuntimeException(" Insertion exception ");
    }

2、 Test the successful way first , That is, when transferring parameters id It's empty , be 2 It's a database 4 There is no problem with all the tables , All inserted successfully , No problem

3、 The retest failed , That is, when transferring parameters id Not empty , be seata Data global transactions will take effect ,2 A database 4 None of the tables has a database , explain seata Of AT The mode has taken effect ,

1.1.2、 Analysis

1、 Let's make a breakpoint , You can find AT The secret of pattern , We directly put a breakpoint at the last position of the call chain , This position is 4 individual mapper Have been inserted successfully , however device The service did not return , So the whole link is not over , And at this time, I lengthen the time of business , Enough for us to debug .

 Insert picture description here
2、 When stopping at the end , We observe gxm-300 Database and gxm-301 database , You'll find that ,4 individual mapper The inserted data of has been inserted into the database , And one mapper Will be in the corresponding undo_log Insert a piece of data into the table , There will be pre image data and post image data , as well as Branch id branch_id

3、gxm-300 Of undo_log surface

id	branch_id	xid	context	rollback_info	log_status	log_created	log_modified	ext
7	278197152412237825	192.168.172.232:8091:278197152336740352	serializer=jackson	(BLOB) 3.66 KB	0	2022-06-09 16:16:10	2022-06-09 16:16:10	
8	278197152475152385	192.168.172.232:8091:278197152336740352	serializer=jackson	(BLOB) 2.27 KB	0	2022-06-09 16:16:10	2022-06-09 16:16:10	

 Insert picture description here
4、 gxm-301 Of undo_log surface

id	branch_id	xid	context	rollback_info	log_status	log_created	log_modified	ext
7	278197152550649857	192.168.172.232:8091:278197152336740352	serializer=jackson	(BLOB) 982 bytes	0	2022-06-09 16:16:10	2022-06-09 16:16:10	
8	278197152600981505	192.168.172.232:8091:278197152336740352	serializer=jackson	(BLOB) 999 bytes	0	2022-06-09 16:16:10	2022-06-09 16:16:10	

 Insert picture description here

5、seata Of branch_table surface

branch_id	xid	transaction_id	resource_group_id	resource_id	branch_type	status	client_id	application_data	gmt_create	gmt_modified
278197152412237825	192.168.172.232:8091:278197152336740352	278197152336740352		jdbc:mysql://127.0.0.1:3306/gxm-300 AT 0 OrderApplication-seata-id:192.168.172.232:56035 2022-06-09 16:16:09.791745 2022-06-09 16:16:09.791745
278197152475152385	192.168.172.232:8091:278197152336740352	278197152336740352		jdbc:mysql://127.0.0.1:3306/gxm-300 AT 0 OrderApplication-seata-id:192.168.172.232:56035 2022-06-09 16:16:09.806598 2022-06-09 16:16:09.806598
278197152550649857	192.168.172.232:8091:278197152336740352	278197152336740352		jdbc:mysql://127.0.0.1:3306/gxm-301 AT 0 DeviceApplication-seata-id:192.168.172.232:56409 2022-06-09 16:16:09.824506 2022-06-09 16:16:09.824506
278197152600981505	192.168.172.232:8091:278197152336740352	278197152336740352		jdbc:mysql://127.0.0.1:3306/gxm-301 AT 0 DeviceApplication-seata-id:192.168.172.232:56409 2022-06-09 16:16:09.837013 2022-06-09 16:16:09.837013

 Insert picture description here

6、 seata Of global_table surface

xid	transaction_id	status	application_id	transaction_service_group	transaction_name	timeout	begin_time	application_data	gmt_create	gmt_modified
192.168.172.232:8091:278197152336740352	278197152336740352	5	OrderApplication-seata-id	my_test_tx_group	default	600000	1654762569770		2022-06-09 16:16:09	2022-06-09 16:16:42

 Insert picture description here
7、seata Of lock_table surface

row_key	xid	transaction_id	branch_id	resource_id	table_name	pk	gmt_create	gmt_modified
jdbc:mysql://127.0.0.1:3306/gxm-300^^^notice_info^^^4 192.168.172.232:8091:278197152336740352 278197152336740352 278197152475152385 jdbc:mysql://127.0.0.1:3306/gxm-300 notice_info 4 2022-06-09 16:16:09 2022-06-09 16:16:09
jdbc:mysql://127.0.0.1:3306/gxm-300^^^work_order^^^4 192.168.172.232:8091:278197152336740352 278197152336740352 278197152412237825 jdbc:mysql://127.0.0.1:3306/gxm-300 work_order 4 2022-06-09 16:16:09 2022-06-09 16:16:09
jdbc:mysql://127.0.0.1:3306/gxm-301^^^work_order_problem_link^^^4 192.168.172.232:8091:278197152336740352 278197152336740352 278197152600981505 jdbc:mysql://127.0.0.1:3306/gxm-301 work_order_problem_link 4 2022-06-09 16:16:09 2022-06-09 16:16:09
jdbc:mysql://127.0.0.1:3306/gxm-301^^^work_problem^^^4 192.168.172.232:8091:278197152336740352 278197152336740352 278197152550649857 jdbc:mysql://127.0.0.1:3306/gxm-301 work_problem 4 2022-06-09 16:16:09 2022-06-09 16:16:09

 Insert picture description here
8、 The relationship is branch_id and transaction_id, One transaction_id Indicates the beginning of a global transaction , There will be more branch_id Branch business

9、 If our business turns out to be ok ( It refers to the normal and successful insertion of business , Or there are exceptions but seata Of AT Mode helps you rollback , And there is no problem rolling back ), Then these tables will not have data , Because our overall affairs are over , Ensure the current business process , Even if it fails , But I rolled it back for you . Once our table has data , Just explain , Business execution is abnormal and seata There is a problem during rollback , This is the time ,seata The table data of relevant information will be stored , Don't delete , We see that we have to deal with it .

For example, in one case , We are in the exception process , Halfway through the first thread , namely work_order Table data insertion succeeded , But we modify it manually in the database , Or other thread transactions modify the newly produced data , But the first site implementation goes to the back , Ready to insert work_problem Something is wrong , So at this point ,seata Of at The mode will be rolled back according to the relevant logs , But when rolling back , It will check. , in the meantime `work_order`` The data just inserted , Has it been modified , Once it is inconsistent with its original record , Then it iu I can't help you deal with it . This is the time , The data of the related table is stored , We have to deal with it manually according to this information .

1.1.3、AT Mode rollback failed , Handle

1、 For the front 1.1.2 section Debugging of , I have a problem here , Maybe it's because my breakpoint stays too long , You will find data in relevant tables , say seata Of at Mode rollback failed . Next we have to deal with .

2、 See the global transaction id278197152336740352

 Insert picture description here
3、 Find out which transaction branches are left under the current global transaction , As you can see, yes gxm-300 There is something wrong with our business , and pk The fields are 4, The description is that the primary key of these two tables is 4 There's a problem with .

 Insert picture description here
4、 So we find it in the corresponding data undo_log, You can see the corresponding branch id It is also corresponding to the previous one , among rollback_info The field records the data of the pre image and the data of the post image
 Insert picture description here
5、 We click rollback_info Field , Then save the data as xxx.json file , Open as follows

{
    
    "@class":"io.seata.rm.datasource.undo.BranchUndoLog",
    "xid":"192.168.172.232:8091:278197152336740352",
    "branchId":278197152412237825,
    "sqlUndoLogs":[
        "java.util.ArrayList",
        [
            {
    
                "@class":"io.seata.rm.datasource.undo.SQLUndoLog",
                "sqlType":"INSERT",
                "tableName":"work_order",
                "beforeImage":{
    
                    "@class":"io.seata.rm.datasource.sql.struct.TableRecords$EmptyTableRecords",
                    "tableName":"work_order",
                    "rows":[
                        "java.util.ArrayList",
                        [

                        ]
                    ]
                },
                "afterImage":{
    
                    "@class":"io.seata.rm.datasource.sql.struct.TableRecords",
                    "tableName":"work_order",
                    "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":"PRIMARY_KEY",
                                            "type":4,
                                            "value":4
                                        },
                                        {
    
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"work_order_number",
                                            "keyType":"NULL",
                                            "type":12,
                                            "value":"asd"
                                        },
                                        {
    
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"work_order_title",
                                            "keyType":"NULL",
                                            "type":12,
                                            "value":"bfkzc4oganhbirygfd87"
                                        },
                                        {
    
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"client_name",
                                            "keyType":"NULL",
                                            "type":12,
                                            "value":" Yuan Yuhuan 2"
                                        },
                                        {
    
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"client_contact",
                                            "keyType":"NULL",
                                            "type":12,
                                            "value":" tencent 333"
                                        },
                                        {
    
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"client_phone",
                                            "keyType":"NULL",
                                            "type":12,
                                            "value":"181562383652"
                                        },
                                        {
    
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"order_service_type",
                                            "keyType":"NULL",
                                            "type":4,
                                            "value":1
                                        },
                                        {
    
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"order_type",
                                            "keyType":"NULL",
                                            "type":4,
                                            "value":1
                                        },
                                        {
    
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"service_type",
                                            "keyType":"NULL",
                                            "type":4,
                                            "value":3
                                        },
                                        {
    
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"deal_user_id",
                                            "keyType":"NULL",
                                            "type":4,
                                            "value":20
                                        },
                                        {
    
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"order_content",
                                            "keyType":"NULL",
                                            "type":-1,
                                            "value":" Weekend evening party 2"
                                        },
                                        {
    
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"create_user_id",
                                            "keyType":"NULL",
                                            "type":4,
                                            "value":null
                                        },
                                        {
    
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"create_time",
                                            "keyType":"NULL",
                                            "type":93,
                                            "value":null
                                        },
                                        {
    
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"specify_processing_day",
                                            "keyType":"NULL",
                                            "type":91,
                                            "value":[
                                                "java.sql.Date",
                                                1653321600000
                                            ]
                                        },
                                        {
    
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"receive_time",
                                            "keyType":"NULL",
                                            "type":93,
                                            "value":null
                                        },
                                        {
    
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"receiver_submit_time",
                                            "keyType":"NULL",
                                            "type":93,
                                            "value":null
                                        },
                                        {
    
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"end_time",
                                            "keyType":"NULL",
                                            "type":93,
                                            "value":null
                                        },
                                        {
    
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"order_status",
                                            "keyType":"NULL",
                                            "type":4,
                                            "value":null
                                        },
                                        {
    
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"receiver_refuse_content",
                                            "keyType":"NULL",
                                            "type":-1,
                                            "value":null
                                        },
                                        {
    
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"receiver_deal_content",
                                            "keyType":"NULL",
                                            "type":-1,
                                            "value":null
                                        },
                                        {
    
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"receiver_result_status",
                                            "keyType":"NULL",
                                            "type":4,
                                            "value":null
                                        },
                                        {
    
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"send_refuse_content",
                                            "keyType":"NULL",
                                            "type":12,
                                            "value":null
                                        },
                                        {
    
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"device_model",
                                            "keyType":"NULL",
                                            "type":12,
                                            "value":null
                                        },
                                        {
    
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"device_number",
                                            "keyType":"NULL",
                                            "type":12,
                                            "value":null
                                        },
                                        {
    
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"service_report_images",
                                            "keyType":"NULL",
                                            "type":-1,
                                            "value":"http://gxm-tensquare.oss-cn-beijing.aliyuncs.com/2022-04/25/e214ba1b-6541-4756-8a2b-fdb1c29111e4.jpg"
                                        }
                                    ]
                                ]
                            }
                        ]
                    ]
                }
            }
        ]
    ]
}

6、 According to the above json The contents of the document , Direct operation is insert, and device No insertion succeeded , But it inserted successfully , We can delete the relevant data , Later, if it is other situations , such as update such , According to the front and back mirror data , Handle as required .

1.2、 Test the corresponding method without placing spring Transaction annotation for , Whether multiple businesses are rolled back normally

1、 For the above method , We know everything order The service called the local 2 individual mapper, Insert into his linked database (gxm-300) in , And then remotely call deivce Method of service , and device Inside the method of is to call device The local 2 individual mapper, Insert into his linked database (gxm-301) in , therefore , We don't add spring Transaction annotation for , I'm not here order Service saveWithDetail Methods and device Service insertWithLink Methodically used spring Transaction annotation for
 Insert picture description here

 Insert picture description here
2、 This result has been proved in our simple use in the first section , Yes. , We don't need to add the corresponding spring The transaction of ,seata It will be guaranteed .

3、 Of course someone might say , For the following method , Maybe device Our business needs , Distributed transactions are not used , It takes spring To manage , I need to add spring Transaction annotations for , This statement , Of course you can add spring Transaction annotation for ,seata Does not affect the , But I don't recommend , Because under this category api It is provided for external calls , If it is internal, the business of this service should be handled in other classes , It shouldn't be here , It is inappropriate to be called internally .

 Insert picture description here

1.3、 test seata When rolling back , After the mirrored data is modified by other transactions , Unable to rollback successful cases

1、 First of all, we need to understand seata Of AT Mode flow , Official documents :Seata AT Pattern

2、 After reading this process , Everyone can make up a problem , It is as follows when the global transaction is not committed , The mirrored data has been modified by other sites , as follows , In this case seata There is no way to deal with , Unless you turn off the front and rear mirror check , Force data updates , This is too insecure .

Order service and inventory service .
After opening the global transaction , Inventory service has committed a local transaction ,50 The inventory is modified to 49, Global transaction not committed .
Another thread starts the local transaction , Modify inventory from 49 To 48.
However, the order service reported an error , The global transaction needs to be rolled back , At this time, the global transaction will fail to rollback , Dirty data .
Is there any good plan to deal with this situation ? Or avoid this situation

3、 This kind of problem ,github Of issue It has been put forward Dirty writes cause data rollback failure ?, This situation , There are two ways to deal with

4、 This official article also says very well Detailed explanation Seata AT Pattern transaction isolation level and global lock design , I also talked about the situation and treatment of dirty writing .

1.3.1、 Simulate this situation

1、 Or the previous interface , We modify seata Global control time , Because the test will take a long time
 Insert picture description here

2、 We are device The service is performing all mapper After pause 20 second , At this time, the database already has 2 It's a database 4 Table data , as follows

 Insert picture description here

3、 Note that we set it up for a long time , So the remote call may timeout , Directly lead to seata Roll back , We have to call the modified thread urgently in the future , therefore , We need to modify the timeout of components called remotely , What I use here is dubbo, So I set up consumers order Of dubbo The call timeout time of , It is amended as follows 30 second , enough . springBoot Integrate dubbo Time out settings for

#  Set the timeout and retry times of remote calls 
dubbo.provider.timeout=30000
dubbo.provider.retries=0
dubbo.consumer.timeout=30000
dubbo.consumer.retries=0

 Insert picture description here

4、 Add a thread to modify uncommitted data , In front of the pause in 20 seconds , Let's call the following interface , Modify the data submitted for the transaction .
 Insert picture description here
5、 Another thread we modified is device Service data , therefore , You can see the console log as follows , and rm At this time, I will keep trying , You can see the picture in the back , Try and fail , The console keeps printing messages about failed attempts .
 Insert picture description here
 Insert picture description here

6、 The data level is as follows , So the database gxm-300 and gxm-301 Of undo_log The total number of data pieces must be 3 strip .
 Insert picture description here
 Insert picture description here

1.3.2、 The first way to deal with it ( Handle by hand , Save according to business )

7、 We can use the above expression data , To manually process according to the business , Such as through lock_table Field of pk, And current business know , Insert three more data of these three tables , The primary keys are 1 Of , So according to our business, we can delete relevant data directly . And remember seata The data of related tables in the database must also be deleted , And corresponding undo_log Table data also needs to be deleted .

 Insert picture description here

1.3.3、 The second way to deal with it (@GlobalLock)

1、 Add to the locally modified transaction @GlobalLock

  • The parameter lockRetryTimes Try interval ,lockRetryTimes Number of attempts , Explain how many times in seconds the global lock will be retried repeatedly , If the record is in the global transaction , It will fail
  • These two parameters are in 1.4.0 And the version above ,1.3.0m Not yet .
     Insert picture description here

2、 You can refer to Seata Introductory series (22)[email protected] Annotation usage scenarios and source code analysis , That's good.

3、 Another modified thread finds the modified data in the global transaction , Therefore, modification is not supported .

 Insert picture description here
4、 Transaction rollback succeeded ,undo_log The front image data of is consistent with the data of the database , Note that it has not been modified by the previous thread .
 Insert picture description here

 Insert picture description here

Two 、TCC Pattern

1、 Actually TCC Patterns and AT The process is the same , It's just AT Is automatically based on undo_log Transaction rollback and compensation , and TCC We need to provide corresponding interfaces , Officials have also indicated Seata TCC Pattern , You can see TCC The first and second phases of are custom logic ,seata Just call . and AT It all depends on undo_log, then seata Judge to help you deal with .

 Insert picture description here

2、 Here we need to introduce some basic annotations and parameters that need to be used later

  • @LocalTCC Apply to SpringCloud+Feign Mode of TCC, But when I experimented , The call uses dubbo, Theoretically, this annotation is not needed ( Official demo of use dubbo Of did not add this annotation ), But I tried , If you don't add it, it will appear tcc BusinessActionContext get null , The official has not dealt with it yet , I wonder what the problem is , I also reply below .
     Insert picture description here

  • @TwoPhaseBusinessAction annotation try Method , among name For the current tcc Methodical bean name , Write the method name ( Remember that the whole situation is unique ),commitMethod Point to the submit method ,rollbackMethod Point to the transaction rollback method . After specifying three methods ,seata Depending on the success or failure of the global transaction , To help us automatically call the commit method or rollback method .

  • @BusinessActionContextParameter Annotations can pass parameters to phase two (commitMethod/rollbackMethod) Methods , This is also the problem mentioned below , The parameters obtained in the second stage can only be the parameter values defined by annotations at the beginning of the first stage , Even if you modify it in the first stage , add to , It is also impossible to obtain the latest parameter value in the second stage .

  • BusinessActionContext Refers to TCC Transaction context , You can get... Through this parameter xidbranchIdactionName, And some parameters , Be careful , The problem here is On prepare Stage , That is to say try Add parameters to the data of the stage code , Or modify the parameters , stay confrim and cancel The modified data cannot be accepted in the phase method .

3、TCC Participants need to implement three methods , They are the first stage Try Method 、 Two stages Confirm Method and two stages Cancel Method . stay TCC Participants need to add @TwoPhaseBusinessAction annotation , And declare these three methods , As shown below

public interface TccAction {
    
    @TwoPhaseBusinessAction(name = "yourTccActionName", commitMethod = "confirm", rollbackMethod = "cancel")
    public boolean try(
    BusinessActionContext businessActionContext, int a, int b);

    public boolean confirm(BusinessActionContext businessActionContext);

    public boolean cancel(BusinessActionContext businessActionContext);
}

@TwoPhaseBusinessAction Annotation property description :

  • name :TCC Name of participant , Customizable , But it must be globally unique .

  • commitMethod: Specify two stages Confirm Method name , Customizable .

  • rollbackMethod: Specify two stages Cancel Method name , Customizable .

4、TCC Method parameter description :

  • Try: The first parameter type must be BusinessActionContext, The number and type of subsequent parameters can be customized .

  • Confirm: There is only one parameter , Parameter type must be BusinessActionContext, The following is the corresponding parameter name (businessActionContext).

  • Cancel: There is only one parameter , Parameter type must be BusinessActionContext, The following is the corresponding parameter name (businessActionContext).

5、TCC Method return type description :

  • A stage of Try The method can be boolean type , You can also customize the return value .

  • Two stage Confirm and Cancel The return type of the method must be boolean type .

6、 The function of each interface :( Below demo In fact, it is not strictly implemented in this way , It is recommended that the production environment follow the following steps to ensure , Create a resource reservation table to lock resources , You can refer to this article , Native TCC Realization )

You can refer to demo, Native TCC Realization https://github.com/prontera/spring-cloud-rest-tcc/tree/readme-img, There is a resource table , be used for try Stage , Reserve resources .

  • Try: Preliminary operation . Complete all business checks , Reserve necessary business resources .( such as select for update Lock a record , Reserve specified resources )

  • Confirm: Confirm operation . Real business logic ( For example, according to try The data of , Operations such as updating inventory ), No business checks , Use only Try Business resources reserved in the stage . therefore , as long as Try Successful operation , Confirm It's bound to succeed . in addition ,Confirm The operation needs to satisfy idempotence , Ensure that a distributed transaction can and can only succeed once .

  • Cancel: Cancel operation . Release Try Business resources reserved in the stage . alike ,Cancel Operations also need to satisfy idempotence

2.1、 Code emulation

2.1.1、 Business service

1、 Or the basic project above , But it needs a little change , Let's take a sample that specializes in dealing with complex businesses service Class out , Call... Respectively order Service and device service , Look clear in this way , as follows , At call time ,BusinessActionContext Parameters , Let's pass it on null that will do ,seata It will be assigned a value .

 Insert picture description here

@GlobalTransactional(name = "default", rollbackFor = Exception.class, timeoutMills = 60000 * 10)
    @Override
    public R saveWithDetail(SaveWithDetailDTO saveWithDetailDTO) {
    
        log.info("create order begin ... xid: " + RootContext.getXID());

        // 1、order  service 
        workOrderService.simpleSave(null, saveWithDetailDTO);
        
        
        // 2、 The remote invocation  device  service 
        // 2.1、 Insert the question table   And problem association table 
        WorkProblemDTO workProblemDTO = new WorkProblemDTO();
        BeanUtils.copyProperties(saveWithDetailDTO.getSoftwareNotSolveProblemList().get(0), workProblemDTO);
        workProblemDTO.setOrderId(saveWithDetailDTO.getId());
        workProblemApi.insertWithLink(null, workProblemDTO);
        return R.ok();
    }

2、 Abnormal simulation we put device In service
 Insert picture description here

2.1.2、order service

1、 Note that we need to annotate the interface @LocalTCC, Turn on tcc Business , And add notes on the method of the first stage @TwoPhaseBusinessAction, And assign the value of the annotation , Indicates the second stage commit and rollback What are the methods , as well as The return value of the three methods is boolean

@LocalTCC
public interface WorkOrderService extends IService<WorkOrder> {
    


    String simpleSave_BusinessActionContextParameter = "saveWithDetailDTO";

    /** *  Add work order  * * @param saveWithDetailDTO saveWithDetailDTO */
    @TwoPhaseBusinessAction(name = "DubboTccSimpleSaveActionOne", commitMethod = "simpleSaveCommit", rollbackMethod = "simpleSaveRollback")
    boolean simpleSave(BusinessActionContext actionContext,
                       @BusinessActionContextParameter(paramName = simpleSave_BusinessActionContextParameter) SaveWithDetailDTO saveWithDetailDTO);


    /** * Commit boolean. *  This method needs to maintain idempotence and anti suspension  * * @param actionContext the action context * @return the boolean */
    public boolean simpleSaveCommit(BusinessActionContext actionContext);

    /** * Rollback boolean. *  This method needs to maintain idempotence and anti suspension  * * @param actionContext the action context * @return the boolean */
    public boolean simpleSaveRollback(BusinessActionContext actionContext);
}

2、 Implementation class , Because the business I simulated is inserting , When rolling back in the second stage , Compensation is definitely more new id Delete it , But I tried , In the first stage actionContext#map Add parameters , Or modify saveWithDetailDTO Parameters , Neither. , In the second stage, only the initial parameters can be obtained saveWithDetailDTO value , That's what I mentioned earlier , If you have the same business needs as me , Consider putting it in redis Take it inside id, wait .

@Slf4j
@Service
public class WorkOrderServiceImpl implements WorkOrderService {
    

    @DubboReference
    private WorkDeviceApi workDeviceApi;
    @DubboReference
    private WorkProblemApi workProblemApi;

    @Autowired
    private NoticeInfoMapper noticeInfoMapper;

    private static final String INSERT_ORDER_ID_KEY = "INSERT_ORDER_ID_KEY";
    private static final String INSERT_NOTICE_INFO_ID_KEY = "INSERT_NOTICE_INFO_ID_KEY";

    /** * * @param saveWithDetailDTO saveWithDetailDTO * @return */
// @Transactional  Of course, this method can also be added spring  Of  Transactional  annotation 
    @Override
    public boolean simpleSave(BusinessActionContext actionContext, SaveWithDetailDTO saveWithDetailDTO) {
    

        //  attribute  BusinessActionContext  We don't need to inject ,seata It will inject into us 
        String actionName = actionContext.getActionName();
        String xid = actionContext.getXid();
        long branchId = actionContext.getBranchId();

        String title = RandomUtil.randomString(20);
        saveWithDetailDTO.setWorkOrderTitle(title);
        saveWithDetailDTO.setWorkOrderNumber("asd");

        // 1、 Call your own service 
        // 1.1、 Insert job information 
        WorkOrder workOrder = new WorkOrder();
        BeanUtils.copyProperties(saveWithDetailDTO, workOrder);
        this.baseMapper.insert(workOrder);
        saveWithDetailDTO.setId(workOrder.getId());
        //  Even if you make changes in this theory BusinessActionContext Stored data , But you are in the second stage (commit/rollback) You can't get the modified data 
        //  You can only get the data initialized at the beginning , Ken is in the second stage BusinessActionContext object , It's a new instance , Only the initial data 
        //  There is no data to be modified later 
// Map<String, Object> actionContextMap = actionContext.getActionContext();
// actionContextMap.put(INSERT_ORDER_ID_KEY, workOrder.getId());


        // 1.2、 Insert message notification table 
        NoticeInfo noticeInfo = new NoticeInfo();
        noticeInfo.setTitle("new work order 【" + title + "】has publish");
        noticeInfoMapper.insert(noticeInfo);
// actionContextMap.put(INSERT_NOTICE_INFO_ID_KEY, noticeInfo.getId());

        return true;
    }

    @Override
    public boolean simpleSaveCommit(BusinessActionContext actionContext) {
    
        //  Here you can get   At the beginning prepare  Annotate the stage  BusinessActionContextParameter  Value 
        log.info("simpleSave Commit, params : {}", JSONUtil.toJsonStr(actionContext.getActionContext(simpleSave_BusinessActionContextParameter)));

        //todo  If resources are reserved in the first stage , Here you have to submit resources 
        //  Indicates whether it was successful 
        return true;
    }

    @Override
    public boolean simpleSaveRollback(BusinessActionContext actionContext) {
    
        //  Here you can get   At the beginning prepare  Annotate the stage  BusinessActionContextParameter  Value 
        JSONObject saveWithDetailDTOJSONObject = (JSONObject) actionContext.getActionContext(simpleSave_BusinessActionContextParameter);
        log.info("simpleSave Commit , params : {}", JSONUtil.toJsonStr(saveWithDetailDTOJSONObject));

        //  Compensation measures , as follows 

        // 1、 Solve idempotency   Work order form id  It's empty , It indicates that the first step has not been successfully implemented , There is no need to compensate for this step 
        //  Here can be changed from redis In order to get , such  Integer orderId = (Integer) actionContext.getActionContext(INSERT_ORDER_ID_KEY);, It is impossible to obtain the value stored at the end of the first section 
        //  I write it here for the convenience of demonstration 1 了 , Because after every demonstration , Metropolis truncate table
        Integer orderId = 1;
        if (orderId == null) {
    
            return true;
        } else {
    
            //  Delete insert work_order The data table 
            this.baseMapper.deleteById(orderId);
        }

        // 2、 Solve idempotency   Message notification table  id  It's empty , Description not inserted , There is no need to compensate for this step 
        //  Here can be changed from redis In order to get , such  Integer noticeInfoId = (Integer) actionContext.getActionContext(INSERT_NOTICE_INFO_ID_KEY);, It is impossible to obtain the value stored at the end of the first section 
        //  I write it here for the convenience of demonstration 1 了 , Because after every demonstration , Metropolis truncate table
        Integer noticeInfoId = 1;
        if (noticeInfoId == null) {
    
            return true;
        } else {
    
            //  Delete insert notice_Info The data table 
            noticeInfoMapper.deleteById(noticeInfoId);
        }
        return true;
    }
}

2.1.3、device service

1、 Note that we need to annotate the interface @LocalTCC, Turn on tcc Business , And add notes on the method of the first stage @TwoPhaseBusinessAction, And assign the value of the annotation , Indicates the second stage commit and rollback What are the methods , as well as The return value of the three methods is boolean

@LocalTCC
public interface WorkProblemApi {
    

    String insertWithLink_BusinessActionContextParameter = "workProblemDTO";

    /** *  Insertion time , Insert the corresponding work order question table  * * @param workProblemDTO * @return */
    @TwoPhaseBusinessAction(name = "DubboTccInsertWithLinkActionTwo", commitMethod = "insertWithLinkCommit", rollbackMethod = "insertWithLinkRollback")
    boolean insertWithLink(BusinessActionContext actionContext,
                           @BusinessActionContextParameter(paramName = insertWithLink_BusinessActionContextParameter) WorkProblemDTO workProblemDTO);


    /** * Commit boolean. * * @param actionContext the action context * @return the boolean */
    public boolean insertWithLinkCommit(BusinessActionContext actionContext);

    /** * Rollback boolean. * * @param actionContext the action context * @return the boolean */
    public boolean insertWithLinkRollback(BusinessActionContext actionContext);
}

2、 Implementation class , Because the business I simulated is inserting , When rolling back in the second stage , Compensation is definitely more new id Delete it , But I tried , In the first stage actionContext#map Add parameters , Or modify workProblemDTO Parameters , Neither. , In the second stage, only the initial parameters can be obtained workProblemDTO value , That's what I mentioned earlier , If you have the same business needs as me , Consider putting it in redis Take it inside id, wait .

@Slf4j
@DubboService
public class WorkProblemApiImpl implements WorkProblemApi {
    

    @Autowired
    private WorkProblemMapper workProblemMapper;

    @Autowired
    private WorkOrderProblemLinkMapper workOrderProblemLinkMapper;

    private static final String INSERT_PROBLEM_ID_KEY = "INSERT_PROBLEM_ID_KEY";
    private static final String INSERT_ORDER_PROBLEM_LINK_ID_KEY = "INSERT_ORDER_PROBLEM_LINK_ID_KEY";

    @Override
    public boolean insertWithLink(BusinessActionContext actionContext, WorkProblemDTO workProblemDTO) {
    
        WorkProblem workProblem = new WorkProblem();
        BeanUtils.copyProperties(workProblemDTO, workProblem);
        // 1、 Insert the question table 
        int insertProblem = workProblemMapper.insert(workProblem);

        // 2、 Insert the work order problem association table 
        WorkOrderProblemLink workOrderProblemLink = new WorkOrderProblemLink();
        workOrderProblemLink.setOrderId(workProblemDTO.getOrderId());
        workOrderProblemLink.setProblemId(workProblem.getId());
        int insertOrderProblemLink = workOrderProblemLinkMapper.insert(workOrderProblemLink);
        //  Simulate anomalies 
        int i = 1 / 0;

        if (insertProblem > 0 && insertOrderProblemLink > 0) {
    
            return true;
        }
        throw new RuntimeException(" Insertion exception ");
    }

    @Override
    public boolean insertWithLinkCommit(BusinessActionContext actionContext) {
    
        //  Here you can get   At the beginning prepare  Annotate the stage  BusinessActionContextParameter  Value 
        log.info("insertWithLink commit, params : {}", JSONUtil.toJsonStr(actionContext.getActionContext(insertWithLink_BusinessActionContextParameter)));
        
		//todo  If resources are reserved in the first stage , Here you have to submit resources 
        //  Indicates whether it was successful 
        return true;
    }

    @Override
    public boolean insertWithLinkRollback(BusinessActionContext actionContext) {
    
        //  Here you can get   At the beginning prepare  Annotate the stage  BusinessActionContextParameter  Value 
        JSONObject workProblemDTOJSONObject = (JSONObject) actionContext.getActionContext(insertWithLink_BusinessActionContextParameter);
        log.info("insertWithLink Rollback, params : {}", JSONUtil.toJsonStr(workProblemDTOJSONObject));

        //  Compensation measures , as follows 

        // 1、 Solve idempotency   List of questions id  It's empty , It indicates that the first step has not been successfully implemented , There is no need to compensate for this step 
        //  Here can be changed from redis In order to get , such  (Integer) actionContext.getActionContext(INSERT_PROBLEM_ID_KEY);, It is impossible to obtain the value stored at the end of the first section 
        //  I write it here for the convenience of demonstration 1 了 , Because after every demonstration , Metropolis truncate table
        Integer insertProblemId = 1;
        if (insertProblemId == null) {
    
            return true;
        } else {
    
            //  Delete insert work_order The data table 
            this.workProblemMapper.deleteById(insertProblemId);
        }

        // 2、 Solve idempotency   Work order problem association table  id  It's empty , Description not inserted , There is no need to compensate for this step 
        //  Here can be changed from redis In order to get , such  (Integer) actionContext.getActionContext(INSERT_ORDER_PROBLEM_LINK_ID_KEY);  It is impossible to obtain the value stored at the end of the first section 
        //  I write it here for the convenience of demonstration 1 了 , Because after every demonstration , Metropolis truncate table
        Integer insertOrderProblemLinkId = 1;
        if (insertOrderProblemLinkId == null) {
    
            return true;
        } else {
    
            //  Delete insert work_order_problem_link The data table 
            workOrderProblemLinkMapper.deleteById(insertOrderProblemLinkId);
        }
        return true;
    }
}

2.1.4、 Test and analysis results

1、 We are 4 individual mapper When it's all finished , And make a breakpoint where the exception has not yet occurred , as follows
 Insert picture description here

2、4 All the business tables are inserted For your data , At this time, because we use tcc Pattern ,rollback Things need to be handled by ourselves , therefore undo_log There is no data in the table , You can also delete this directly undo_log surface
 Insert picture description here

3、seata Server side 3 A watch , You can see branch_table The branch type in the table has been changed to TCC Pattern , One, two branches , Namely order Service tcc, and device Service tcc, There is another field in the table application-data Is the data you operate .

 Insert picture description here
4、 After releasing the breakpoint , You can see that an exception has occurred , therefore 2 A service (4 individual mapper) We have to roll back

5、order The service log analysis is as follows
 Insert picture description here
 Insert picture description here

6、device The service log analysis is as follows
 Insert picture description here

 Insert picture description here

7、 view the database , Of course, you can also see the self increment of each table id, Whether it has been from 2 Here we go , If from 2 Here we go , Just explain , There was an insertion before , However, it was later rolled back and deleted .
 Insert picture description here

2.2、 How to control exceptions

1、 This part comes from seata-TCC Pattern

2、 stay TCC During the execution of the model , There may also be various exceptions , One of the most common is idle rollback 、 idempotent 、 Hanging, etc . Now let me talk about Seata How to handle these three exceptions

2.2.1、 How to handle empty rollback

1、 What is an empty rollback ?

Empty rollback refers to a distributed transaction , When there is no calling party Try In the case of method ,TM Driving the two-phase rollback calls the participant's Cancel Method .

2、 So how does empty rollback come about ?

After the global transaction is started , participants A After the branch registration is completed, the participant phase will be executed RPC Method , If participants at this time A The machine is down , Network anomalies , Will cause RPC Call failed , That is, participants A The one-stage method was not successfully implemented , But at this time, the global transaction has been started ,Seata It must be advanced to the final state , Participants will be called when the global transaction is rolled back A Of Cancel Method , This causes an empty rollback .

3、 To prevent empty rollback , Then it must be in Cancel Method to identify that this is an empty rollback ,Seata How is it done ?

Seata The way to do this is to add a TCC Transaction control table , Containing transactions XID and BranchID Information , stay Try Insert a record when the method executes , Indicates that a stage has been implemented , perform Cancel Method to read this record , If the record does not exist , explain Try Method not implemented .

2.2.2、 How to deal with idempotent

1、 Idempotent problem refers to TC Repeat the two-stage submission , therefore Confirm/Cancel The interface needs to support idempotent processing , That is, resources will not be repeatedly committed or released .

2、 So how does idempotent problem come into being ?

In the participants A After the second stage , Due to network jitter or downtime , Can cause TC No participants received A Execute the return result of the second stage ,TC The call will be repeated , Until the result of the second stage implementation is successful .

3、Seata How to deal with idempotent problem ?

The same is true in TCC Add a field of record status in the transaction control table status, This field has 3 It's worth , Respectively :

  • tried:1
  • committed:2
  • rollbacked:3

Two stages Confirm/Cancel After method execution , Change the state to committed or rollbacked state . When calling the second stage repeatedly Confirm/Cancel When the method is used , The idempotent problem can be solved by judging the transaction state .

2.2.3、 How to deal with suspension

1、 Suspension refers to the second stage Cancel Method ratio A stage Try The method takes precedence , Because empty rollback is allowed , At the end of the second stage Cancel Method, and then the direct null rollback returns success , At this time, the global transaction has ended , But because of Try Method is then executed , This will create a stage Try The resources reserved by method can never be committed and released .

2、 So how did the suspension come into being ?

In execution participants A A phase of Try When the method is used , Network congestion , because Seata There is a timeout limit for global transactions , perform Try After method timeout ,TM Decide to rollback globally , After the rollback is completed, if at this time RPC The request arrived at the participant A, perform Try Method to reserve resources , This causes suspension .

3、Seata How to deal with suspension ?

stay TCC Fields of transaction control table record status status Add a status in :

  • suspended:4

When performing phase II Cancel When the method is used , If you find that TCC There are relevant records in the transaction control table , Explain the second stage Cancel Method first stage Try Method execution , So insert a status=4 A record of the state , When a stage Try When the method is executed later , Judge status=4 , Then there are two stages Cancel Has been carried out , And back to false To stop a stage Try Method executed successfully .

4、 Parameters can be added to the code useTCCFence = true, Turn on seata Put the hanging

@TwoPhaseBusinessAction(name = "beanName", commitMethod = "commit", rollbackMethod = "rollback", useTCCFence = true)

3、 ... and 、SAGA Pattern

1、 This saga There are so many pattern pits , Mainly, the official documents are too messy , But in fact, the test cases under the source code are still good , There are too few documents , In order to find a situation that can be used in production , I'm really nosing around .

2、 The first point , Official saga The documentation of the pattern is Be sure to see ,SEATA Saga Pattern , After reading it, you can get a general understanding , We all need to know the meaning of the parameters of the state language , Otherwise, it can't be written later .

3、 Then the official code example , It is suggested that the young partner who just started , Be sure to go through it first , I have a bottom

  • Test cases under the source code ,io.seata.saga.engine.StateMachineTests, List almost all the state machine situations

 Insert picture description here

 Insert picture description here

  • There is also an official example code , The project address is seata-samples, Find an example of the project situation you need , Current saga The pattern is as follows
     Insert picture description here

3.1、 Code emulation

1、 According to the above official documents and sample project code , We know ,saga Currently, a state machine based approach is provided , The official language of state machine also provides a visual interface State machine designer presentation address :http://seata.io/saga_designer/index.html

But this online tool , It seems that some old versions are not supported
 Insert picture description here

This is not as detailed as above , It should be the first version  Insert picture description here

 Insert picture description here

3.1.1 Create database

1、saga The schema needs to add some tables to the database of the service initiator , Of course saga There are memory based databases (H2) The pattern of , But the authorities don't recommend that you do that .
 Insert picture description here
2、 As follows
 Insert picture description here
3、 perform sql Script , Because my subsequent demonstration is from order Service initiation , The database used is gxm-300, So I have this sql Where the script is executed , As shown in the figure below, three tables are added .

 Insert picture description here

3.1.2 Business code

1、 It's the same as before , Let's take a sample that specializes in dealing with complex businesses service Class out , Call and... Respectively device service

  • order service ( Use gxm-300 Database work_order Table and notice_info surface )
  • device service ( Use gxm-301 Database work_order_problem_link Table and work_problem surface )

3.1.2.1 order service

1、WorkOrderService Interface class , One is the business method , The other is the compensation method for business failure .
 Insert picture description here

2、WorkOrderServiceImpl Implementation class , One is the business method , The other is the compensation method for business failure .

package cn.gxm.order.service.impl;

import cn.gxm.order.dto.method.service.savewithdetail.SaveWithDetailDTO;
import cn.gxm.order.mapper.NoticeInfoMapper;
import cn.gxm.order.mapper.WorkOrderMapper;
import cn.gxm.order.pojo.NoticeInfo;
import cn.gxm.order.pojo.WorkOrder;
import cn.gxm.order.service.WorkOrderService;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/** * @author GXM * @version 1.0.0 * @Description TODO * @createTime 2022 year 04 month 14 Japan  */
@Slf4j
@Service
public class WorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, WorkOrder> implements WorkOrderService {
    

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private NoticeInfoMapper noticeInfoMapper;

    /** *  test seata * * @param saveWithDetailDTO saveWithDetailDTO * @return */
    @Override
    public SaveWithDetailDTO simpleSave(String businessKey, SaveWithDetailDTO saveWithDetailDTO) {
    

        //  You can see our  workProblemApi  Is here or not spring  Inside 
// System.out.println(applicationContext.getBeanDefinitionCount());
// for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) {
    
// System.out.println(beanDefinitionName);
// }

        String title = RandomUtil.randomString(20);
        saveWithDetailDTO.setWorkOrderTitle(title);
        saveWithDetailDTO.setWorkOrderNumber("asd");

        // 1、 Call your own service 
        // 1.1、 Insert job information 
        WorkOrder workOrder = new WorkOrder();
        BeanUtils.copyProperties(saveWithDetailDTO, workOrder);
        workOrder.setBusinessKey(businessKey);
        this.baseMapper.insert(workOrder);
        saveWithDetailDTO.setId(workOrder.getId());

        // 1.2、 Insert message notification table 
        NoticeInfo noticeInfo = new NoticeInfo();
        noticeInfo.setTitle("new work order 【" + title + "】has publish");
        noticeInfo.setBusinessKey(businessKey);
        noticeInfoMapper.insert(noticeInfo);
        saveWithDetailDTO.setNoticeInfoId(noticeInfo.getId());

        return saveWithDetailDTO;
    }


    @Override
    public boolean compensateCreateOrder(String businessKey) {
    
        log.info("compensateCreateOrder business key : {}", businessKey);
        if (StrUtil.isNotBlank(businessKey)) {
    
            // 1、 according to  business key  To operate   compensate   Because my business here is to insert , therefore , I directly based on business key  Delete relevant data 

            // 1.1、 Delete  work_order  Table data 
            LambdaQueryWrapper<WorkOrder> workOrderQueryWrapper = new LambdaQueryWrapper<>();
            workOrderQueryWrapper.eq(WorkOrder::getBusinessKey, businessKey);
            this.baseMapper.delete(workOrderQueryWrapper);

            // 1.2、 Delete  notice_info  Table data 
            LambdaQueryWrapper<NoticeInfo> noticeInfoQueryWrapper = new LambdaQueryWrapper<>();
            noticeInfoQueryWrapper.eq(NoticeInfo::getBusinessKey, businessKey);
            noticeInfoMapper.delete(noticeInfoQueryWrapper);
        }

        return true;
    }
}

3.1.2.1 device service

1、WorkProblemApi Interface class , One is the business method , The other is the compensation method for business failure .
 Insert picture description here
2、WorkProblemApiImpl Implementation class , One is the business method , The other is the compensation method for business failure .

package cn.gxm.device.client;

import cn.gxm.device.api.WorkProblemApi;
import cn.gxm.device.dto.common.WorkProblemDTO;
import cn.gxm.device.mapper.WorkOrderProblemLinkMapper;
import cn.gxm.device.mapper.WorkProblemMapper;
import cn.gxm.device.pojo.WorkOrderProblemLink;
import cn.gxm.device.pojo.WorkProblem;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;

/** * @author GXM * @version 1.0.0 * @Description TODO * @createTime 2022 year 05 month 19 Japan  */
@Slf4j
@DubboService
public class WorkProblemApiImpl implements WorkProblemApi {
    

    @Autowired
    private WorkProblemMapper workProblemMapper;

    @Autowired
    private WorkOrderProblemLinkMapper workOrderProblemLinkMapper;


    @Override
    public WorkProblemDTO insertWithLink(String businessKey, WorkProblemDTO workProblemDTO) {
    
        WorkProblem workProblem = new WorkProblem();
        BeanUtils.copyProperties(workProblemDTO, workProblem);
        workProblem.setBusinessKey(businessKey);
        // 1、 Insert the question table 
        workProblemMapper.insert(workProblem);
        workProblemDTO.setId(workProblem.getId());

        // 2、 Insert the work order problem association table 
        WorkOrderProblemLink workOrderProblemLink = new WorkOrderProblemLink();
        workOrderProblemLink.setOrderId(workProblemDTO.getOrderId());
        workOrderProblemLink.setProblemId(workProblem.getId());
        workOrderProblemLink.setBusinessKey(businessKey);
        workOrderProblemLinkMapper.insert(workOrderProblemLink);
        workProblemDTO.setOrderProblemLinkId(workOrderProblemLink.getId());

        if (workProblemDTO.getProblem().equals("exception")) {
    
            int i = 1 / 0;
        }
        return workProblemDTO;
    }

    /** *  Business compensation  * * @param businessKey seata Business key * @return */
    @Override
    public boolean compensateInsertWithLink(String businessKey) {
    
        log.info("compensateInsertWithLink business key : {}", businessKey);
        if (StrUtil.isNotBlank(businessKey)) {
    
            // 1、 according to  business key  To operate   compensate   Because my business here is to insert , therefore , I directly based on business key  Delete relevant data 

            // 1.1、 Delete  work_problem  Table data 
            LambdaQueryWrapper<WorkProblem> workProblemQueryWrapper = new LambdaQueryWrapper<>();
            workProblemQueryWrapper.eq(WorkProblem::getBusinessKey, businessKey);
            workProblemMapper.delete(workProblemQueryWrapper);

            // 1.2、 Delete  notice_info  Table data 
            LambdaQueryWrapper<WorkOrderProblemLink> workOrderProblemLinkQueryWrapper = new LambdaQueryWrapper<>();
            workOrderProblemLinkQueryWrapper.eq(WorkOrderProblemLink::getBusinessKey, businessKey);
            workOrderProblemLinkMapper.delete(workOrderProblemLinkQueryWrapper);
        }
        return true;
    }
}

3.1.2.3 Comprehensive complex business

1、 I wrote this type directly order Under service
 Insert picture description here

2、 Implementation class

package cn.gxm.order.service.impl;

import cn.gxm.common.resp.R;
import cn.gxm.order.dto.method.service.savewithdetail.SaveWithDetailDTO;
import cn.gxm.order.service.BusinessService;
import io.seata.core.context.RootContext;
import io.seata.saga.engine.StateMachineEngine;
import io.seata.saga.statelang.domain.StateMachineInstance;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

/** * @author GXM * @version 1.0.0 * @Description TODO * @createTime 2022 year 06 month 10 Japan  */
@Service
@Slf4j
public class BusinessServiceImpl implements BusinessService {
    


    @Autowired
    private StateMachineEngine stateMachineEngine;

    /** *  The business logic is  * // 1、order  service  * workOrderService.simpleSave(saveWithDetailDTO); * // 2、 The remote invocation  device  service  * // 2.1、 Insert the question table   And problem association table  * WorkProblemDTO workProblemDTO = new WorkProblemDTO(); * BeanUtils.copyProperties(saveWithDetailDTO.getSoftwareNotSolveProblemList().get(0), workProblemDTO); * workProblemDTO.setOrderId(saveWithDetailDTO.getId()); * workProblemApi.insertWithLink(workProblemDTO); * * @param saveWithDetailDTO * @return */
    @Override
    public R saveWithDetailInStatemachineEngine(SaveWithDetailDTO saveWithDetailDTO) {
    
        log.info("create order begin ... xid: " + RootContext.getXID());

        String businessKey = String.valueOf(System.currentTimeMillis());

        // 1、 The following state machine describes the above process 
        Map<String, Object> paramMap = new HashMap<>(1);
        // 1.1、 This  `saveWithDetailDTOKey`, It can be in the state machine json Through the file  $.[saveWithDetailDTOKey]  obtain , And add `.` Can represent specific data 
        paramMap.put("saveWithDetailDTOKey", saveWithDetailDTO);
        paramMap.put("businessKey", businessKey);

        String stateMachineName = "createOrderAndProblemStateMachine";
        // 1.2、 Execute state machine json file , The specific business processes are json In file .
// StateMachineInstance instance = stateMachineEngine.start(stateMachineName, null, paramMap);
        StateMachineInstance instance = stateMachineEngine.startWithBusinessKey(stateMachineName, null, businessKey, paramMap);
        log.info(" The most general implementation result : {}; xid : {}; businessKey: {}; compensationStatus {}",
                instance.getStatus(), instance.getId(), instance.getBusinessKey(), instance.getCompensationStatus());

        return R.ok();
    }
}

3.1.3 Project configuration saga Pattern

1、 First, we need to write a state language file of our business , To show your business situation , And rollback compensation , You can use the official online tool mentioned above

I won't elaborate on some grammar , Pull the official document to the bottom , That is to say, these semantic , If you don't understand, you can go and have a look

 Insert picture description here

{
    
  "Name": "createOrderAndProblemStateMachine",  #  The name of the state machine , Follow up use should be based on this unique to find 
  "Comment": " Create a work order state machine ", #  brief introduction 
  "StartState": "CreateOrder",  #  The initial state 
  "Version": "0.0.1", #  current version 
  "States": {
      #  Status list 
    "CreateOrder": {
     #  be known as CreateOrder Status list of 
      "Type": "ServiceTask", #  type 
      "ServiceName": "workOrderServiceImpl", #  Corresponding services bean name ,saga Will arrive spring Of bean Find this name in the container bean.
      "ServiceMethod": "simpleSave",    # workOrderServiceImpl The name is simpleSave Methods 
      "Next": "ChoiceState",  #  Next state 
      "CompensateState": "CompensateCreateOrder",  #  The name of the compensation status of the current service ( There are definitions below )
      "ParameterTypes": [  # workOrderServiceImpl#simpleSave  Method parameter type ( Don't write , But if there are generics , Just write , Official note )
        "java.lang.String",
        "cn.gxm.order.dto.method.service.savewithdetail.SaveWithDetailDTO"
      ],
      "Input": [    # workOrderServiceImpl#simpleSave  Parameter value of the method of 
        "$.[businessKey]",
        "$.[saveWithDetailDTOKey]"
      ],
      "Output": {
     # workOrderServiceImpl#simpleSave  The return value of the method , Stored in the context of the state machine ,key yes  simpleSaveResult, Value is the entire return result of the method 
        "simpleSaveResult": "$.#root"
      },
      "Status": {
      #  At present CreateOrder The state of   Service execution state mapping , The framework defines three states ,SU  success 、FA  Failure 、UN  Unknown ,  We need to map the state of service execution to these three states , Help the framework judge the consistency of the whole transaction , It's a map structure ,key Is a conditional expression , Generally, it takes the return value of the service or the exception thrown for judgment , The default is SpringEL The expression determines the service return parameter , belt $Exception{
     The beginning indicates to judge the type of exception .value When the conditional expression is true, the service execution state is mapped to this value 
      #  One thing to say here is that the abnormal judgment should be put in front , Otherwise, if you put the judgment based on the return value in front , Once something goes wrong , Then the method has no return value , So this one #root.id Is the wrong grammar , because #root yes null, Of course, the most general state is "UN"
        "$Exception{java.lang.Throwable}": "UN",
        "#root.id != null && #root.noticeInfoId != null": "SU",
        "#root.id == null || #root.noticeInfoId == null": "FA"
      }
    },
    "ChoiceState": {
    
      "Type": "Choice",
      "Choices": [
        {
    
           #  Only CreateOrder Phase creation succeeded (work_order Yes id 了 , also notice_info Also have id, Note the insertion was successful ), Before taking the next step 
          "Expression": "[simpleSaveResult].id != null && [simpleSaveResult].noticeInfoId != null",
          "Next": "CreateProblem"
        }
      ],
      "Default": "Fail" #  Otherwise, default to failure ( Failure status is defined below )
    },
    "CreateProblem": {
    
      "Type": "ServiceTask",
      "ServiceName": "workProblemApi",
      "ServiceMethod": "insertWithLink",
      "CompensateState": "CompensateCreateProblem",
      "Input": [
        "$.[businessKey]",
        {
    
          "problem": "$.[saveWithDetailDTOKey].softwareNotSolveProblemList[0].problem",
          "type": "$.[saveWithDetailDTOKey].softwareNotSolveProblemList[0].type",
          "orderId": "$.[simpleSaveResult].id"
        }
      ],
      "Output": {
    
        "insertWithLinkResult": "$.#root"
      },
      "Status": {
    
        "$Exception{java.lang.Throwable}": "UN",
        "#root.id != null && #root.orderProblemLinkId != null": "SU",
        "#root.id == null || #root.orderProblemLinkId == null": "FA"
      },
      "Catch": [
        {
    
          "Exceptions": [
            "java.lang.Throwable"
          ],
          "Next": "CompensationTrigger"
        }
      ],
      "Next": "Succeed"
    },
    "CompensateCreateOrder": {
      # CreateOrder Compensation measures 
      "Type": "ServiceTask",
      "ServiceName": "workOrderServiceImpl", #  need  workOrderServiceImpl  Of bean
      "ServiceMethod": "compensateCreateOrder", #  call  workOrderServiceImpl#compensateCreateOrder Method inside 
      "Input": [
        "$.[businessKey]"
      ]
    },
    "CompensateCreateProblem": {
    
      "Type": "ServiceTask",
      "ServiceName": "workProblemApi",
      "ServiceMethod": "compensateInsertWithLink",
      "Input": [
        "$.[businessKey]"
      ]
    },
    "CompensationTrigger": {
    
      "Type": "CompensationTrigger",
      "Next": "Fail"
    },
    "Succeed": {
    
      "Type": "Succeed"
    },
    "Fail": {
    
      "Type": "Fail",
      "ErrorCode": "CREATE_FAILED",
      "Message": "create order failed"
    }
  }
}

2、 To configure saga Configuration information of the state machine , Like your state machine json What is the name of the document , Where is the , And injected into the spring In the container , Official examples , Everyone can see that it is xml, I'll change it here to springboot Configuration mode injection is ok , You can choose a way at will
 Insert picture description here
3、 I'll change it here to springboot Of @Configuration Injection mode , The content in it corresponds to the above xml The contents of the document ,

There's one thing to pay attention to , When the state machine is executing , Will go to spring Of bean Corresponding bean, We use dubbo The way to inject , Will not be in spring Inside the container of , So there will be no corresponding bean, But in fact, we use @DubboReference It's available , therefore , Here we manually inject .
 Insert picture description here

 Insert picture description here

package cn.gxm.order.config;

import cn.gxm.device.api.WorkProblemApi;
import cn.gxm.order.service.impl.WorkOrderServiceImpl;
import com.zaxxer.hikari.HikariDataSource;
import io.seata.saga.engine.StateMachineEngine;
import io.seata.saga.engine.config.DbStateMachineConfig;
import io.seata.saga.engine.impl.ProcessCtrlStateMachineEngine;
import io.seata.saga.rm.StateMachineEngineHolder;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import javax.sql.DataSource;
import java.io.File;

/** * @author GXM * @version 1.0.0 * @Description seata saga Mode configuration information  * @createTime 2022 year 06 month 13 Japan  */
@Configuration
public class SeataSagaConfig {
    


    /** * bean  The default is the method name , It's OK not to write here , But in case of explicit semantics   It's better to write  * * @return */
    @Bean(name = "seataSagaDataSource")
    public DataSource seataSagaDataSource() {
    
        HikariDataSource dataSource = new HikariDataSource();
        //  The database address is  seata_state_inst 、seata_state_machine_def、seata_state_machine_inst  Address of three tables , Generally, it is in the database of the business initiator 
        dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/gxm-300?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useSSL=true&characterEncoding=UTF-8");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }

    @Bean(name = "dbStateMachineConfig")
    public DbStateMachineConfig dbStateMachineConfig(@Qualifier("seataSagaDataSource") DataSource seataSagaDataSource) {
    
        DbStateMachineConfig dbStateMachineConfig = new DbStateMachineConfig();
        dbStateMachineConfig.setDataSource(seataSagaDataSource);

        ClassPathResource resource = new ClassPathResource("statelang" + File.separator + "create_order_and_problem.json");
        dbStateMachineConfig.setResources(new Resource[]{
    resource});
        dbStateMachineConfig.setEnableAsync(true);
        //  Execute thread ( This official document is strange , The types do not match ......)  Thread pool used in event driven execution ,  If all state machines are executed synchronously and there is no circular task, it is unnecessary 
// dbStateMachineConfig.setThreadPoolExecutor();
        dbStateMachineConfig.setApplicationId("test_saga");
        dbStateMachineConfig.setTxServiceGroup("my_test_tx_group");

        return dbStateMachineConfig;
    }

    /** * saga  State machine   example  */
    @Bean(name = "stateMachineEngine")
    public StateMachineEngine stateMachineEngine(@Qualifier("dbStateMachineConfig") DbStateMachineConfig dbStateMachineConfig) {
    
        ProcessCtrlStateMachineEngine processCtrlStateMachineEngine = new ProcessCtrlStateMachineEngine();
        processCtrlStateMachineEngine.setStateMachineConfig(dbStateMachineConfig);
        return processCtrlStateMachineEngine;
    }


    /** * Seata Server This is required for transaction recovery Holder Get stateMachineEngine example  * * @param stateMachineEngine * @return */
    @Bean
    public StateMachineEngineHolder stateMachineEngineHolder(@Qualifier("stateMachineEngine") StateMachineEngine stateMachineEngine) {
    
        StateMachineEngineHolder stateMachineEngineHolder = new StateMachineEngineHolder();
        stateMachineEngineHolder.setStateMachineEngine(stateMachineEngine);
        return stateMachineEngineHolder;
    }


    @DubboReference
    private WorkProblemApi workProblemApi;

    /** *  because 2.x Version of  dubbo  Use   annotation  @DubboReference  when , Will not inject spring  in (@DubboReference  Not at all  Spring Defined  Bean, So it doesn't generate  BeanDefinition , That is, I won't take the initiative  createBean , Can only be triggered during attribute injection ), *  and saga The state machine is reading  *  The time is from spring  Get other services in bean, So here's a manual injection  *  Specific analysis can see  https://heapdump.cn/article/3610812 * @return */
    @Bean(name = "workProblemApi")
    public WorkProblemApi workProblemApi() {
    
        return workProblemApi;
    }
}

3.1.4、 explain ( important )

1、 There is a problem , It needs to be explained , Is the parameter businessKey, Each time we start a business, we get the current timestamp as businessKey, Pass it into the business logic , This is for , Subsequent compensation , Know how to compensate , for instance , Our current business , If you fail , We must find the corresponding 4 Tabular 4 Data , And then delete , We just need to put the generated primary key id, Put it into the global object of the state machine for flow , that will do , But there's a situation , Once an exception occurs in a certain state , Then there is no data returned in the state machine , Then there is no way to id Pass in the next step , What about the compensation business in the back . therefore , There are two ways

2、 The first one is , This article does this , Business key Pass in , And the corresponding four tables , You need one businessKey Field , When the corresponding business is modified , hold businessKey Fill it up , When making compensation , Directly according to the business key To operate .

3、 The second kind , Or incoming business key, But how to add business in the corresponding table key Field , But save to redis Among such third parties , For example, the current business , When inserting the database , In an hmap,key Business key,filed Is the corresponding table name , Values are generated id, Then when it comes to compensation , According to business key from redis To take to .

3.2、 Situation test

3.2.1、 Normal condition

3.2.1.1、 State machine object analysis

1、 Let's test the cases without abnormal conditions , In the business service in , Set a breakpoint , View the data results of execution
 Insert picture description here
2、 The preliminary results are as follows :
 Insert picture description here
3、 Pass in the parameter
 Insert picture description here
4、 Result parameters
 Insert picture description here

5、 The most general implementation result
 Insert picture description here
6、 State machine objects
 Insert picture description here
7、 The first state machine
 Insert picture description here

8、 The second state machine
 Insert picture description here

3.2.1.2、 Console log

1、 Execute first CreateOrder State machine , That is to insert order Two tables of service
 Insert picture description here

2、 Re execution CreateProblem State machine , That is to insert device Two tables of service

 Insert picture description here

3、 The total results are as follows , We focus on instance.getStatus() and instance.getCompensationStatus() If there is any problem .

 Insert picture description here

3.2.1.3、 Database data ( Later, I have time to fill in the meaning of this part of the table )

1、order Under service gxm-300 Database situation , Of course, business table notice_info and work_order The data is certain , I will not put the picture
 Insert picture description here

2、device Under service gxm-301 Database situation , Of course, business table work_order_problem_link and work_problem The data is certain , I will not put the picture
 Insert picture description here
3、seata Three tables of data on the server

 Insert picture description here

3.2.2、 Abnormal situation

1、 Before testing this situation , Clear the table data

truncate table `gxm-300`.notice_info;
truncate table `gxm-300`.undo_log;
truncate table `gxm-300`.work_order;
truncate table `gxm-300`.seata_state_inst;
truncate table `gxm-300`.seata_state_machine_def;
truncate table `gxm-300`.seata_state_machine_inst;



truncate table `gxm-301`.undo_log;
truncate table `gxm-301`.work_order_problem_link;
truncate table `gxm-301`.work_problem;


truncate table `seata`.branch_table;
truncate table `seata`.global_table;
truncate table `seata`.lock_table;

3.2.2.1、 State machine object analysis

1、 We are device The server threw an exception
 Insert picture description here
2、 Then there was the previous log Place a breakpoint
 Insert picture description here
3、 State machine objects
 Insert picture description here
4、 Let's talk about this state , This state is normal ( With compensation ), See the official instructions
 Insert picture description here

 Insert picture description here

5、 The list of state machines is 4 individual .
 Insert picture description here

3.2.2.2、 Console log

1、 Execute first CreateOrder State machine , That is to insert order Two tables of service , No problem , Because at this time, the business is still normal .
 Insert picture description here

2、 Re execution CreateProblem State machine , That is to insert device Two tables of service , Then an error java.lang.ArithmeticException: / by zero
 Insert picture description here
3、order Service received device Error messages for
 Insert picture description here
4、 Start the compensation state
 Insert picture description here
5、 First compensate device service , Because it finally executes ( This picture should be on page 4 In the middle of Zhang , After execution , You can see State[CompensateCreateProblem] finish with status[SU])

 Insert picture description here
6、 Recompense order service

 Insert picture description here
7、order Compensation is also successful , Total results

The most general implementation result : UN; xid : 192.168.172.232:8091:279996470823649280; businessKey: 1655191560707; compensationStatus SU

 Insert picture description here

3.2.2.3、 Database data ( Later, I have time to fill in the meaning of this part of the table )

1、order Under service gxm-300 Database situation , Of course, business table notice_info and work_order The data is Definitely not , Because it rolled back , I will not put the picture

 Insert picture description here
2、device Under service gxm-301 Database situation , Of course, business table work_order_problem_link and work_problem The data is Definitely not , Because it rolled back , I will not put the picture
 Insert picture description here

3、seata Three tables of data on the server

 Insert picture description here

3.2.3、 Additional explanation

1、 According to the state language we wrote earlier json The file knows , Compensation trigger CompensationTrigger, Is in CreateProblem It's triggered when
 Insert picture description here
2、 That's for the starting state CreateOrder Come on , It also has 2 A local mapper, And it doesn't set a compensation trigger , Once directly in CreateOrder What about failure , So there are two ways ,

  • The first way ,CreateOrder Phase failure , Also directly trigger the compensation point , In this way, it is also implemented directly CompensateCreateOrder nothing more , Because it is compensated by flashbacks , It is the first .
  • The second way , Do not set it to trigger the compensation point , Use it directly spring Rollback the transaction of it , Because it is a local project 2 individual mapper.
     Insert picture description here
    3、 This means that the second method is set directly spring Transaction rollback , as follows
     Insert picture description here

3.3、 Other questions

1、 Note that once an exception occurs in the intermediate state , Then it's hard for you to get the result of this state
 Insert picture description here
3、 According to the first 2 spot , The same can be , We're setting up ServiceTask When , It is also necessary to put abnormal judgment in the first place
 Insert picture description here

Four 、XA Pattern

4.1、 Instructions

1、 Actually XA and AT almost , I mean, the code is almost , So there are not many changes , The main point is the database support you use XA, such as MySQL It's OK , The main point is to open the mode , The default is AT Pattern ( Of course, this parameter seata.data-source-proxy-mode yes 1.4.0 Start offering , Previous versions can only be switched through code modification data source agent , It is said that )
 Insert picture description here

2、 First, we need to modify the proxy data source , If you're using seata-starer, And the version seata Version of ≥1.4.0 You can directly use annotations to replace , Here's the picture ,

Before that AT Patterns in , Not configured , Because seata-starer rely on , Built in GlobalTransactionScanner Automatic initialization function , The default is AT Pattern , So there's no need to configure

 Insert picture description here

3、 But if your version doesn't ≥ 1.4.0, Then you can only use code to switch , Of course, you can choose to update directly (seata to update , Or update it separately , It's said later that )

@Bean("dataSource")
    public DataSource dataSource(DruidDataSource druidDataSource) {
    
        // DataSourceProxy for AT mode
        // return new DataSourceProxy(druidDataSource);

        // DataSourceProxyXA for XA mode
        return new DataSourceProxyXA(druidDataSource);
    }

4、 because XA Mode does not use undo_log surface , So we can delete it directly , Last gxm-300 and gxm-301 as follows
 Insert picture description here

5、 Because I'm using spring-cloud-starter-alibaba-seata rely on , Inside seata Version or 1.3.0 edition , Switch directly without using that annotation AT and XA Pattern , If I use code change , You have to go from the data source to mapper, Change it all over again , There's really some trouble , therefore , We can manually lift seata Version of , Of course, the official website also has suggestions , You can prompt the version in the following way , So I'm here hold order Service and device Service seata Manually lift to 1.4.0 edition .
 Insert picture description here
 Insert picture description here

4.2、 Code changes

1、 stay device Service and order Add data source agent configuration to the service ( Use comments or code , Look at your version or you want to use that )
 Insert picture description here

2、 Others are like AT There is no difference in the mode
 Insert picture description here

3、 If the project has no performance requirements, I suggest using XA Pattern , because , It is strong consistency , and AT Pattern is the most general consistency . Explanation , Look at section five , How to choose four modes .

4.3、 Normal test ( Reference resources AT Pattern )

Omit

4.4、 Abnormal test ( Reference resources AT Pattern )

Omit

4.5、 test seata When rolling back , After the mirrored data is modified by other transactions , Unable to rollback successful cases ( Reference resources AT Pattern )

1、 We're still with AT Model as , Add an interface , Modify the inserted data

 Insert picture description here

2、 And in device Service hibernation

 Insert picture description here

3、 Remember to modify the global transaction time and the timeout time of the remote calling component ,AT Pattern has , No more talk here

4、 stay device service Sleep time , We call to change the interface , You will find that it has been blocking , Wait until the plug-in interface is over , It also returns , And look at the console data , It is found that the data has not been modified , But isn't the log insert statement executed first . That's the sum of AT The difference between the modes .XA as follows

1、 because XA Data will not be submitted in the first stage , Will lock that resource to the second stage ( You go to the database during its sleep , You can't see the inserted data ), After the completion of the first phase , Call to modify the interface , yes Can't find That data .
 Insert picture description here 2、 and AT The pattern is submitted directly in the first stage , So you can find that data , Subsequent failure rollback is based on undo_log Mirror data to rollback , So AT Pattern is the most general consistency , and XA Patterns are strongly consistent .

 Insert picture description here

5、 ... and 、 How to choose four modes ( It is strongly recommended to look at )

1、 The advantages and disadvantages of the four modes and what we need to deal with , This article all says Distributed transactions ——Seata、XA、TCC、AT、SAGA Pattern

6、 ... and 、 Problems encountered

6.1、Cannot construct instance of java.time.LocalDateTime

1、 Many people have encountered this problem ,github Of issues It is also mentioned above , The main reason is seata When rolling back , be used undo_log Image data , By default, the mirrored data is fastjson Serialized , Then if your business table has a time field , And is datetime type , that seata When rolling back such data , Will be affected . such as , My current business table notice_info There is this time field , Once the rollback of this business is involved , Want to go undo_log Find the front and back mirrored data of the previous table in the table of , It will fail in deserialization .

 Insert picture description here
2、 This is the content of the image , You can see that there is indeed this time field .
 Insert picture description here
3、 When this problem arises , At the global transaction initiator , That is to use @GlobalTransactional Annotated Services Unlimited error reporting , Constantly reporting errors , You can see the picture below , I turned that service off , Otherwise, keep refreshing that error .
 Insert picture description here

4、 Solution , Finally, I used to reduce MySQL Version to 8.0.20

6.2、io.seata.core.exception.RmTransactionException: Response[ TransactionException[branch register request failed. xid=xx, msg=Data truncation: Data too

1、 A screenshot of the problem is shown below
 Insert picture description here
2、 But according to the log above, you can't see anything , It's just that the data is big , After searching online , You will find that this is because lock_table Table when inserting data , The field is too long .

3、 So there is a problem with the field of that table , Don't make random changes based on the Internet , Look at the server log , Because the client did not say the field of that table ,seata The server logs are as follows , But it doesn't seem to say that table , Just say PK Field , therefore , If you know something seata Streamline the operation process , I knew it was lock_table Tabular pk Field
 Insert picture description here
4、 therefore , So let's revise that lock_table Tabular pk Field length is enough .
 Insert picture description here

6.3、saga State machine not found dubbo Of bean

1、 We go through @BubboReference Yes. , But when the state machine executes , Can't find
 Insert picture description here
2、 The reason is because 2.x Version of dubbo Use annotation @DubboReference when , Will not inject spring in (@DubboReference Not at all Spring Defined Bean, So it doesn't generate BeanDefinition , That is, I won't take the initiative createBean , Can only be triggered during attribute injection ), and saga The state machine is reading The time is from spring Get other services in bean, So here's a manual injection Specific analysis can see https://heapdump.cn/article/3610812

3、 The solution is to manually inject into spring Of bean In the container .
 Insert picture description here

6.4、XA Appears in mode java.lang.NoSuchMethodException: com.mysql.cj.conf.PropertySet.getBooleanReadableProperty(java.lang.String)

4、 The reason is that seata The default is DruidDataSource Database connection pool , and DruidDataSource Inside util In bag MySqlUtils Class createXAConnection Method , Will use MySQL Driven getBooleanReadableProperty Method , But the higher version MySQL There is no such method in the driver , So wrong reporting , I will directly lower here MySQL The driver version is ok , take mysql The drive package version is switched to 8.0.11, In this version ,getBooleanReadableProperty(String) Methods still exist .

This problem ,github It is also mentioned above ,

 Insert picture description here

 Insert picture description here
 Insert picture description here

原网站

版权声明
本文为[Clang clang]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/188/202207070843570417.html