写于:2019-04-15 21:52:37

【08】-谈谈分布式事务 中我们从理论层次谈论了什么是分布式事务以及分布式事务的解决方案。

但脱离实际应用的理论都是空谈。为了加深对分布式事务的理解,了解实际场景的相关应用。接下来针对相关解决方案的实现方式进行场景模拟。

我们先来聊聊:多数据源事务

# 准备环境

在多数据源模拟中,针对了常用的 redis 、mysql、rabbitmq 进行多数据源事务模拟操作。

  • 2 个 mysql 数据库

    可以通过 docker 启动两个 mysql 数据库
    参考命令:
    docker run --name mysql_1 -p 3307:3306 -v ~/mysql_1/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d docker.io/mysql:5.7
    
  • 1个 rabbitmq

    可以通过 docker 启动 一个 rabbitmq 服务
    参考命令:
    docker run -d --hostname my-rabbit --name rabbit -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 -p 25672:25672 -p 61613:61613 -p 1883:1883 rabbitmq:management
    
  • 1 个 redis 服务

    可以通过 docker 启动一个 redis 服务
    参考命令:
    docker run -p 6379:6379 -v ~/redis/data:/data  -d redis:3.2 redis-server --appendonly yes
    

运行环境:spring boot 2.1.4 RELEASE

# 案例一:mysql - mysql 多数据源事务

前提条件

数据连接池为阿里开源的 Druid orm 框架使用 mybatis 多数据源事务:采用的是 链式事务,(JTA不演示)

数据库1:t_account 表,存放用户信息 数据库2:t_roder 表,存放生成的订单信息。

