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

前言

单体事务我们都知道怎么处理,直接一个个注解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表
在这里插入图片描述
然后我们把表的数据清空测试异常情况
在这里插入图片描述
数据库
在这里插入图片描述
说明数据已经回滚。完美

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
分布式事务微服务架构中的发展历程可以分为以下几个阶段: 1. 单体应用阶段:在传统的单体应用架构中,事务管理是相对简单的,通常使用数据库本身提供的事务机制来保证数据的一致性。这种方式在单体应用中能够很好地工作,但随着业务的增长和复杂性的提升,单体应用面临着可扩展性和灵活性的限制。 2. 集中式事务管理阶段:随着微服务架构的兴起,应用被拆分成多个小型服务,每个服务有自己独立的数据库。在这种情况下,跨服务的事务管理变得复杂起来。集中式事务管理器应运而生,例如使用分布式事务管理器(如XA协议)来协调多个服务之间的事务操作。这种方式能够保证数据的一致性,但也带来了性能和可用性的挑战。 3. 最终一致性解决方案阶段:由于集中式事务管理存在一些问题,例如性能瓶颈、单点故障等,逐渐出现了一些基于最终一致性的解决方案。最终一致性指的是,在一段时间后,系统中的所有副本最终都会达到一致的状态。这种方式通过使用消息队列、事件驱动等机制来解耦服务之间的依赖关系,并使用补偿机制来处理异常情况,从而实现了分布式事务处理。 4. Saga模式阶段:Saga是一种用于处理长时间和复杂事务的模式。它将事务拆分成一系列小的、可回滚的事务片段,每个事务片段对应一个服务的操作。每个事务片段都有自己的补偿操作,用于撤销之前的操作。Saga模式通过协调不同服务之间的事务片段来实现分布式事务的一致性。 需要注意的是,以上阶段并不是严格的时间顺序,不同的组织和系统可能会在不同的阶段采用不同的解决方案。此外,随着技术的发展和实践经验的积累,可能还会出现新的解决方案和模式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值