SpringCloud+Seata+nacos案例(包含源码 Seata及nacos安装教程)

本文涉及的产品
RDS DuckDB + QuickBI 企业套餐,8核32GB + QuickBI 专业版
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
RDS Agent Manager,2核4GB
简介: SpringCloud+Seata+nacos案例(包含源码 Seata及nacos安装教程)

一.Seata简介


Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。在 Seata 开源之前,Seata 对应的内部版本在阿里经济体内部一直扮演着分布式一致性中间件的角色,帮助经济体平稳的度过历年的双 11,对各 BU 业务进行了有力的支撑。经过多年沉淀与积累,商业化产品先后在阿里云、金融云进行售卖。2019.1为了打造更加完善的技术生态和普惠技术成果,Seata 正式宣布对外开源,未来 Seata 将以社区共建的形式帮助其技术更加可靠与完备。


二.Seata安装(Windows安装)


1.下载地址:下载地址 博主这里使用的是v0.9.0

2.下载后需要对对应文件尽心修改

a.file.conf修改


transport {
  # tcp udt unix-domain-socket
  type = "TCP"
  #NIO NATIVE
  server = "NIO"
  #enable heartbeat
  heartbeat = true
  #thread factory for netty
  thread-factory {
    boss-thread-prefix = "NettyBoss"
    worker-thread-prefix = "NettyServerNIOWorker"
    server-executor-thread-prefix = "NettyServerBizHandler"
    share-boss-worker = false
    client-selector-thread-prefix = "NettyClientSelector"
    client-selector-thread-size = 1
    client-worker-thread-prefix = "NettyClientWorkerThread"
    # netty boss thread size,will not be used for UDT
    boss-thread-size = 1
    #auto default pin or 8
    worker-thread-size = 8
  }
  shutdown {
    # when destroy server, wait seconds
    wait = 3
  }
  serialization = "seata"
  compressor = "none"
}
service {
  #vgroup->rgroup
  vgroup_mapping.my_test_tx_group = "fsp_ex_group"
  #only support single node
  default.grouplist = "127.0.0.1:8091"
  #degrade current not support
  enableDegrade = false
  #disable
  disable = false
  #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
  max.commit.retry.timeout = "-1"
  max.rollback.retry.timeout = "-1"
  #若报配置异常则加上下面一句配置即可
  disableGlobalTransaction=false
}
client {
  async.commit.buffer.limit = 10000
  lock {
    retry.internal = 10
    retry.times = 30
  }
  report.retry.count = 5
  tm.commit.retry.count = 1
  tm.rollback.retry.count = 1
}
## transaction log store
store {
  ## store mode: file、db
  mode = "db"
  ## file store
  file {
    dir = "sessionStore"
    # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
    max-branch-session-size = 16384
    # globe session size , if exceeded throws exceptions
    max-global-session-size = 512
    # file buffer size , if exceeded allocate new buffer
    file-write-buffer-cache-size = 16384
    # when recover batch read size
    session.reload.read_size = 100
    # async, sync
    flush-disk-mode = async
  }
  ## database store
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
    datasource = "dbcp"
    ## mysql/oracle/h2/oceanbase etc.
    db-type = "mysql"
    driver-class-name = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://localhost:3306/seata"
    user = "root"
    password = "123456"
    min-conn = 1
    max-conn = 3
    global.table = "global_table"
    branch.table = "branch_table"
    lock-table = "lock_table"
    query-limit = 100
  }
}
lock {
  ## the lock store mode: local、remote
  mode = "remote"
  local {
    ## store locks in user's database
  }
  remote {
    ## store locks in the seata's server
  }
}
recovery {
  #schedule committing retry period in milliseconds
  committing-retry-period = 1000
  #schedule asyn committing retry period in milliseconds
  asyn-committing-retry-period = 1000
  #schedule rollbacking retry period in milliseconds
  rollbacking-retry-period = 1000
  #schedule timeout retry period in milliseconds
  timeout-retry-period = 1000
}
transaction {
  undo.data.validation = true
  undo.log.serialization = "jackson"
  undo.log.save.days = 7
  #schedule delete expired undo_log in milliseconds
  undo.log.delete.period = 86400000
  undo.log.table = "undo_log"
}
## metrics settings
metrics {
  enabled = false
  registry-type = "compact"
  # multi exporters use comma divided
  exporter-list = "prometheus"
  exporter-prometheus-port = 9898
}
support {
  ## spring
  spring {
    # auto proxy the DataSource bean
    datasource.autoproxy = false
  }
}

