• SpringCloud系列之集成分布式事务Seata应用篇


    前言

    单体应用被拆分成各个独立的业务模块后,就不得不要去面对分布式事务,好在阿里已经开源分布式事务组件Seata,虽还在迭代中,难免会有bug产生,但随着社区发展及反馈,相信终究会越来越稳定,话不多说让我们开始吧。

    项目版本

    spring-boot.version:2.2.5.RELEASE
    spring-cloud.version:Hoxton.SR3
    seata.version:1.2.0

    项目说明

    项目模块说明如下:

    前端请求接口经由网关服务进行路由转发后进入cloud-web模块,经cloud-web模块调用相应业务微服务模块,执行业务逻辑后响应前端请求。

    Seata服务端部署

    1.下载Seata服务端部署文件
    https://github.com/seata/seata/releases/download/v1.2.0/seata-server-1.2.0.zip
    如嫌下载慢,可关注本文下方微信公众号二维码,关注后回复“666”即可获取开发常用工具包
    2.解压至本地目录后,执行seata-server.bat脚本,过程中无报错则说明部署正常,Linux环境下操作类似不做展开说明

    Seata客户端集成
    cloud-web

    部分pom.xml,后续模块引入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>
    

    application.properties ,后续模块引入seata配置项大致一样,根据业务模块调整3个配置项即可,后续模块不再单独说明

    # seata配置
    seata.enabled=true
    #seata.excludes-for-auto-proxying=firstClassNameForExclude,secondClassNameForExclude
    seata.application-id=cloud-web
    seata.tx-service-group=cloud-web_tx_group
    seata.enable-auto-data-source-proxy=true
    seata.use-jdk-proxy=false
    
    seata.client.rm.async-commit-buffer-limit=1000
    seata.client.rm.report-retry-count=5
    seata.client.rm.table-meta-check-enable=false
    seata.client.rm.report-success-enable=false
    seata.client.rm.saga-branch-register-enable=false
    seata.client.rm.lock.retry-interval=10
    seata.client.rm.lock.retry-times=30
    seata.client.rm.lock.retry-policy-branch-rollback-on-conflict=true
    
    seata.client.tm.commit-retry-count=5
    seata.client.tm.rollback-retry-count=5
    seata.client.tm.degrade-check=false
    seata.client.tm.degrade-check-allow-times=10
    seata.client.tm.degrade-check-period=2000
    
    seata.client.undo.data-validation=true
    seata.client.undo.log-serialization=jackson
    seata.client.undo.only-care-update-columns=true
    seata.client.undo.log-table=undo_log
    
    seata.client.log.exceptionRate=100
    seata.service.vgroup-mapping.cloud-web_tx_group=default
    seata.service.grouplist.default=${cloud-web.seata.service.grouplist.default}
    seata.service.enable-degrade=false
    seata.service.disable-global-transaction=false
    
    seata.transport.shutdown.wait=3
    seata.transport.thread-factory.boss-thread-prefix=NettyBoss
    seata.transport.thread-factory.worker-thread-prefix=NettyServerNIOWorker
    seata.transport.thread-factory.server-executor-thread-prefix=NettyServerBizHandler
    seata.transport.thread-factory.share-boss-worker=false
    seata.transport.thread-factory.client-selector-thread-prefix=NettyClientSelector
    seata.transport.thread-factory.client-selector-thread-size=1
    seata.transport.thread-factory.client-worker-thread-prefix=NettyClientWorkerThread
    seata.transport.thread-factory.worker-thread-size=default
    seata.transport.thread-factory.boss-thread-size=1
    seata.transport.type=TCP
    seata.transport.server=NIO
    seata.transport.heartbeat=true
    seata.transport.serialization=seata
    seata.transport.compressor=none
    seata.transport.enable-client-batch-send-request=true
    
    seata.config.type=file
    seata.registry.type=file
    

    重点3个配置项需要调整下,其余保持默认
    seata.application-id=cloud-web
    seata.tx-service-group=cloud-web_tx_group
    seata.service.vgroup-mapping.cloud-web_tx_group=default

    OrderController.java

    @RestController
    @RequestMapping(value = "/order")
    public class OrderController {
    
        @Autowired
        OrderFacade orderFacade;
    
        @GlobalTransactional
        @GetMapping("/add")
        public String add(@RequestParam("cartId") Long cartId){
            orderFacade.addOrder(cartId);
            return "OK";
        }
    
    }
    

    在需要开启分布式事务的方法上添加@GlobalTransactional注解即可

    module-order

    项目结构图如下

    OrderService.java

    @RestController
    public class OrderService implements OrderFacade {
    
        @Autowired
        private TbOrderMapper tbOrderMapper;
        @Autowired
        private CartFacade cartFacade;
        @Autowired
        private GoodsFacade goodsFacade;
        @Autowired
        private WalletFacade walletFacade;
    
        /**
         * <p >
         * 功能:新增订单
         * </p>
         * @param cartId 购物车ID
         * @author wuyubin
         * @date  2020年05月22日
         * @return
         */
        @Override
        public void addOrder(Long cartId) {
            CartDTO cart = cartFacade.getCartById(cartId);
            TbOrder order = new TbOrder();
            order.setUserId(cart.getUserId());
            order.setGoodsId(cart.getGoodsId());
            order.setOrderNo(String.valueOf(System.currentTimeMillis()));
            order.setCreateTime(System.currentTimeMillis());
            order.setUpdateTime(order.getCreateTime());
            order.setIsDeleted(Byte.valueOf("0"));
            // 新增订单
            tbOrderMapper.insert(order);
            // 删除购物车
            cartFacade.deleteCartById(cartId);
            GoodsDTO goods = goodsFacade.getByGoodsId(cart.getGoodsId());
            // 扣减库存
            goodsFacade.substractStock(goods.getId());
            // 扣减金额
            walletFacade.substractMoney(cart.getUserId(),goods.getMoney());
            throw new RuntimeException();
        }
    
    module-cart

    项目结构图如下

    CartService.java

    @RestController
    public class CartService implements CartFacade {
    
        Logger LOGGER = LoggerFactory.getLogger(CartService.class);
    
        @Autowired
        private TbCartMapper tbCartMapper;
        /**
         * <p >
         * 功能:增加商品至购物车
         * </p>
         * @param userId 用户ID
         * @param goodsId 商品ID
         * @author wuyubin
         * @date  2020年05月22日
         * @return
         */
        @Override
        public String addCart(Long userId,Long goodsId) {
            TbCart cart = new TbCart();
            cart.setUserId(userId);
            cart.setGoodsId(goodsId);
            cart.setCreateTime(System.currentTimeMillis());
            cart.setUpdateTime(cart.getCreateTime());
            cart.setIsDeleted(Byte.valueOf("0"));
            tbCartMapper.insert(cart);
            return null;
        }
        /**
         * <p >
         * 功能:获取购物车信息
         * </p>
         * @param cartId 购物车ID
         * @author wuyubin
         * @date  2020年05月22日
         * @return
         */
        @Override
        public CartDTO getCartById(Long cartId) {
            CartDTO cartDTO = null;
            TbCart cart = tbCartMapper.selectById(cartId);
            if (null != cart) {
                cartDTO = new CartDTO();
                cartDTO.setUserId(cart.getUserId());
                cartDTO.setGoodsId(cart.getGoodsId());
            }
            return cartDTO;
        }
        /**
         * <p >
         * 功能:删除购物车信息
         * </p>
         * @param cartId 购物车ID
         * @author wuyubin
         * @date  2020年05月22日
         * @return
         */
        @Override
        public void deleteCartById(Long cartId) {
            tbCartMapper.deleteById(cartId);
        }
    }
    
    module-goods

    项目结构图如下

    GoodsService.java

    @RestController
    public class GoodsService implements GoodsFacade {
    
        @Autowired
        private TbGoodsMapper tbGoodsMapper;
        /**
         * <p >
         * 功能:获取商品信息
         * </p>
         * @param goodsId 商品ID
         * @author wuyubin
         * @date  2020年05月22日
         * @return
         */
        @Override
        public GoodsDTO getByGoodsId(Long goodsId) {
            GoodsDTO goodsDTO = null;
            TbGoods goods = tbGoodsMapper.selectById(goodsId);
            if (null != goods) {
                goodsDTO = new GoodsDTO();
                BeanUtils.copyProperties(goods,goodsDTO);
            }
            return goodsDTO;
        }
        /**
         * <p >
         * 功能:扣减商品库存
         * </p>
         * @param goodsId 商品ID
         * @author wuyubin
         * @date  2020年05月22日
         * @return
         */
        @Override
        public void substractStock(@RequestParam("goodsId") Long goodsId) {
            if (tbGoodsMapper.updateSubstractStockNumById(goodsId) != 1) {
                throw new RuntimeException("扣减库存异常");
            }
        }
    }
    
    module-wallet

    项目结构图如下

    WalletService.java

    @RestController
    public class WalletService implements WalletFacade {
    
        @Autowired
        private TbWalletMapper tbWalletMapper;
    
        /**
         * <p >
         * 功能:扣减用户钱包金额
         * </p>
         * @param userId 用户ID
         * @param money 金额
         * @author wuyubin
         * @date  2020年05月22日
         * @return
         */
        @Override
        public void substractMoney(Long userId, BigDecimal money) {
            if (tbWalletMapper.updateSubstractMoney(userId,money) != 1) {
                throw new RuntimeException("用户金额异常");
            }
        }
    }
    
    
    表结构说明

    undo_log 表:seata依赖表

    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 DEFAULT CHARSET=utf8;
    

    tb_cart 表:购物车表

    CREATE TABLE `tb_cart`  (
      `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
      `user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户ID',
      `goods_id` bigint(20) NULL DEFAULT NULL COMMENT '商品ID',
      `create_time` bigint(20) NULL DEFAULT NULL COMMENT '创建时间戳',
      `update_time` bigint(20) NULL DEFAULT NULL COMMENT '更新时间戳',
      `is_deleted` tinyint(4) NULL DEFAULT 0 COMMENT '删除标志 0:未删除;1:已删除',
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '购物车表' ROW_FORMAT = Dynamic;
    -- 初始化数据
    INSERT INTO `tb_cart` VALUES (1, 1, 1, 1590114829756, 1590114829756, 0);
    

    tb_goods 表:商品表

    CREATE TABLE `tb_goods`  (
      `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
      `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商品名称',
      `stock_num` bigint(20) NULL DEFAULT NULL COMMENT '商品库存数量',
      `money` decimal(10, 2) NULL DEFAULT NULL COMMENT '商品金额',
      `create_time` bigint(20) NULL DEFAULT NULL COMMENT '创建时间戳',
      `update_time` bigint(20) NULL DEFAULT NULL COMMENT '更新时间戳',
      `is_deleted` tinyint(4) NULL DEFAULT NULL COMMENT '删除标志 0:未删除;1:已删除',
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '商品表' ROW_FORMAT = Dynamic;
    -- 初始化数据
    INSERT INTO `tb_goods` VALUES (1, '键盘', 100, 100.00, 1590132270000, 1590377130, 0);
    

    tb_wallet 表:钱包表

    CREATE TABLE `tb_wallet`  (
      `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
      `user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户ID',
      `money` decimal(10, 2) NULL DEFAULT NULL COMMENT '金额',
      `create_time` bigint(20) NULL DEFAULT NULL COMMENT '创建时间戳',
      `update_time` bigint(20) NULL DEFAULT NULL COMMENT '更新时间戳',
      `is_deleted` tinyint(4) NULL DEFAULT NULL COMMENT '删除标志 0:未删除;1:已删除',
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '钱包表' ROW_FORMAT = Dynamic;
    -- 初始化数据
    INSERT INTO `tb_wallet` VALUES (1, 1, 500.00, 1590132270000, 1590377130, 0);
    

    tb_order 表:订单表

    CREATE TABLE `tb_order`  (
      `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
      `order_no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '订单编号',
      `user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户ID',
      `goods_id` bigint(20) NULL DEFAULT NULL COMMENT '商品ID',
      `create_time` bigint(20) NULL DEFAULT NULL COMMENT '创建时间',
      `update_time` bigint(20) NULL DEFAULT NULL COMMENT '更新时间',
      `is_deleted` tinyint(4) NULL DEFAULT 0 COMMENT '是否删除 0:未删除;1:已删除',
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '订单表' ROW_FORMAT = Dynamic;
    

    所有服务启动后,请求以下接口
    http://localhost:9005/cloud-web/order/add?cartId=1

    查看各服务模块日志,你会发现均有如下信息输出,提示已回滚

    因为在order模块中addOrder方法下,我这边人为抛出一个运行时异常,看样子事务已经生效了

    我们看下数据库中数据是否已回滚正常,核实后发现数据均以回滚




    接下来我们把order模块中addOrder方法下将“throw new RuntimeException();”代码块注释掉,重启订单模块服务后再次访问上述接口地址,发现访问正常

    查看各服务模块日志,你会发现均有如下信息输出,提示已提交成功

    我们再一次核实下数据表中的数据
    购物车表已将原先记录逻辑删除

    订单表新增一条订单记录

    商品表库存数量已扣减1

    钱包表金额已扣减100

    最后我们测试其中一个服务出现异常,验证下事务是否回滚正常,我们将购物车表逻辑删除恢复正常,将商品表库存改成0,这时我们再请求上述接口地址,发现返回异常了,我们再核实下数据,发现数据表中的数据均以回滚。好啦,SpringCloud集成分布式事务Seata的示例就到这里啦,后续有深入的研究再分享出来。

    参考资料

    https://github.com/seata/seata
    https://seata.io/zh-cn/docs/overview/what-is-seata.html

    系列文章

    SpringCloud系列之配置中心(Config)使用说明

    SpringCloud系列之服务注册发现(Eureka)应用篇

    SpringCloud系列之网关(Gateway)应用篇

    SpringCloud系列之集成Dubbo应用篇

    项目源码

    在这里插入图片描述

  • 相关阅读:
    SystemTap
    在qemu上运行BusyBox
    Initramfs 原理和实践
    在qemu环境中用gdb调试Linux内核
    [转载] 你所不知道的TIME_WAIT和CLOSE_WAIT
    Linux VXLAN
    :not伪类选择器一些错误的写法
    c# 微软小冰-虚拟女友聊天
    Django使用表单操作数据库
    Django内置过滤器详解附代码附效果图--附全部内置过滤器帮助文档
  • 原文地址:https://www.cnblogs.com/chinaWu/p/13255200.html
Copyright © 2020-2023  润新知