zoukankan      html  css  js  c++  java
  • SpringCloud Alibaba微服务实战七

    导读:本篇作为SpringCloud Alibaba微服务实战系列的第七篇,主要内容是使用Seata解决分布式事务问题。系列文章,欢迎持续关注。

    场景说明

    订单服务order-service需要对外提供创建订单的接口,创建订单的业务逻辑如下:
    image.png
    先调用本地的orderService保存订单操作,然后通过feign调用远程的accout-service进行账户余额扣减,最后再通过feign调用远程的product-service进行库存扣减操作。

    关键的逻辑代码如下:

    • OrderController对外提供创建订单的接口
    @PostMapping("/order/create")
    public ResultData<OrderDTO> create(@RequestBody OrderDTO orderDTO){
    	log.info("create order:{}",orderDTO);
    	orderDTO.setOrderNo(UUID.randomUUID().toString());
    	orderDTO.setAmount(orderDTO.getPrice().multiply(new BigDecimal(orderDTO.getCount())));
    	orderService.createOrder(orderDTO);
    	return ResultData.success("create order success");
    }
    
    • OrderServiceImpl负责处理创建订单的业务逻辑
    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public void createOrder(OrderDTO orderDTO) {
    	Order order = new Order();
    	BeanUtils.copyProperties(orderDTO,order);
    	//本地存储Order
    	this.saveOrder(order);
    	//库存扣减
    	productFeign.deduct(orderDTO.getProductCode(),order.getCount());
    	//账户余额扣减
    	accountFeign.reduce(orderDTO.getAccountCode(), orderDTO.getAmount());
    }
    
    @Transactional(rollbackFor = RuntimeException.class)
    void saveOrder(Order order) {
    	orderMapper.insert(order);
    }
    

    本地先保存,然后调用两个远程服务进行扣减操作。

    • AccountServiceImpl扣减账户余额
    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public void reduceAccount(String accountCode, BigDecimal amount) {
    	Account account = accountMapper.selectByCode(accountCode);
    	if(null == account){
    		throw new RuntimeException("can't reduce amount,account is null");
    	}
    	BigDecimal subAmount = account.getAmount().subtract(amount);
    	if(subAmount.compareTo(BigDecimal.ZERO) < 0){
    		throw new RuntimeException("can't reduce amount,account'amount is less than reduce amount");
    	}
    	account.setAmount(subAmount);
    	accountMapper.updateById(account);
    }
    

    做些简单的校验,当账户余额不足的时候不允许扣减操作。

    • ProductServiceImpl扣减产品库存
    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public void deduct(String productCode, Integer deductCount) {
    	Product product = productMapper.selectByCode(productCode);
    	if(null == product){
    		throw new RuntimeException("can't deduct product,product is null");
    	}
    	int surplus = product.getCount() - deductCount;
    	if(surplus < 0){
    		throw new RuntimeException("can't deduct product,product's count is less than deduct count");
    	}
    	product.setCount(surplus);
    	productMapper.updateById(product);
    }
    

    做些简单的校验,当产品库存不足时不允许扣减操作。

    order-serviceproduct-serviceaccount-service分属不同的服务,当其中一个服务抛出异常无法提交时就会导致分布式事务,如当使用feign调用account-service执行扣减账户余额时,account-service校验账户余额不足抛出异常,但是order-service的保存操作不会回滚;或者是前两步执行成功但是product-service校验不通过前面的操作也不会回滚,这就导致了数据不一致,也就是分布式事务问题!

    Seata解决方案

    在Springcloud Alibaba体系中使用Seata作为分布式事务解决方案,大家可以访问seata官网去了解详情。
    这次我们先使用Seata的file配置解决上面出现的问题,后面再来对其改造。

    下载安装Seata Server。

    • 从 Release 页面下载Seata Server
    • 下载完成后直接启动Server端服务。
      在Linux/Mac下
      $ sh ./bin/seata-server.sh
      在Windows下
      binseata-server.bat

    引入seata组件

    <dependency>
    	<groupId>com.alibaba.cloud</groupId>
    	<artifactId>spring-cloud-alibaba-seata</artifactId>
    </dependency>
    

    在配置文件中增加seata配置

    spring:
      cloud:
        alibaba:
          seata:
            tx-service-group: ${spring.application.name}-seata
    

    Seata Client 配置修改

    • 将Seata Server 配置目录下的registry.conffile.conf 2个文件拷贝到微服务中的resources文件夹下
      image.png

    • 修改拷贝后的registry.conf

    registry{
      type = "file"
    
      file {
        name = "file.conf"
      }
    }
    
    config{
      type = "file"
    
      file {
        name = "file.conf"
      }
    }
    
    • 修改file.conf
      image.png
      主要修改如下三处:
      service.vgroup_mapping.后面的值修改为配置文件spring.cloud.alibaba.seata.tx-service-group的属性
      service.default.grouplist=修改为Seata Server的ip:端口
      support.spring.datasource.autoproxy的值修改为true,开启datasource自动代理

    生成undo_log表

    在微服务的业务库下执行如下语句,生成undo_log表

    -- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库)
    drop table `undo_log`;
    CREATE TABLE `undo_log` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `branch_id` bigint(20) NOT NULL,
      `xid` varchar(100) NOT NULL,
      `context` varchar(128) NOT NULL,
      `rollback_info` longblob NOT NULL,
      `log_status` int(11) NOT NULL,
      `log_created` datetime NOT NULL,
      `log_modified` datetime NOT NULL,
      `ext` varchar(100) DEFAULT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
    

    开启全局事务

    在分布式事务方法入口添加注解@GlobalTransactional,这里只需要在createOrder方法上添加此注解即可!

    @GlobalTransactional(name = "TX_ORDER_CREATE")
    @Override
    public void createOrder(OrderDTO orderDTO) {
    	Order order = new Order();
    	BeanUtils.copyProperties(orderDTO,order);
    	//本地存储Order
    	this.saveOrder(order);
    	log.info("ORDER XID is: {}", RootContext.getXID());
    	//账户余额扣减
    	accountFeign.reduce(orderDTO.getAccountCode(), orderDTO.getAmount());
    	//库存扣减
    	productFeign.deduct(orderDTO.getProductCode(),orderDTO.getCount());
    }
    

    在代码中可以使用RootContext.getXID()获取全局xid

    启动服务

    服务正常启动后在Seata Server控制台可以看到注册信息
    image.png

    接口测试

    改造完成后对接口进行测试,如果其他服务抛出异常会看到如下错误日志,再结合数据库数据观察是否正常回滚
    image.png

    执行过程中我们通过debug可以发现undo_log表会不断插入数据,在执行后又会被删除。
    image.png

    通过上面几步我们使用Seata实现了分布式事务,保证了数据的一致性,最后说一句Seata真香,你们要不要感受一下。
    至此本期的“SpringCloud Alibaba微服务实战七 - 分布式事务”篇也就该结束啦,咱们下期有缘再见!
    image.png
    再见之前让我在求一波关注吧,O(∩_∩)O哈哈~!
    image.png

    系列文章

    • SpringCloud Alibaba微服务实战六 - 配置隔离

    • SpringCloud Alibaba微服务实战五 - 限流熔断

    • SpringCloud Alibaba微服务实战四 - 版本管理

    • SpringCloud Alibaba微服务实战三 - 服务调用

    • SpringCloud Alibaba微服务实战二 - 服务注册

    • SpringCloud Alibaba微服务实战一 - 基础环境准备

  • 相关阅读:
    RubyConf的podcast
    一篇很好的英语学习文章:一个孤独而封闭世界――英语口语
    新浪和搜狐的读书频道
    新想法:个性化的RSS
    代码搜索:Koders
    我看到的Web 2.0: 自组织的大众化参与
    土豆网的后舍男孩挺搞笑的
    可以给pdf加批注的软件VeryPDF PDF Editor
    张海迪写的描写英语学习经验的书《美丽的英语》
    Fowler出来推荐Rake了(基于Ruby的build工具)
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13295916.html
Copyright © 2011-2022 走看看