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

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 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


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
15天前
|
负载均衡 Java 开发者
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
59 5
|
5月前
|
NoSQL Java Nacos
SpringCloud集成Seata并使用Nacos做注册中心与配置中心
SpringCloud集成Seata并使用Nacos做注册中心与配置中心
167 3
|
3月前
|
SQL NoSQL 数据库
SpringCloud基础6——分布式事务,Seata
分布式事务、ACID原则、CAP定理、Seata、Seata的四种分布式方案:XA、AT、TCC、SAGA模式
SpringCloud基础6——分布式事务,Seata
|
4月前
|
安全 Nacos 数据安全/隐私保护
升级指南:从Nacos 1.3.0 到 2.3.0,并兼容 Seata 的鉴权配置
本文详细介绍了如何在微服务环境下从 Nacos 1.3.0 升级到 2.3.0,并确保 Seata 各版本的兼容性。作者小米分享了升级过程中的关键步骤,包括备份配置、更新鉴权信息及验证测试等,并解答了常见问题。通过这些步骤,可以帮助读者顺利完成升级并提高系统的安全性与一致性。
138 8
升级指南:从Nacos 1.3.0 到 2.3.0,并兼容 Seata 的鉴权配置
|
2月前
|
负载均衡 Java API
【Spring Cloud生态】Spring Cloud Gateway基本配置
【Spring Cloud生态】Spring Cloud Gateway基本配置
47 0
|
4月前
|
Java Spring
【Azure Spring Cloud】Spring Cloud Azure 4.0 调用Key Vault遇见认证错误 AADSTS90002: Tenant not found.
【Azure Spring Cloud】Spring Cloud Azure 4.0 调用Key Vault遇见认证错误 AADSTS90002: Tenant not found.
|
4月前
|
关系型数据库 MySQL 数据库
SpringCloud2023中使用Seata解决分布式事务
对于分布式系统而言,需要保证分布式系统中的数据一致性,保证数据在子系统中始终保持一致,避免业务出现问题。分布式系统中对数据的操作要么一起成功,要么一起失败,必须是一个整体性的事务。Seata简化了这个使用过程。
94 2
|
4月前
|
Java Spring 容器
【Azure Spring Cloud】在Azure Spring Apps上看见 App Memory Usage 和 jvm.menory.use 的指标的疑问及OOM
【Azure Spring Cloud】在Azure Spring Apps上看见 App Memory Usage 和 jvm.menory.use 的指标的疑问及OOM
|
4月前
|
存储 Java Spring
【Azure Spring Cloud】Azure Spring Cloud服务,如何获取应用程序日志文件呢?
【Azure Spring Cloud】Azure Spring Cloud服务,如何获取应用程序日志文件呢?
|
4月前
|
SQL Java 数据库连接
【Azure Spring Cloud】Azure Spring Cloud connect to SQL using MSI
【Azure Spring Cloud】Azure Spring Cloud connect to SQL using MSI