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依赖

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

            <dependency>
               <groupId>com.alibaba.cloud</groupId>
               <artifactId>spring-cloud-alibaba-seata</artifactId>
               <version>2.2.0.RELEASE</version>
               <exclusions>
                   <exclusion>
                       <groupId>io.seata</groupId>
                       <artifactId>seata-spring-boot-starter</artifactId>
                   </exclusion>
               </exclusions>
           </dependency>
           <dependency>
               <groupId>io.seata</groupId>
               <artifactId>seata-spring-boot-starter</artifactId>
               <version>1.2.0</version>
           </dependency>

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

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

        <!--seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-seata</artifactId>
            <version>2.2.0.RELEASE</version>
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.2.0</version>
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-all</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>1.2.0</version>
        </dependency>

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

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

配置和脚本存放位置:

我的配置

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配置中心中查看

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中可以查看是否启动了集群

 

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

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值