b.registry.conf修改


registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "file"
  nacos {
    serverAddr = "localhost"
    namespace = ""
    cluster = "default"
  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    application = "default"
    weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = "0"
  }
  zk {
    cluster = "default"
    serverAddr = "127.0.0.1:2181"
    session.timeout = 6000
    connect.timeout = 2000
  }
  consul {
    cluster = "default"
    serverAddr = "127.0.0.1:8500"
  }
  etcd3 {
    cluster = "default"
    serverAddr = "http://localhost:2379"
  }
  sofa {
    serverAddr = "127.0.0.1:9603"
    application = "default"
    region = "DEFAULT_ZONE"
    datacenter = "DefaultDataCenter"
    cluster = "default"
    group = "SEATA_GROUP"
    addressWaitTime = "3000"
  }
  file {
    name = "file.conf"
  }
}
config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "file"
  nacos {
    serverAddr = "localhost"
    namespace = ""
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  apollo {
    app.id = "seata-server"
    apollo.meta = "http://192.168.1.204:8801"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    session.timeout = 6000
    connect.timeout = 2000
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}


3.创建seata数据库(mysql) 将seata->conf->db_store.sql放到数据库中运行即可


4.到bin目录下找到seata-server.bat运行即可 如下即为成功运行:

20210616102002253.png

三.nacos安装


a.下载地址:https://github.com/alibaba/nacos/releases


b.修改application.properties文件(将数据库对应配置注释打开即可)


20210616102403571.png


c.找到startup.cmd编辑里面的将set MODE="cluster"一行改成set MODE=“standalone”


d.点击startup.cmd运行即可,如下即为成功:


20210616102923852.png


e.访问localhost:8848即可 默认账号密码都是:nacos


四.项目案例


1.项目结构

2021061610313318.png


account-service:负责对用户余扣减


business-service:负责完成下单的逻辑,包含 2 个主要的步骤,就是对库存服务和订单服务的远程调用


order-service:负责完成保存用户订单的操作


storage-service:负责完成对库存的扣减


2.各模块主要代码展示:


account-service


@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountTblMapper accountTblMapper ;
    private static Logger logger = LoggerFactory.getLogger(AccountServiceImpl.class) ;
    @Transactional
    @Override
    public void debit(String userId, int money) {
        logger.info("开始扣减用户:{}的余额,数量为:{}",userId ,money);
        //1 查询数据库里面的账户
        AccountTbl accountTbl = accountTblMapper.selectOne(new LambdaQueryWrapper<AccountTbl>().
                eq(AccountTbl::getUserId, userId));
        if(accountTbl==null){
            throw new IllegalArgumentException("此账号不存在")  ;
        }
        //2 扣减的操作
        int idleMoney = accountTbl.getMoney() - money ;
        // 3 库存的校验
        if(idleMoney<0){
            throw new RuntimeException("库存不足") ;
        }
        //4 设置到账户里面
        accountTbl.setMoney(idleMoney);
        //5 保存到数据库里里面
        accountTblMapper.updateById(accountTbl) ;
        if("SXT_USER_2".equals(userId)){
            throw new RuntimeException("不想成功") ;
        }
    }
}


####Pom文件


<dependencies>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.0</version>
        </dependency>
        <!--MySQL依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>

business-service

@Service
public class BusinessServiceImpl implements BusinessService {
    private static Logger logger = LoggerFactory.getLogger(BusinessServiceImpl.class) ;
    @Autowired
    private StorageServiceApi storageServiceApi ;
    @Autowired
    private OrderServiceApi orderServiceApi ;
    @Override
    public void purchase(String userId, String productNo, int count) {
        logger.info("准备下单,用户{},商品{},数量{}",userId ,productNo ,count);
        // 1 远程调用库存服务--> 库存的扣减f
        storageServiceApi.deduct(productNo, count);
        // 2 远程调用订单服务-->订单的新增
        orderServiceApi.create(userId, productNo, count);
        logger.info("下单成功");
    }
}

####通过feign调用 代码:


OrderServiceApi

