分布式(微服务)事务处理

前言

单体事务我们都知道怎么处理,直接一个个注解Transactional搞定,虽然单体事务很好处理,但是一旦到分布式事务上的时候就有点麻烦了。其实就算是在单体服务中一个有事务的方法调其他方法的时候也会出现事务失效不能回滚的问题,当然这不是我们今天讨论的问题。我们在微服务开发中肯定会遇到服务a想要调服务b的情况,在这种情况下如果服务a在调用完服务b后发生的异常如果没有一些事务处理的方案的话服务b是不会回滚的,这样问题就大了。事实上先分布式处理理论上有很多种方式。理论这里就不说了,我们直接代码来看。我们使用的是tcc-lcn分布式事务处理。

准备工作

1. 代码准备。

代码我就不自己写了,在githut上找了一个demo
源码 https://github.com/codingapi/txlcn-demo

2. 数据库准备

创建一个数据库名叫tx-manager
在这里插入图片描述

3. 表准备

执行sql脚本。我直接把脚本代码提出来了,可以直接下面代码复制下来粘贴到一个sql文本里。执行就可以个

DROP TABLE IF EXISTS `t_demo`;
CREATE TABLE `t_demo` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `kid` varchar(45) DEFAULT NULL,
  `demo_field` varchar(255) DEFAULT NULL,
  `group_id` varchar(64) DEFAULT NULL,
  `unit_id` varchar(32) DEFAULT NULL,
  `app_name` varchar(128) DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

会生成这样的表,这里t_demo表是我们服务a、b、c维护的表
在这里插入图片描述

4.运行redis

准备一个无密码的redis启动,可以在redis.conf文件设置
在这里插入图片描述
cmd 启动
在这里插入图片描述

修改代码配置改成自己数据库

项目结构
在这里插入图片描述

txlcn-demo-tm 模块

在这里插入图片描述
主要看一些依赖 配置文件 和启动类
依赖见上图
配置文件别看那么多只需要看看 application.properties
内容如下

##################
# 这个是启动本服务的配置文件,其它的application-xxx.properties 是开发者的个性化配置,不用关心。
# 你可以在 https://txlcn.org/zh-cn/docs/setting/manager.html 看到所有的个性化配置
#################

spring.application.name=TransactionManager
server.port=7970
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tx-manager?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=update

mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.use-generated-keys=true

启动类

package org.txlcn.tm;

import com.codingapi.txlcn.tm.config.EnableTransactionManagerServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableTransactionManagerServer  //这个注解看名字就知道启动事务管理服务
public class TransactionManagerApplication {

    public static void main(String[] args) {
        SpringApplication.run(TransactionManagerApplication.class, args);
    }
}

txlcn-demo-tm 模块 就没有其他代码了需要关系了,我们只需要把数据库的连接改成我们的就好了

txlcn-demo-spring-service-a txlcn-demo-spring-service-b txlcn-demo-spring-service-c 模块

都除了业务不同都差多,我们也只看重点 启动类 配置类 service层
启动类

package org.txlcn.demo.servicea;

import com.codingapi.txlcn.tc.config.EnableDistributedTransaction;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * Description:
 * Date: 2018/12/25
 *
 * @author ujued
 */
@SpringBootApplication
@EnableDiscoveryClient
@EnableDistributedTransaction //重点是注解
public class SpringServiceAApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringServiceAApplication.class, args);
    }

//ribbon 处理 我们不改成Feign 
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

txlcn-demo-spring-service-a 模块的service
代码根据参数跑出异常,抛异常数据就会回滚

package org.txlcn.demo.servicea;

import com.codingapi.txlcn.common.util.Transactions;
import com.codingapi.txlcn.tracing.TracingContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.txlcn.demo.common.db.domain.Demo;
import org.txlcn.demo.common.spring.ServiceBClient;
import org.txlcn.demo.common.spring.ServiceCClient;

import java.util.Date;
import java.util.Objects;

/**
 * Description:
 * Date: 2018/12/25
 *
 * @author ujued
 */
@Service
@Slf4j
public class DemoServiceImpl implements DemoService {

    private final DemoMapper demoMapper;

    private final ServiceBClient serviceBClient;

    private final ServiceCClient serviceCClient;

    private final RestTemplate restTemplate;

    @Autowired
    public DemoServiceImpl(DemoMapper demoMapper, ServiceBClient serviceBClient, ServiceCClient serviceCClient, RestTemplate restTemplate) {
        this.demoMapper = demoMapper;
        this.serviceBClient = serviceBClient;
        this.serviceCClient = serviceCClient;
        this.restTemplate = restTemplate;
    }

    @Override
    public String execute(String value, String exFlag) {
        // step1. call remote ServiceD
//        String dResp = serviceBClient.rpc(value);

        String dResp = restTemplate.getForObject("http://127.0.0.1:12002/rpc?value=" + value, String.class);

        // step2. call remote ServiceE
        String eResp = serviceCClient.rpc(value);

        // step3. execute local transaction
        Demo demo = new Demo();
        demo.setGroupId(TracingContext.tracing().groupId());
        demo.setDemoField(value);
        demo.setCreateTime(new Date());
        demo.setAppName(Transactions.getApplicationId());
        demoMapper.save(demo);
        //设置异常
        //int i = 1/0;
        // 置异常标志,DTX 回滚
        if (Objects.nonNull(exFlag)) {
            throw new IllegalStateException("by exFlag");
        }

        return dResp + " > " + eResp + " > " + "ok-service-a";
    }
}

txlcn-demo-spring-service-b 模块的service txlcn-demo-spring-service-b 类似就不看了

package org.txlcn.demo.serviceb;

import com.codingapi.txlcn.common.util.Transactions;
import com.codingapi.txlcn.tc.annotation.DTXPropagation;
import com.codingapi.txlcn.tc.annotation.TxcTransaction;
import com.codingapi.txlcn.tracing.TracingContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.txlcn.demo.common.db.domain.Demo;

import java.util.Date;

/**
 * Description:
 * Date: 2018/12/25
 *
 * @author ujued
 */
@Service
@Slf4j
public class DemoServiceImpl implements DemoService {

    private final DemoMapper demoMapper;

    @Autowired
    public DemoServiceImpl(DemoMapper demoMapper) {
        this.demoMapper = demoMapper;
    }

    @Override
    @TxcTransaction(propagation = DTXPropagation.SUPPORTS) // 注意这个
    @Transactional
    public String rpc(String value) {


        Demo demo = new Demo();
        demo.setGroupId(TracingContext.tracing().groupId());
        demo.setDemoField(value);
        demo.setAppName(Transactions.getApplicationId());
        demo.setCreateTime(new Date());
        demoMapper.save(demo);
        return "ok-service-b";
    }
}

调用是a调b、c

测试

依次启动服务
在这里插入图片描述
t_demo表
在这里插入图片描述
然后我们把表的数据清空测试异常情况
在这里插入图片描述
数据库
在这里插入图片描述
说明数据已经回滚。完美

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值