项目结构 mysql_mysql_项目结构

  • com.qguofeng.config 配置文件存放路径 PrimaryDataSourceConfig -> 从数据源-链接 mysql_2 管理 t_order 等表 CommonDruidDataSourceProperties -> druid 公共的配置,也可以针对不同的数据源进行,不同的配置设定 ChainedTransactionManagerConfig -> 链式事务 配置 DataSourceConstan -> 常量类
  • com.qguofeng.controller 采用 api 请求的方式模拟事务操作 AccountController -> 账户相关事务模拟 OrderController -> 订单相关事务模拟
  • com.qguofeng.domain 数据库表对应实体 back -> 存放从库实体 (该项目中 存放 订单) primary -> 存放主库实体 (该项目中 存放 用户
  • com.qguofneg.mapper 操作接口 base -> mapper curd 基类 back -> 存放从库操作 mapper (该项目中 订单操作mapper) primary -> 存放主库操作 mapper(该项目中 用户操作mapper
  • resource.mybatis.mapper 操作对应 xml 文件 back -> 存放从库 xml (该项目中 订单操作sql 存放 xml) primary -> 存放主库 xml (该项目中 用户操作sql 存放 xml)

模拟一:主库操作模拟事务回滚(也就是操作用户信息)

创建一个用户

public class AccountController {
    /** @Transactional 不指定事务管理器,默认使用 @Primay 指定的事务管理器 **/
    @RequestMapping("/create")
    @Transactional
    public String createAccount(@RequestParam(value = "error")Integer error){
        Account account = new Account();
        account.setAmount(0L);
        account.setAccount(UUID.randomUUID().toString().replace("-",""));
        accountMapper.insert(account);
        int a = 1/ error;
        return "success";
    }
}

查看日志信息

mysql_mysql_模拟一日志

模拟二:从库操作模拟事务回滚(也就是操作订单数据)

更新已存在订单的付款金额

public class OrderController {
    /** 指定事务管理为从库事务管理 **/
    @RequestMapping("/update")
	@Transactional(transactionManager = DataSourceConstan.BACK)
    public String updateOrder(@RequestParam(value = "error")int error){
        Order order = orderMapper.selectByPrimaryKey("dan_shu_ju_yuan_ce_shi_dan_hao");
        order.setPayAmount(random.nextLong());
        orderMapper.updateByPrimaryKey(order);
        int a = 1/error;
        return "success";
    }
}

查看日志信息

mysql_mysql_模拟二日志

模拟三:主库操作和从库操作在同一个事务中(同时操作用户和订单)

生成一个订单,同时更新用户余额

public class OrderController {
    /** 需要用到链式事务管理器,才能够完成多个事务的回滚 **/
	@RequestMapping("/create")
    @Transactional(transactionManager = DataSourceConstan.PRIMARY_BACK)
    public String createOrder(@RequestParam(value = "error1")int error1,
                              @RequestParam(value = "error2") int error2,
                              @RequestParam(value = "account")String account){
        // 生成订单
        Order order = new Order();
        order.setAccount(account);
        order.setOrderNo(UUID.randomUUID().toString());
        order.setPayAmount(100L);
        orderMapper.insert(order);
        int a = 1/error1;

        // 扣除用户金额
        Account user = accountMapper.selectByPrimaryKey(account);
        user.setAmount(user.getAmount() - 100L);
        accountMapper.updateByPrimaryKey(user);

        int b = 1/ error2;
        return "success";
    }
}

查看日志信息

mysql_mysql_模拟三日志

mysql_mysql_模拟三日志

# 案例二:mysql-redis 多数据源事务

前提条件

数据连接池为阿里开源的 Druid orm 框架使用 mybatis 多数据源事务:直接使用 spring 自动配置的事务管理器

数据库1:t_account 表,存放用户信息 redis :

项目结构

mysql_redis项目结构

  • com.qguofneg.config redis 配置类 RedisConfiguration -> redis 配置信息
  • com.qguofeng.controller api 模拟事务请求 AccountController 用户信息 api RedisController redis api
  • com.qguofneg.domain 数据库对应实体
  • com.qguofeng.mapper 对应mapper

模拟一:使用 @Transaction 注解,模拟 redis 操作数据回滚

public class RedisController {
    @RequestMapping("/account-1")
    @Transactional
    public String accountRedis(@RequestParam(value = "error")int error){
        String account = "dan_shu_ju_yuan_ce_shi_zhang_hao";
        Account user = accountMapper.selectByPrimaryKey(account);
        // 存入redis 缓存中
        SupportTransactionredisTemplate.opsForValue().set(account,user.toString());
        int a = 1/ error;
        return "success";
    }
}

查看日志

mysql_redis_模拟一日志

模拟二:使用@Transaction注解,模拟 更新用户信息,然后存入 redis 数据回滚

public class AccountController {
    @Transactional
    public String accountUpdate(@RequestParam(value = "error1") int error1,
                                @RequestParam(value = "error2") int error2){
        String account = "dan_shu_ju_yuan_ce_shi_zhang_hao";
        Account user = accountMapper.selectByPrimaryKey(account);
        user.setAmount(user.getAmount() - 100L);

        // 更新 mysql 数据
        accountMapper.updateByPrimaryKey(user);

        int a = 1 / error1;

        // 存入redis 缓存中
        supportTransactionRedisTemplage.opsForValue().set(account,user.toString());
        int b = 1 /error2;
        return "success";
    }
}

查看日志

mysql_redis_模拟二_1日志

mysql_redis_模拟二_2日志

# 案例三:mysql-rabbitmq 多数据源事务

前提条件

数据连接池为阿里开源的 Druid orm 框架使用 mybatis 多数据源事务:直接使用 spring 自动配置的事务管理器

数据库1:t_account 表,存放用户信息 Rabbitmq

项目结构

mysql_rabbitmq项目结构

  • com.qguofneg.config 配置类 ExchangeConfig 交换配置 QueueConfig 队列配置 RabbitMqConfig mq配置
  • com.qguofeng.controller api调用接口 AccountController 用户相关 api RabbitMqController Mq相关 api
  • com.qguofeng.mapper mybatis 操作

模拟一:发送消息,触发事务回滚

public class RabbitMqController {
    @RequestMapping("/send-msg")
    @Transactional
    public String sendMsg(@RequestParam(value = "error")int error){
        rabbitTemplate.convertAndSend("first-queue",random.nextInt(1000));
        int a = 1/error;
        return "success";
    }
}

查看日志 mysql_rabbitmq_模拟一日志

模拟二:更改用户信息,同时发送消息,触发事务回滚

public class AccountController {
    @RequestMapping("/update")
    @Transactional
    public String update(@RequestParam(value = "error1")int error1,
                         @RequestParam(value = "error2")int erro2){
        String account = "dan_shu_ju_yuan_ce_shi_zhang_hao";
        Account user = accountMapper.selectByPrimaryKey(account);
        user.setAmount(user.getAmount() - 200L);
        // 更新 mysql 数据
        accountMapper.updateByPrimaryKey(user);
        int a = 1 / error1;

        // 发送消息
        rabbitTemplate.convertAndSend("first-queue",user.toString());
        int b = 1/ erro2;

        return "success";
    }
}

查看日志 mysql_rabbitmq_模拟二日志_1

mysql_rabbitmq_模拟二日志_2

精彩内容推送,请关注公众号!
最近更新时间: 3/24/2020, 9:44:42 PM