SpringCloud Alibaba之Seata 1.2版本的分布式事务案例演示

所有代码都在github上:https://github.com/demonruin/cloud2020/tree/master

 

参考资料:https://mp.weixin.qq.com/s/2KSidJ72YsovpJ94P1aK1g seata官徽

 新人文档:https://seata.io/zh-cn/docs/ops/deploy-guide-beginner.html

 

说真的,这个seata搭建与案例演示让我爬了好多坑~~总算测试成功了~现在将自己的步骤与注意事项记录下来:

此片文章的前提是上一篇中讲到的seata-server的正确安装,下面进行client端的配置与编码等准备。

mysql: 5.7

nacos: latest镜像

spring-cloud-alibaba: 2.2.0

seata: 1.2.0

第一步下载seata服务,第二步创建Seata高可用的db,此两步在上篇文章中已经提到了~可回看,下面讲客户端操作

1、先准备业务数据库,新建数据库seata-order,seata-account,seata-storage,并分别执行一下建表语句

CREATE TABLE `t_order` (`id` BIGINT (11) NOT NULL AUTO_INCREMENT PRIMARY KEY,`user_id` BIGINT (11) DEFAULT NULL COMMENT '用户id',`product_id` BIGINT (11) DEFAULT NULL COMMENT '产品id',`count` INT (11) DEFAULT NULL COMMENT '数量',`money` DECIMAL (11,0) DEFAULT NULL COMMENT '金额',`status' INT ( 1 ) DEFAULT NULL COMMENT '订单状态:0:创建中; 1:已完结' 
) ENGINE = INNODB AUTO_INCREMENT = 7 DEFAULT CHARSET = utf8;
CREATE TABLE `t_storage` (`id` BIGINT (11) NOT NULL AUTO_INCREMENT PRIMARY KEY,`product_id` BIGINT (11) DEFAULT NULL COMMENT '产品id',`total` INT (11) DEFAULT NULL COMMENT '总库存',`used` INT (11) DEFAULT NULL COMMENT '已用库存',`residue` INT (11) DEFAULT NULL COMMENT '剩余库存') ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; 
INSERT INTO seata_storage.`t_storage` (id,product_id,total,used,residue) VALUES ('1','1','100','0','100'); 
CREATE TABLE t_account (`id` BIGINT (11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'id',`user_id` BIGINT (11) DEFAULT NULL COMMENT '用户id',`total` DECIMAL (10,0) DEFAULT NULL COMMENT '总额度',`used` DECIMAL (10,0) DEFAULT NULL COMMENT '已用余额',`residue` DECIMAL (10,0) DEFAULT '0' COMMENT '剩余可用额度') ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; 
INSERT INTO seata_account.t_account (id,user_id,total,used,residue) VALUES ('1','1','1000','0','1000'); 

然后每个涉及到事务的数据库中都多添加一张undo_log表

-- 注意此处0.3.0+ 增加唯一索引 ux_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;

2、添加seata依赖

注意:官方推荐是这么配置,但是我本地集成的时候出现了问题,版本不对

            
               com.alibaba.cloud
               spring-cloud-alibaba-seata
               2.2.0.RELEASE
               
                   
                       io.seata
                       seata-spring-boot-starter
                   
               
           
           
               io.seata
               seata-spring-boot-starter
               1.2.0
           

这样配置好以后我的依赖中seata-all的版本是0.7,以致于我的注解配置enable-auto-data-source-proxy: true报错

后经过修改,修改pom文件中的依赖为这个样子

        
        
            com.alibaba.cloud
            spring-cloud-alibaba-seata
            2.2.0.RELEASE
            
                
                    io.seata
                    seata-spring-boot-starter
                
            
        
        
            io.seata
            seata-spring-boot-starter
            1.2.0
            
                
                    io.seata
                    seata-all
                
            
        
        
            io.seata
            seata-all
            1.2.0
        

此处要根据自己的项目中更新的依赖来判断要不要做一步处理~~~~

3、加入seata所需的参数配置

从官方github仓库拿到参考配置做修改:https://github.com/seata/seata/tree/develop/script/client,只是参考,官方给的是全部配置,自己挑选对应的配置拿下来即可~加入到你的项目的appliaction.yml中即可

seata:
  enabled: true
  application-id: orders-service
  tx-service-group: my_test_tx_group
  enable-auto-data-source-proxy: true
  config:
    type: nacos
    file:
      name: file.conf
    nacos:
      namespace:
      serverAddr: 127.0.0.1:8848
      group: SEATA_GROUP
      userName: "nacos"
      password: "nacos"
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      namespace:
      userName: "nacos"
      password: "nacos"

4、配置为高可用db模式参数并提交至配置中心

运行你下载的nacos,并参考https://github.com/seata/seata/tree/develop/script/config-center 的config.txt并修改,此处也是给的全量配置参数,选取对应的配置即可,运行仓库中提供的nacos脚本,将以上信息提交到nacos控制台,如果有需要更改,可直接通过控制台更改。脚本存放地址https://github.com/seata/seata/blob/develop/script/config-center/nacos/nacos-config.sh

注意:此配置只需要执行一次即可,因为是针对seata-server的,然后脚本存放的目录在参考网址中对应的nacos/zk等文件夹中,需要注意即可,我是下载下来,然后拷贝到了项目中,将config.txt和脚本放在了相对路径下,这个相对路径要和参考网址上那个一样,否则会报找不到config.txt

配置和脚本存放位置:

SpringCloud Alibaba之Seata 1.2版本的分布式事务案例演示_第1张图片

我的配置

service.vgroupMapping.my_test_tx_group=default
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true
store.db.user=root
store.db.password=root
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000

然后生成配置后,可以nacos配置中心中查看SpringCloud Alibaba之Seata 1.2版本的分布式事务案例演示_第2张图片

5、更改seata-server服务端的注册&配置中心为nacos然后重新启动seata-server,这一步在上一篇应该修改过了,这里在贴一下

更改server中的registry.conf

registry {
 # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
 type = "nacos"
 nacos {
   application = "seata-server"
   serverAddr = "localhost"
   namespace = ""
   cluster = "default"
   username = "nacos"
   password = "nacos"
}
}

config {
 # file、nacos 、apollo、zk、consul、etcd3
 type = "nacos"

 nacos {
   serverAddr = "localhost"
   namespace = ""
   group = "SEATA_GROUP"
   username = "nacos"
   password = "nacos"
}
}

6、开发业务代码加入全局事务注解进行调试

此处因为业务代码比较多,所以我这里只贴关键和注意的部分,其他的可以去我的github上直接下载源码项目cloud2020,主要是三个module:cloudalibaba-seata-order-service2001,cloudalibaba-seata-storage-service2002,cloudalibaba-seata-account-service2001

cloudalibaba-seata-order-service2001中:

orderServiceImpl

package com.king.springcloud.service.impl;

import com.king.springcloud.dao.OrderDao;
import com.king.springcloud.domain.Order;
import com.king.springcloud.service.AccountService;
import com.king.springcloud.service.OrderService;
import com.king.springcloud.service.StorageService;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * created by king on 2020/6/3 10:56 下午
 */
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    @Resource
    private OrderDao orderDao;

    @Resource
    private StorageService storageService;

    @Resource
    private AccountService accountService;

    /**
     * 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
     * 简单说:下订单->扣库存->减余额->改状态
     */
    @Override
    @GlobalTransactional
    public void create(Order order) {
        log.info("----->开始新建订单");
        //1 新建订单
        orderDao.create(order);

        //2 扣减库存
        log.info("----->订单微服务开始调用库存,做扣减Count");
        storageService.decreaseStorage(order.getProductId(),order.getCount());
        log.info("----->订单微服务开始调用库存,做扣减end");

        //3 扣减账户
        log.info("----->订单微服务开始调用账户,做扣减Money");
        accountService.decreaseAccount(order.getUserId(),order.getMoney());
        log.info("----->订单微服务开始调用账户,做扣减end");

        //4 修改订单状态,从零到1,1代表已经完成
        log.info("----->修改订单状态开始");
        orderDao.update(order.getUserId(),0);
        log.info("----->修改订单状态结束");

        log.info("----->下订单结束了,O(∩_∩)O哈哈~");

    }
}

accountService:

package com.king.springcloud.service;

import com.king.springcloud.domain.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.math.BigDecimal;

/**
 * created by king on 2020/6/3 11:01 下午
 */
@FeignClient(value = "seata-account-service")
public interface AccountService {

    @PostMapping(value = "account/decrease")
    CommonResult decreaseAccount(@RequestParam(value="userId") Long userId, @RequestParam(value="money") BigDecimal money);
}

storageService:

package com.king.springcloud.service;

import com.king.springcloud.domain.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * created by king on 2020/6/3 10:58 下午
 */
@FeignClient(value = "seata-storage-service")
public interface StorageService {

    @PostMapping(value = "/storage/decrease")
    CommonResult decreaseStorage(@RequestParam(value ="productId") Long productId,@RequestParam(value="count") Integer count);
}

此处需要注意:在FeignClient调用的下面接口中,@RequestParam中要有value即@RequestParam(value ="productId") Long productId格式,不能省略value写成@RequestParam(productId) Long productId这样,否则会访问的时候报异常feign.FeignException: status 400 reading xxx#xxxx(String); 

orderDao:

package com.king.springcloud.dao;

import com.king.springcloud.domain.Order;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * created by king on 2020/6/3 10:51 下午
 */
@Mapper
public interface OrderDao {
    //1、创建订单
    void create (Order order);
    //2、更新订单状态,从0改为1
    void update (@Param("userId") Long userId, @Param("status") Integer status);

}

注意:此处要加@Param,而且是ibatis下的包,我此处就因为倒错包了,错误导入了Feing包下的,导致了以下异常binding.BindingException: Parameter 'userId' not found. Available parameters

剩余两个module也就是业务操作处理了,可以直接下载项目源码来查看比较方便,主要是项目构建module的几个步骤,

  • 添加pom中的seata依赖
  • 添加bootstrap.yml 和 applicaiton.yml中添加nacos、seata、mybatis、feign等相关配置
  • 构建主启动类,添加controller、service、dao、mapper、domain等业务相关代码

7、进行调试代码,在被调用的account中添加sleep或者int i=1/0等异常代码,来测试事务回滚状态:

当没有添加@GlobalTranctional注解时,上述两种异常发生后都不会回滚,超时时账户也会扣减,而且由于feign的重试机制,账户余额还有可能被多次扣减,但是两种情况下订单状态都不会改变

当添加了@GlobalTranctional注解后,上述两种情况都会回滚,可以在在中间环境打个断点,来查看seata库中的表的数据,存储的就是preimage、事务统一id、分组等信息,还有undo_log表中也会存在记录,但是当事务处理完,提交或回滚后,会删除这些表中数据,这也就是为什么我们执行完事务后,再去查看这些表,发现里面依然是空表~~

 

8、高可用Seata-server搭建

确保你已经完成了以上七步操作后,按照以上的第1,6两步即可把你的seata新节点接入到同一个nacos集群,配置&注册中心中,由于是同一个配置中心,所以db也是采用的共同配置.至此高可用搭建已经顺利完结,如果你想测试,仅需关掉其中一个server节点,验证服务是否可用即可.

 启动另一个seata-server命令:

sh ./seata-server.sh -p 8877

然后nacos中可以查看是否启动了集群SpringCloud Alibaba之Seata 1.2版本的分布式事务案例演示_第3张图片

SpringCloud Alibaba之Seata 1.2版本的分布式事务案例演示_第4张图片

 

至此,seata的分布式事务项目构建案例完成了,演示成功了~~~~~

你可能感兴趣的:(SpringCloud,SpringCloud,Alibaba,Springboot)