前置操作
-
创建network,指定容器内部通信ip网段
# 172.20.0.0/16表示容器内部通信网段范围:172.20.0.0~172.20.255.55 docker network create --subnet=172.20.0.0/16 ht
安装nacos容器
-
创建一个临时容器,单机模式启动
docker run --name nacos-standalone -e MODE=standalone -p 8848:8848 -d -p 9848:9848 nacos/nacos-server:v2.3.0
-
拷贝容器内配置文件至宿主机
docker cp nacos-standalone:/home/nacos/conf C:\MY\Docker\nacos docker cp nacos-standalone:/home/nacos/data C:\MY\Docker\nacos docker cp nacos-standalone:/home/nacos/logs C:\MY\Docker\nacos
-
使用mysql存储nacos数据,导入nacos数据库
nacos数据库文件存放目录:容器内部 /home/nacos/conf/mysql-schema.sql
-
修改conf文件夹下的application.properties
# spring server.servlet.contextPath=${SERVER_SERVLET_CONTEXTPATH:/nacos} server.contextPath=/nacos server.port=${NACOS_APPLICATION_PORT:8848} server.tomcat.accesslog.max-days=30 server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D %{User-Agent}i %{Request-Source}i server.tomcat.accesslog.enabled=${TOMCAT_ACCESSLOG_ENABLED:false} server.error.include-message=ALWAYS # default current work dir server.tomcat.basedir=file:. #*************** Config Module Related Configurations ***************# ### Deprecated configuration property, it is recommended to use `spring.sql.init.platform` replaced. #spring.datasource.platform=${SPRING_DATASOURCE_PLATFORM:} spring.sql.init.platform=mysql nacos.cmdb.dumpTaskInterval=3600 nacos.cmdb.eventTaskInterval=10 nacos.cmdb.labelTaskInterval=300 nacos.cmdb.loadDataAtStart=false db.num=1 db.url.0=jdbc:mysql://mysql:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false db.user.0=root db.password.0=123456 ## DB connection pool settings db.pool.config.connectionTimeout=30000 db.pool.config.validationTimeout=10000 db.pool.config.maximumPoolSize=20 db.pool.config.minimumIdle=2 ### The auth system to use, currently only 'nacos' and 'ldap' is supported: #开启鉴权 nacos.core.auth.enabled=true nacos.core.auth.system.type=nacos ### worked when nacos.core.auth.system.type=nacos ### The token expiration in seconds: nacos.core.auth.plugin.nacos.token.expire.seconds=18000 ### The default token: #设置秘钥 随意Base64编码 长度至少32字节 nacos.core.auth.plugin.nacos.token.secret.key=WWxoc2RWbFhUblpqTWxKc1lsYzVlbVJJVm10bFVUMDk= ### Turn on/off caching of auth information. By turning on this switch, the update of auth information would have a 15 seconds delay. nacos.core.auth.caching.enabled=${NACOS_AUTH_CACHE_ENABLE:false} nacos.core.auth.enable.userAgentAuthWhite=${NACOS_AUTH_USER_AGENT_AUTH_WHITE_ENABLE:false} #随意配置下面两个属性,不配置启动报错 nacos.core.auth.server.identity.key=test nacos.core.auth.server.identity.value=test ## spring security config ### turn off security nacos.security.ignore.urls=${NACOS_SECURITY_IGNORE_URLS:/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/v1/auth/**,/v1/console/health/**,/actuator/**,/v1/console/server/**} # metrics for elastic search management.metrics.export.elastic.enabled=false management.metrics.export.influx.enabled=false nacos.naming.distro.taskDispatchThreadCount=10 nacos.naming.distro.taskDispatchPeriod=200 nacos.naming.distro.batchSyncKeyCount=1000 nacos.naming.distro.initDataRatio=0.9 nacos.naming.distro.syncRetryDelay=5000 nacos.naming.data.warmup=true nacos.console.ui.enabled=true nacos.core.param.check.enabled=true重点介绍下需要修改的配置:开启鉴权,使用数据库中账号和密码登录(默认:nacos/nacos)
spring.sql.init.platform=mysql db.num=1 db.url.0=jdbc:mysql://mysql:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false db.user.0=root db.password.0=123456 #开启鉴权 登录账号和密码在数据库中users表 nacos.core.auth.enabled=true #设置秘钥 随意Base64编码 长度至少32字节 nacos.core.auth.plugin.nacos.token.secret.key=WWxoc2RWbFhUblpqTWxKc1lsYzVlbVJJVm10bFVUMDk= #随意配置下面两个属性,不配置启动报错 nacos.core.auth.server.identity.key=test nacos.core.auth.server.identity.value=test
-
删除临时容器,创建正式nacos容器并挂载目录
使用单机模式,其余配置参考官方文档:Nacos Docker
docker run -itd \ --name nacos \ --network ht \ --ip 172.20.0.3 \ -e MODE=standalone \ -e JVM_XMS=512M -e JVM_XMX=512M -e JVM_XMN=256M \ -p 8848:8848 -p 9848:9848 -p 9849:9849 \ -v C:\MY\Docker\nacos\conf:/home/nacos/conf \ -v C:\MY\Docker\nacos\data:/home/nacos/data \ -v C:\MY\Docker\nacos\logs:/home/nacos/logs \ nacos/nacos-server:v2.3.0
- 访问http://localhost:8848/nacos,默认账户和密码:nacos/nacos
- 新建开发环境命名空间(根据自己需求,使用默认public也可以),名字自定义,ID尤为重要
安装seata容器
-
创建临时容器
docker run -itd --name seata-server -p 8091:8091 -p 7091:7091 seataio/seata-server:2.0.0 -
拷贝容器内resources目录下所有配置文件至宿主机,此处没有挂载seata日志文件,seata日志文件容器内部默认存储地址应该是 /root/logs/seata/(没有找到相关资料)
docker cp seata-server:/seata-server/resources/. C:\MY\Docker\seata\config
-
创建seata数据库,建表语句如下: 官方地址
-- -------------------------------- The script used when storeMode is 'db' -------------------------------- -- the table to store GlobalSession data CREATE TABLE IF NOT EXISTS `global_table` ( `xid` VARCHAR(128) NOT NULL, `transaction_id` BIGINT, `status` TINYINT NOT NULL, `application_id` VARCHAR(32), `transaction_service_group` VARCHAR(32), `transaction_name` VARCHAR(128), `timeout` INT, `begin_time` BIGINT, `application_data` VARCHAR(2000), `gmt_create` DATETIME, `gmt_modified` DATETIME, PRIMARY KEY (`xid`), KEY `idx_status_gmt_modified` (`status` , `gmt_modified`), KEY `idx_transaction_id` (`transaction_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; -- the table to store BranchSession data CREATE TABLE IF NOT EXISTS `branch_table` ( `branch_id` BIGINT NOT NULL, `xid` VARCHAR(128) NOT NULL, `transaction_id` BIGINT, `resource_group_id` VARCHAR(32), `resource_id` VARCHAR(256), `branch_type` VARCHAR(8), `status` TINYINT, `client_id` VARCHAR(64), `application_data` VARCHAR(2000), `gmt_create` DATETIME(6), `gmt_modified` DATETIME(6), PRIMARY KEY (`branch_id`), KEY `idx_xid` (`xid`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; -- the table to store lock data CREATE TABLE IF NOT EXISTS `lock_table` ( `row_key` VARCHAR(128) NOT NULL, `xid` VARCHAR(128), `transaction_id` BIGINT, `branch_id` BIGINT NOT NULL, `resource_id` VARCHAR(256), `table_name` VARCHAR(32), `pk` VARCHAR(36), `status` TINYINT NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking', `gmt_create` DATETIME, `gmt_modified` DATETIME, PRIMARY KEY (`row_key`), KEY `idx_status` (`status`), KEY `idx_branch_id` (`branch_id`), KEY `idx_xid` (`xid`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; CREATE TABLE IF NOT EXISTS `distributed_lock` ( `lock_key` CHAR(20) NOT NULL, `lock_value` VARCHAR(20) NOT NULL, `expire` BIGINT, primary key (`lock_key`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0); INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0); INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0); INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);
-
修改config目录下application.yml配置文件,使用nacos作为注册/配置中心,mysql存储事务日志,所有配置参考同目录下application.example.yml
# Copyright 1999-2019 Seata.io Group. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. server: port: 7091 spring: application: name: seata-server logging: config: classpath:logback-spring.xml file: path: ${log.home:${user.home}/logs/seata} extend: logstash-appender: destination: 127.0.0.1:4560 kafka-appender: bootstrap-servers: 127.0.0.1:9092 topic: logback_to_logstash console: user: username: seata password: seata seata: # 配置中心 nacos config: # support: nacos 、 consul 、 apollo 、 zk 、 etcd3 type: nacos nacos: # 容器内部ip 创建nacos容器时已指定 server-addr: 172.20.0.3:8848 namespace: dev group: DEV_GROUP username: nacos password: nacos # 新版nacos默认 /nacos前缀 context-path: /nacos ##if use MSE Nacos with auth, mutex with username/password attribute #access-key: #secret-key: # 配置中心文件名 data-id: seata-dev.properties # 注册中心 nacos registry: # support: nacos 、 eureka 、 redis 、 zk 、 consul 、 etcd3 、 sofa type: nacos preferred-networks: 30.240.* nacos: application: seata-server #nacos容器内部ip 可通过命名 docker inspect [容器名] 查看ip server-addr: 172.20.0.3:8848 group: DEV_GROUP namespace: dev cluster: default username: nacos password: nacos context-path: /nacos ##if use MSE Nacos with auth, mutex with username/password attribute #access-key: #secret-key: # server: # service-port: 8091 #If not configured, the default is '${server.port} + 1000' security: secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017 tokenValidityInMilliseconds: 1800000 ignore: urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login,/metadata/v1/**
- nacos注册中心创建配置文件,Data ID、Group和上述配置文件保持一致;配置文件内容可直接复制官方文档config.txt,此处只需配置store,其余配置基本都有默认值
#Transaction storage configuration, only for the server. The file, db, and redis configuration values are optional.
store.mode=db
store.lock.mode=db
store.session.mode=db
#Used for password encryption
#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
store.db.datasource=druid
store.db.dbType=mysql
#使用mysql8驱动
store.db.driverClassName=com.mysql.cj.jdbc.Driver
#设置时区
store.db.url=jdbc:mysql://mysql:3306/seata?serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
store.db.user=root
store.db.password=123456
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
-
创建正式容器,挂载目录
注:SEATA_IP指定seata-server启动的IP, 该IP用于向注册中心注册时使用;设置一个能容器外部能ping通的地址,在同一台服务器部署设置为本机ip地址; 从 Seata 2.0 开始,JDBC 驱动需放在
lib/jdbc/目录下,需自行在宿主机添加对应的jdbc驱动docker run -itd --name seata --network ht --ip 172.20.0.4 -e SEATA_IP=192.168.1.5 -p 8091:8091 -p 7091:7091 -v C:\MY\Docker\seata\config:/seata-server/resources C:\MY\Docker\seata\lib\jdbc:/lib/jdbc seataio/seata-server:2.0.0
- 查看nacos注册列表
- 查看注册中心ip
- 查看seata启动日志,观察nacos配置文件是否生效
Order模块
-
创建数据库,导入undo_log、order_tbl表
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; DROP TABLE IF EXISTS `order_tbl`; CREATE TABLE `order_tbl` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` varchar(255) DEFAULT NULL, `commodity_code` varchar(255) DEFAULT NULL, `count` int(11) DEFAULT 0, `money` int(11) DEFAULT 0, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
创建项目,加入依赖 (注:使用最新版nacos和seata可能会有兼容问题,仅练习用)
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.xxx</groupId> <artifactId>seata_at</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <groupId>com.xxx</groupId> <artifactId>order</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <dependencies> <!-- nacos --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <exclusions> <exclusion> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> </exclusion> </exclusions> </dependency> <!-- 使用2.3.0 nacos --> <dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- seata --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <exclusions> <exclusion> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> </exclusion> </exclusions> </dependency> <!-- 使用2.0.0 seata --> <dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency> <!-- loadbalancer --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> <!-- openfeign --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>${spring.boot.version}</version> </plugin> </plugins> </build> </project>
-
配置nacos、seata、feign
server: port: 8081 spring: application: name: order profiles: active: dev cloud: nacos: # 注册中心 discovery: server-addr: 127.0.0.1:8848 username: nacos password: nacos namespace: dev group: DEV_GROUP # 配置中心 config: server-addr: 127.0.0.1:8848 context-path: /nacos username: nacos password: nacos namespace: dev group: DEV_GROUP #☆ 配置文件后缀名 cloud项目启动时,自动调用nacos配置中心中DataID = ${spring.application.name}-{spring.profiles.active}.${spring.cloud.nacos.config.file-extension} (business-dev.yml) 文件 file-extension: yml # 使用nacos负载均衡 loadbalancer: nacos: enabled: true config: #☆ 导入Nacos配置中心的配置文件 import: - optional:nacos:${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}?refresh=true seata: enabled: true application-id: order-seata #设置分组名称 tx-service-group: shop_tx_group service: vgroup-mapping: # 当前分组的集群名称,seata单机模式集群名称默认为default shop_tx_group: default registry: type: nacos nacos: # 配置seata服务在注册中心服务名,默认为seata-server application: seata-server server-addr: 127.0.0.1:8848 context-path: /nacos username: nacos password: nacos namespace: dev group: DEV_GROUP cluster: default feign: client: config: # default:对所有服务生效 需要对单个服务配置将default改为服务名 default: # 设置连接超时时间 connect-timeout: 3000 # 设置读取数据超时时间 read-timeout: 2000 # 调用日志打印等级,需要同步将Feign调用类的日志等级设置为Debug才生效 logger-level: basic -
nacos配置中心创建order-dev.yml文件,并配置数据库信息
-
启动类添加@EnableDiscoveryClient、@EnableFeignClients注解
@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } }
-
创建通用模块,编写全局异常处理
@RestControllerAdvice public class GlobalExceptionHandle { //ResponseStatus必须写,否则远程调用默认成功 @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler(ServiceException.class) public R serviceException(ServiceException serviceException){ return R.fail(serviceException.getMessage()); } }
-
使用OpenFeign远程调用
// nacos注册名 @FeignClient("account") public interface AccountClient { /** * 注:@RequestParam注解不能少,否则会报错 */ @GetMapping("/account/debit") R debit(@RequestParam("userId") String userId, @RequestParam("money") Integer money); }
-
编写生成订单接口
@RestController public class OrderController { @Resource private IOrderService orderService; /** * 创建订单 */ @GetMapping("/create") public R create(String userId, String commodityCode, Integer orderCount){ orderService.create(userId,commodityCode,orderCount); return R.success(); } }@Service public class OrderServiceImpl implements IOrderService { @Resource private AccountClient accountClient; @Resource private OrderMapper orderMapper; //添加@GlobalTransactional注解,实现全局事务,默认为AT模式 @GlobalTransactional @Override public void create(String userId, String commodityCode, Integer orderCount) { try { //先创建订单再扣减余额,方便测试 //计算总费用 int orderMoney = calculate(commodityCode, orderCount); //生成订单 Order order = new Order(); order.setUserId(userId); order.setCommodityCode(commodityCode); order.setCount(orderCount); order.setMoney(orderMoney); int isCreate = orderMapper.insert(order); if(isCreate<=0){ throw new ServiceException("订单创建失败"); } //扣减余额 accountClient.debit(userId,orderMoney); } catch (Exception e) { throw new ServiceException(e.getMessage()); } } private int calculate(String commodityCode, int orderCount) { //假设每件商品500 return 500*orderCount; } }
- 测试接口,发生异常,事务回滚
Account模块
-
数据库
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; DROP TABLE IF EXISTS `order_tbl`; CREATE TABLE `order_tbl` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` varchar(255) DEFAULT NULL, `money` int(11) DEFAULT 0, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -
配置和Order模块一致
-
编写扣减余额接口
/** * 扣减余额 */ @GetMapping("/debit") public R debit(String userId, Integer money){ accountService.debit(userId,money); return R.success(); }
Gitee地址:seata_at: seata AT模式 (gitee.com)