@Service
public class OrderServiceApi {
    private static Logger logger = LoggerFactory.getLogger(OrderServiceApi.class) ;
//    @Autowired
//    private RestTemplate restTemplate ;
    @Autowired
    private OrderServiceFeign orderServiceFeign ;
    public void create(String userId ,String productNo ,int count){
//        ResponseEntity<Void> responseEntity = restTemplate.getForEntity(
//                "http://order-service/create/{userId}/{productNo}/{count}",
//                Void.class,
//                userId,
//                productNo,
//                count
//        );
        ResponseEntity<Void> responseEntity = orderServiceFeign.create(userId, productNo, count);
        if(responseEntity.getStatusCode()== HttpStatus.OK){
            logger.info("远程调用订单服务成功");
            return;
        }
        throw new RuntimeException("远程调用订单服务失败") ;
    }
}

StorageServiceApi

@Service
public class StorageServiceApi {
    private static Logger logger = LoggerFactory.getLogger(StorageServiceApi.class) ;
//    @Autowired
//    private RestTemplate restTemplate ; //ribbon
    @Autowired
    private StorageServiceFeign storageServiceFeign ;
    public void deduct(String productNo ,int count){
//        ResponseEntity<Void> responseEntity = restTemplate.getForEntity(
//                "http://storage-service/deduct/{productNo}/{count}",
//                Void.class,
//                productNo,
//                count
//        );
        ResponseEntity<Void> responseEntity = storageServiceFeign.deduct(productNo, count);
        if(responseEntity.getStatusCode()== HttpStatus.OK){
            logger.info("远程调用库存服务成功");
            return;
        }
        throw new RuntimeException("远程调用库存服务失败");
    }
}

###order-service

@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private OrderTblMapper orderTblMapper ;
    @Autowired
    private AccountServiceApi accountServiceApi ;
    private static Logger logger = LoggerFactory.getLogger(OrderServiceImpl.class) ;
    @Transactional
    @Override
    public OrderTbl createOrder(String userId, String productNo, int count) {
        logger.info("开始创建一个订单,用户为{},商品为{},数量为{}",userId,productNo,count);
        // 1 扣减用户的余额 account-service - >rpc
        int money = cal(productNo,count) ;
        ResponseEntity<Void> responseEntity = accountServiceApi.debit(userId, money);
        if(responseEntity.getStatusCode()!= HttpStatus.OK){
            throw new RuntimeException("远程扣减用户的余额失败") ;
        }
        //2 写本地的订单表
        OrderTbl orderTbl = new OrderTbl();
        orderTbl.setUserId(userId);
        orderTbl.setCommodityCode(productNo);
        orderTbl.setCount(count);
        orderTblMapper.insert(orderTbl) ;
        logger.info("创建订单成功,用户为{},商品为{},数量{}",userId,productNo,count);
        return orderTbl;
    }
    /**
     * 计算商品的总价格
     * @param productNo
     * @param count
     * @return
     */
    private int cal(String productNo, int count) {
        //我们的数据库里面非常简单,没有商品的表
        int money = 0  ;
        if("HUAWEI_0001".equals(productNo)){
            money = 2000 ;
        }else if("XIAOMI_002".equals("productNo")){
            money = 1000 ;
        }else {
            money = 9999 ;
        }
        return money * count ;
    }
}

###storage-service

@Service
public class StorageServiceImpl implements StorageService {
    @Autowired
    private StorageTblMapper storageTblMapper ;
    private static Logger logger = LoggerFactory.getLogger(StorageServiceImpl.class) ;
    @Transactional
    @Override
    public void deduct(String productNo, int count) {
        logger.info("开始扣减商品{}的库存,数量为{}",productNo,count);
        //1 查询数据库里面该商品的库存
        StorageTbl storageTbl = storageTblMapper.selectOne(new LambdaQueryWrapper<StorageTbl>()
                .eq(StorageTbl::getCommodityCode, productNo));
        if(storageTbl==null){
            throw new IllegalArgumentException("商品不存在") ;
        }
        // 2 扣减操作
        int idleCount = storageTbl.getCount() - count ;
        if(idleCount< 0 ){
            throw  new RuntimeException("库存不足") ;
        }
        //3 设置到商品的库存里面
        storageTbl.setCount(idleCount);
        // 4 保存到数据库里面
        storageTblMapper.updateById(storageTbl) ;
        logger.info("扣减商品{}的库存成功,剩余的库存为{}" ,productNo, idleCount);
    }
}


五.测试(其中的数据表在项目下seata.sql 放到之前安装seata时创建的seata数据库运行即可)


1.启动四个服务(若有改动后重启只需重启order-service ,business-service)


2.还原数据库数据


20210616104311207.png

20210616104319620.png


3.正常测试:


2021061610433329.png

20210616104338801.png

20210616104345261.png


4.异常回滚测试:

