美文网首页
seata AT模式实践

seata AT模式实践

作者: 甯缺毋滥 | 来源:发表于2021-01-12 09:24 被阅读0次

环境版本:

jdk1.8
maven 3.6
spring-cloud Hoxton.RELEASE
spring-cloud-alibaba 2.1.1.RELEASE
seata-service 0.9
dynamic-datasource-spring-boot-starter 3.0以上

一.简单调用

1.1.在全局事务的开始方法加入 @GlobalTransactional 即可

模拟一个报错的代码,在第五行断点,看看各个表的情况

@PostMapping(value = "test-seata")
@GlobalTransactional(rollbackFor = Exception.class)
public void testSeata(@RequestBody @ApiParam HsSaveReq hsSave) throws InterruptedException {
    hsService.saveHs(hsSave);
    int i = 1 / 0;
    log.info("成功");
}

1.2.报错前

分支事务表
全局事务表
全局锁
undo_log

undo_log中的rollback_info里面存储了对象的前后数据信息,实践的时候可以看看

1.3.报错或者超时(默认6s)的时候

1.正常回滚,并删除3张锁表的当前事务信息,业务数据回滚(利用undo_log存储的rollback_info进行反向sql),且异步执行
2.脏数据回滚不成功,需要人工干预
3.undo_log日志表删除

1.4.客户端打印信息

控制台信息

上面的信息为业务sql插入
1.打印出了全局事务id(xid),分支id(branchId),seata的模式(branchType),资源编码(resourceId)
2.undo_log日志删除刷新
3.AT模式的二阶段提交完成
4.回滚状态:已回滚

二.多服务调用

需要增加一个 拦截器来传递上下服务之间的全局事务id (xid)

2.1.实现WebMvcConfigurer ,重写 addInterceptors

@Configuration
public class SeataHandlerInterceptorConfiguration implements WebMvcConfigurer {
    public SeataHandlerInterceptorConfiguration() {
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册HandlerInterceptor,拦截所有请求
        registry.addInterceptor(new SeataHandlerInterceptor()).addPathPatterns(new String[] { "/**" });
    }
}

2.2.实现 ClientHttpRequestInterceptor

public class SeataRestTemplateInterceptor implements ClientHttpRequestInterceptor {
    public SeataRestTemplateInterceptor() {
    }

    @Override
    public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes,
            ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
        HttpRequestWrapper requestWrapper = new HttpRequestWrapper(httpRequest);
        String xid = RootContext.getXID();
        if (StringUtils.isNotEmpty(xid)) {
            // 构建请求头
            requestWrapper.getHeaders().add("TX_XID", xid);
        }

        return clientHttpRequestExecution.execute(requestWrapper, bytes);
    }
}

2.3.实现HandlerInterceptor,重写 preHandle和afterCompletion

@Slf4j
@Configuration
public class SeataHandlerInterceptor implements HandlerInterceptor {

    public SeataHandlerInterceptor() {
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String xid = RootContext.getXID();
        String rpcXid = request.getHeader("TX_XID");
        // 获取全局事务编号
        if (log.isDebugEnabled()) {
            log.debug("xid in RootContext {} xid in RpcContext {}", xid, rpcXid);
        }

        if (xid == null && rpcXid != null) {
            // 设置全局事务编号
            RootContext.bind(rpcXid);
            if (log.isDebugEnabled()) {
                log.debug("bind {} to RootContext", rpcXid);
            }
        }

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
        String rpcXid = request.getHeader("TX_XID");
        if (StringUtils.isNotEmpty(rpcXid)) {
            String unbindXid = RootContext.unbind();
            if (log.isDebugEnabled()) {
                log.debug("unbind {} from RootContext", unbindXid);
            }

            if (!rpcXid.equalsIgnoreCase(unbindXid)) {
                log.warn("xid in change during RPC from {} to {}", rpcXid, unbindXid);
                if (unbindXid != null) {
                    RootContext.bind(unbindXid);
                    log.warn("bind {} back to RootContext", unbindXid);
                }
            }

        }
    }

}

2.4.业务编写,这里调用采用的是openFeign

@PostMapping(value = "test-seata-ll")
@ApiModelProperty(value = "seata AT模式 多服务调用回滚")
@GlobalTransactional(rollbackFor = Exception.class)
public void testSeataLl(@RequestBody @ApiParam HsSaveReq hsSave) throws Exception {
    // 模拟 A调用B,C
    // A,B成功,C失败情况,看看回滚
    hsService.saveHs(hsSave);
    // 删除一个存在的记录
    ResultData<Boolean> B = testProxy.deletePlatformOrgan("1");
    // 删除一个不存在的记录
    ResultData<Boolean> C = testProxy.deletePlatformRole("1");
    //可以根据自己的业务进行判断
    if (!"200".equals(B.getCode()) || !"200".equals(C.getCode())) {
        throw new Exception("回滚");
    }
}

也可以看看seata库中表的情况和undo_log表的情况,也是一致的

相关文章

网友评论

      本文标题:seata AT模式实践

      本文链接:https://www.haomeiwen.com/subject/mnmdaktx.html