Seata distributed transaction failure notification

Time:2022-5-7

1、 Background

In our useSeataAs a distributed transaction, sometimes our distributed transaction can not succeed every time, and we need to notify these failed distributed transactions. This article briefly records how to implement notification.

2、 Function realization

  1. The email notification is simulated here, but the email is not really sent, but a log is simply recorded.

3、 Precautions

1. Suppose that our distributed transaction rollback fails inATIn the mode, the data recorded in the table will be locked. You need to get this record laterGlobal lockOperation will fail.

give an example:

It is assumed that the data recorded in the following data sheet exists

account number amount of money
zhangsan 100

zhangsanThis record participates in distributed transactions.

  1. In the distributed transaction, change the amount of Zhangsan to 90
  2. Another colleague directly operated the database and modified Zhangsan’s record to 80 (at this time, the modification can be successful, because Seata is submitted in two phases. After the first phase, the local lock of the record will be released, and the global lock of the record will not be released.)
  3. Next, continue other distributed transaction operations, but an exception occurs. At this time, the distributed transaction will fail to roll back, because the amount of Zhangsan has changed to 80, which is not corresponding to the previous one.
  4. If the distributed transaction operation is performed on Zhangsan again or the operation that needs to obtain the global lock will fail.

4、 Implementation steps

1. Write a class to implement the failurehandler interface

FailureHandlerThe full class name of is:io.seata.tm.api.FailureHandler, it has a default implementationDefaultFailureHandlerImpl, here we write a class to inherit this class.

package com.huan.seata.handler;

import io.seata.tm.api.DefaultFailureHandlerImpl;
import io.seata.tm.api.GlobalTransaction;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 *Seata distributed transaction failure handling
 *
 * @author huan. Fu 2021 / 10 / 8 - 1:51 PM
 */
@Component("failureHandler")
@Slf4j
public class EmailSeataFailureHandler extends DefaultFailureHandlerImpl {
    
    @Override
    public void onBeginFailure(GlobalTransaction tx, Throwable cause) {
        super.onBeginFailure(tx, cause);
        log. Warn ("email notification: distributed transaction has an exception: [onbeginfailure], XID: [{}]", tx.getxid());
    }
    
    @Override
    public void onCommitFailure(GlobalTransaction tx, Throwable cause) {
        super.onCommitFailure(tx, cause);
        log. Warn ("email notification: distributed transaction has an exception: [oncommitfailure], XID: [{}], tx.getxid());
    }
    
    @Override
    public void onRollbackFailure(GlobalTransaction tx, Throwable originalException) {
        super.onRollbackFailure(tx, originalException);
        log. Warn ("email notification: distributed transaction has an exception: [onrollbackfailure], XID: [{}], tx.getxid());
    }
    
    @Override
    public void onRollbackRetrying(GlobalTransaction tx, Throwable originalException) {
        super.onRollbackRetrying(tx, originalException);
        log. Warn ("email notification: distributed transaction has exception: [onrollbackretrying], XID: [{}], tx.getxid());
    }
}

2. The value of the beanname added to spring

Found during the testFailureHandlerBeanname added to spring must befailureHandlerOtherwise, an error is reported, which is determined by the following code in the automatic configuration of Seata.

BEAN_NAME_FAILURE_HANDLERThe value of the constant isfailureHandler

/**
 *Source code
 * The type Seata auto configuration
 */
@ConditionalOnProperty(prefix = SEATA_PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
public class SeataAutoConfiguration {
    private static final Logger LOGGER = LoggerFactory.getLogger(SeataAutoConfiguration.class);

    @Bean(BEAN_NAME_FAILURE_HANDLER)
    @ConditionalOnMissingBean(FailureHandler.class)
    public FailureHandler failureHandler() {
        return new DefaultFailureHandlerImpl();
    }

    @Bean
    @DependsOn({BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER, BEAN_NAME_FAILURE_HANDLER})
    @ConditionalOnMissingBean(GlobalTransactionScanner.class)
    public GlobalTransactionScanner globalTransactionScanner(SeataProperties seataProperties, FailureHandler failureHandler) {
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("Automatically configure Seata");
        }
        return new GlobalTransactionScanner(seataProperties.getApplicationId(), seataProperties.getTxServiceGroup(), failureHandler);
    }

}

5、 Testing

visitcurl -X GET http://localhost:50027/createOrder\?accountId\=1\&amount\=10\&hasException\=true, then manually break the point, modify the database data to make the data inconsistent, and then the code runs out of exception. Rollback here fails because undo_ The data in the log table is not available.

Seata distributed transaction failure notification

6、 Complete code

https://gitee.com/huan1993/spring-cloud-parent/tree/master/seata/seata-springboot-failure-handler