20210616104404655.png


20210616104411454.png

20210616104415929.png


相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
目录
相关文章
|
JSON Java Nacos
SpringCloud 应用 Nacos 配置中心注解
在 Spring Cloud 应用中可以非常低成本地集成 Nacos 实现配置动态刷新,在应用程序代码中通过 Spring 官方的注解 @Value 和 @ConfigurationProperties,引用 Spring enviroment 上下文中的属性值,这种用法的最大优点是无代码层面侵入性,但也存在诸多限制,为了解决问题,提升应用接入 Nacos 配置中心的易用性,Spring Cloud Alibaba 发布一套全新的 Nacos 配置中心的注解。
1849 150
|
存储 Java Nacos
Spring Cloud+Nacos+KMS 动态配置最佳实践
本文讲述了 Spring Cloud 应用中结合 Nacos 实现了运行期配置动态更新的功能,以及在此基础上结合 KMS 在不改动代码的情况下对应用使用的敏感配置进行保护,解决将配置迁移到 Nacos 中可能存在的数据安全顾虑,并对其底层工作原理做了简单介绍。
1860 166
|
Cloud Native Java Nacos
微服务时代的新宠儿!Spring Cloud Nacos实战指南,带你玩转服务发现与配置管理,拥抱云原生潮流!
【8月更文挑战第29天】Spring Cloud Nacos作为微服务架构中的新兴之星,凭借其轻量、高效的特点,迅速成为服务发现、配置管理和治理的首选方案。Nacos(命名和配置服务)由阿里巴巴开源,为云原生应用提供了动态服务发现及配置管理等功能,简化了服务间的调用与依赖管理。本文将指导你通过五个步骤在Spring Boot项目中集成Nacos,实现服务注册、发现及配置动态管理,从而轻松搭建出高效的微服务环境。
912 0
|
网络协议 Java Nacos
SpringCloudAlibaba-Seata2.0.0与Nacos2.2.1
检查 Nacos 控制台,确认 Seata 服务器和 Spring Boot 应用已成功注册。 - 通过执行全局事务验证 Seata 的分布式事务管理功能。
1773 31
|
Cloud Native Java Nacos
springcloud/springboot集成NACOS 做注册和配置中心以及nacos源码分析
通过本文,我们详细介绍了如何在 Spring Cloud 和 Spring Boot 中集成 Nacos 进行服务注册和配置管理,并对 Nacos 的源码进行了初步分析。Nacos 作为一个强大的服务注册和配置管理平台,为微服务架构提供
5043 14
|
Java 关系型数据库 数据库
微服务SpringCloud分布式事务之Seata
SpringCloud+SpringCloudAlibaba的Seata实现分布式事务,步骤超详细,附带视频教程
1210 1
|
JSON SpringCloudAlibaba Java
Springcloud Alibaba + jdk17+nacos 项目实践
本文基于 `Springcloud Alibaba + JDK17 + Nacos2.x` 介绍了一个微服务项目的搭建过程,包括项目依赖、配置文件、开发实践中的新特性(如文本块、NPE增强、模式匹配)以及常见的问题和解决方案。通过本文,读者可以了解如何高效地搭建和开发微服务项目,并解决一些常见的开发难题。项目代码已上传至 Gitee,欢迎交流学习。
1658 1
Springcloud Alibaba + jdk17+nacos 项目实践
|
SQL NoSQL 数据库
SpringCloud基础6——分布式事务,Seata
分布式事务、ACID原则、CAP定理、Seata、Seata的四种分布式方案:XA、AT、TCC、SAGA模式
SpringCloud基础6——分布式事务,Seata
|
负载均衡 Java Nacos
SpringCloud基础2——Nacos配置、Feign、Gateway
nacos配置管理、Feign远程调用、Gateway服务网关
SpringCloud基础2——Nacos配置、Feign、Gateway
|
安全 Nacos 数据安全/隐私保护
升级指南:从Nacos 1.3.0 到 2.3.0,并兼容 Seata 的鉴权配置
本文详细介绍了如何在微服务环境下从 Nacos 1.3.0 升级到 2.3.0,并确保 Seata 各版本的兼容性。作者小米分享了升级过程中的关键步骤,包括备份配置、更新鉴权信息及验证测试等,并解答了常见问题。通过这些步骤,可以帮助读者顺利完成升级并提高系统的安全性与一致性。
596 8
升级指南:从Nacos 1.3.0 到 2.3.0,并兼容 Seata 的鉴权配置