说在前面
本文基于seata1.6.1 AT模式,以nacos作为配置、注册中心。本篇我们先使用,正确使用后再看下一篇相关概念和原理,这样可以掌握的更透彻。
1、seata服务的安装与配置
下载seata-server:https://seata.io/zh-cn/blog/download.html
解压后找到conf目录下的application.yml文件,从示例文件中找我们需要的配置复制到application.yml
温馨提示:复制以下代码注意修改【namespace】【data-id】【url】【user】【password】
server: port: 7091 spring: application: name: seata-server logging: config: classpath:logback-spring.xml file: path: ${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: config: # support: nacos 、 consul 、 apollo 、 zk 、 etcd3 type: nacos nacos: server-addr: 127.0.0.1:8848 namespace: 434e7801-0b5d-4fd2-b96a-acbac849fd84 group: SEATA_GROUP username: password: context-path: ##if use MSE Nacos with auth, mutex with username/password attribute #access-key: #secret-key: data-id: seataServer.properties registry: # support: nacos 、 eureka 、 redis 、 zk 、 consul 、 etcd3 、 sofa type: nacos preferred-networks: 30.240.* nacos: application: seata-server server-addr: 127.0.0.1:8848 group: SEATA_GROUP namespace: 434e7801-0b5d-4fd2-b96a-acbac849fd84 cluster: default username: password: context-path: ##if use MSE Nacos with auth, mutex with username/password attribute #access-key: #secret-key: store: # support: file 、 db 、 redis mode: db db: datasource: druid db-type: mysql driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true&serverTimezone=GMT user: root password: 1234 min-conn: 10 max-conn: 100 global-table: global_table branch-table: branch_table lock-table: lock_table distributed-lock-table: distributed_lock query-limit: 1000 max-wait: 5000 # 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,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
此时我们去nacos中新建一个配置,这里的Data ID就是上述配置中需要的。
配置文件的内容:【注意修改url、user、password】
#Transaction routing rules configuration, only for the client service.vgroupMapping.default_tx_group=default service.default.grouplist=127.0.0.1:8091 # 数据存储方式,db代表数据库 store.mode=db store.db.datasource=druid store.db.dbType=mysql store.db.driverClassName=com.mysql.cj.jdbc.Driver store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true&serverTimezone=GMT store.db.user=root store.db.password=1234 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 # 事务、日志等配置 server.recovery.committingRetryPeriod=3000 server.recovery.asynCommittingRetryPeriod=3000 server.recovery.rollbackingRetryPeriod=3000 server.recovery.timeoutRetryPeriod=3000 server.maxCommitRetryTimeout=-1 server.maxRollbackRetryTimeout=-1 server.rollbackRetryTimeoutUnlockEnable=false server.undo.logSaveDays=7 server.undo.logDeletePeriod=86400000 # 客户端与服务端传输方式 transport.serialization=seata transport.compressor=none # 关闭metrics功能,提高性能 metrics.enabled=false metrics.registryType=compact metrics.exporterList=prometheus metrics.exporterPrometheusPort=9898
注意【service.vgroupMapping.default_tx_group=default】,后续需要使用。
我这里是用的windows,直接去bin目录下双击seata-server.bat
之后去nacos看一下,有健康实例就说明seata服务注册成功了。
2、使用seata
首先是sql脚本,在seata服务目录的\script\server\db下找到mysql的脚本执行一下
除此之外,我们还需要一个undo_log表
SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS =0;-- ------------------------------ Table structure for undo_log-- ----------------------------DROPTABLE IF EXISTS `undo_log`;CREATETABLE `undo_log` ( `id` bigint(20)NOTNULL AUTO_INCREMENT, `branch_id` bigint(20)NOTNULL, `xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOTNULL, `context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOTNULL, `rollback_info` longblobNOTNULL, `log_status` int(11)NOTNULL, `log_created` datetimeNOTNULL, `log_modified` datetimeNOTNULL, PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT =12 CHARACTER SET= utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;SET FOREIGN_KEY_CHECKS =1;
以及我们的两张业务表
SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS =0;-- ------------------------------ Table structure for sys_order-- ----------------------------DROPTABLE IF EXISTS `sys_order`;CREATETABLE `sys_order` ( `id` bigint(20)UNSIGNEDNOTNULL, `product_id` bigint(20)UNSIGNEDNULL DEFAULT NULL, `number` int(10)UNSIGNEDNULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET= utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;SET FOREIGN_KEY_CHECKS =1;SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS =0;-- ------------------------------ Table structure for sys_stock-- ----------------------------DROPTABLE IF EXISTS `sys_stock`;CREATETABLE `sys_stock` ( `id` bigint(20)UNSIGNEDNOTNULL, `product_id` bigint(20)UNSIGNEDNULL DEFAULT NULL, `number` int(10)UNSIGNEDNULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET= utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;-- ------------------------------ Records of sys_stock-- ----------------------------INSERTINTO `sys_stock` VALUES(1629394254497902593,1629394038201819138,10);SET FOREIGN_KEY_CHECKS =1;
然后创建一个订单服务、一个库存服务,两个服务的pom依赖引入一致,我这里springboot版本号是2.2.5.RELEASE
<!-- nacos-discovery --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId><version>2.2.1.RELEASE</version></dependency><!-- nacos-config --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId><version>2.2.1.RELEASE</version></dependency><!-- org.springframework.cloud/spring-cloud-starter-openfeign --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId><version>2.2.2.RELEASE</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.2</version></dependency><!-- mysql-connector-java --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version><scope>provided</scope></dependency><!-- seata-spring-boot-starter --><dependency><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId><version>1.6.1</version></dependency><!-- druid-spring-boot-starter --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.10</version></dependency>
库存项目代码:properties 相关的值自己改一下。
spring.cloud.nacos.discovery.server-addr=localhost:8848 spring.cloud.nacos.discovery.namespace=434e7801-0b5d-4fd2-b96a-acbac849fd84 spring.cloud.nacos.discovery.group=SEATA_GROUP spring.application.name=sparrow-stock server.port=8082 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver #spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=1234 ##要和seataServer.properties中的保持一致seata.tx-service-group=default_tx_group seata.service.vgroup-mapping.default_tx_group=default
//-----------controller
"/stock") (publicclassStockController { privateStockServicestockService; "/minus") (publicStringminus( ("productId") LongproductId){ returnstockService.minus(productId); } }
//---------entity
"sys_stock") (publicclassStock { privateLongid; "product_id") (privateLongproductId; "number") (privateIntegernumber; }
//------------service
publicinterfaceStockServiceextendsIService<Stock> { Stringminus(LongproductId); }
//-------------impl
"stockService") (publicclassStockServiceImplextendsServiceImpl<StockMapper, Stock>implementsStockService { privateStockMapperstockMapper; publicStringminus(LongproductId) { //int a =1/0;System.out.println("----------"+RootContext.getXID()); stockMapper.minus(productId); return"库存-1"; } }
//-------------mapper
publicinterfaceStockMapperextendsBaseMapper<Stock> { "update sys_stock set number=number-1 where product_id=#{productId}") (Integerminus(LongproductId); }
订单服务代码:
spring.cloud.nacos.discovery.server-addr=localhost:8848 spring.cloud.nacos.discovery.namespace=434e7801-0b5d-4fd2-b96a-acbac849fd84 spring.cloud.nacos.discovery.group=SEATA_GROUP spring.application.name=sparrow-order server.port=8083 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver #spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=1234 ##要和seataServer.properties中的保持一致seata.tx-service-group=default_tx_group seata.service.vgroup-mapping.default_tx_group=default
//--------------Controller
publicclassOrderController { privateOrderServiceorderService; "/add") (publicStringadd(LongproductId){ returnorderService.add(productId); } }
//--------------entity
"sys_order") (publicclassOrder { privateLongid; "product_id") (privateLongproductId; "number") (privateIntegernumber; }
//-----------feign
value="sparrow-stock",path="/stock") (publicinterfaceStockService { "/minus") (Stringminus( ("productId")LongproductId); }
//----------service
publicinterfaceOrderServiceextendsIService<Order> { Stringadd(LongproductId); }
//-----------impl
"orderService") (publicclassOrderServiceImplextendsServiceImpl<OrderMapper, Order>implementsOrderService { privateStockServicestockService; publicStringadd(LongproductId) { System.out.println("----------"+RootContext.getXID()); //减库存stockService.minus(productId); Orderorder=newOrder(); order.setId(IdWorker.getId()); order.setProductId(productId); order.setNumber(1); this.save(order); inta=1/0; return"商品下单成功"; } }
//-------------mapper
publicinterfaceOrderMapperextendsBaseMapper<Order> { }
先演示不用seata,使用spring事务注解@Transactional
两种情况:1、本地服务代码先执行,然后再调远程服务接口,比如我在减库存的时候失败了,其实两边数据都会回滚
2、调用远程服务接口后,还需要执行本地服务的业务代码。比如先减库存成功了,但是订单服务这边还需要执行其他业务,但是这些业务失败了,那么订单这里会回滚数据,库存服务那边就没法回滚了。这里为了方便演示,我把库存业务放到前面去,结果就是库存减成功了,而订单服务这边数据回滚了。
使用seata的@GlobalTransactional 在本地服务的controller上加。
再次调用接口,库存表中的数量没有减少
想看undo_log表中数据是临时数据,想看的话可以debug。至此,配置与使用篇完结。下一篇我们看看seata的相关概念和实现原理。