目录
一、seata服务端搭建
1、下载seata-server
2、seata服务端启动
1、修改config目录下的file.conf
2.修改config目录下registry.conf
3、mysql新建库和表
4、初始化seata配置
1、修改config.txt配置
2、配置添加到nacos
5、启动seata
二、微服务整合seata
1、新建微服务,引入相关依赖
1、微服务pom.xml
2、seata模块pom.xml
2、子模块pom.xml
3、子模块yml配置
4、建立数据库
5、业务模块
1、库存服务
2、订单模块
6、测试验证
选择微服务配套版本的seata-server下载,根据需要选择windows或者linux版本的。
Releases · seata/seata · GitHub
组件版本对照表:
本地搭建我下载的是1.3.0的windows版本
实际使用需要将事务的一些相关信息存到数据库,启动seata前需要修改相关配置。
mode改为db
db模块修改数据库的url,user,password
## transaction log store, only used in seata-server
store {
## store mode: file、db、redis
mode = "db"## file store property
file {
## store location dir
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
maxBranchSessionSize = 16384
# globe session size , if exceeded throws exceptions
maxGlobalSessionSize = 512
# file buffer size , if exceeded allocate new buffer
fileWriteBufferCacheSize = 16384
# when recover batch read size
sessionReloadReadSize = 100
# async, sync
flushDiskMode = async
}## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://101.37.23.0:3306/seata"
user = "root"
password = "nymi@2023"
minConn = 5
maxConn = 30
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}## redis store property
redis {
host = "127.0.0.1"
port = "6379"
password = ""
database = "0"
minConn = 1
maxConn = 10
queryLimit = 100
}}
此配置是将seata注册到注册中心,seata的配置持久化到配置中心。
我们使用nacos作为注册中心和配置中心。
需要分别将type改为nacos并修改nacos下的配置。
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"nacos {
application = "seata-server"
serverAddr = "101.37.23.0:80"
group = "SEATA_GROUP"
namespace = ""
cluster = "default"
username = "nacos"
password = "nacos"
}
eureka {
serviceUrl = "http://localhost:8761/eureka"
application = "default"
weight = "1"
}
redis {
serverAddr = "localhost:6379"
db = 0
password = ""
cluster = "default"
timeout = 0
}
zk {
cluster = "default"
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
}
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 = "nacos"nacos {
serverAddr = "101.37.23.0:80"
namespace = ""
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
appId = "seata-server"
apolloMeta = "http://192.168.1.204:8801"
namespace = "application"
}
zk {
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}
下载seata官网的对应版本的源码。
下载中心
解压过后复制整个script目录放到seata-server解压后的文件夹下。
进入script/server/db目录,造第1步配置的mysql数据库(seata)中运行mysql.sql脚本(mysql需要先新建名为seata的数据库)
打开第3步下的 script/config-center目录,
修改service.vgroupMapping.guangzhou=default,其中guangzhou处的配置要和项目的微服务一致,后续application.yml里会讲到。
修改store.mode=db
修改store.db.后面的一些数据库相关的配置
确保nacos已经启动。
进入script/config-center/nacos目录,
在此处打开git(.sh文件windows下借助git可以运行)
运行下面的命令(ip和port替换为实际的)
sh nacos-config.sh -h ip -p port -g SEATA_GROUP -t seata-group
进入seata/bin目录,
双击seata-server.bat文件,cmd窗口会显示seata-server的启动情况。
启动成功后,可以在nacos客户端看到seata-server服务已经注册进来了。
4.0.0 seata com.wind.springcloud alibaba 0.0.1-SNAPSHOT alibaba springcloudalibaba pom 1.8 2.2.5.RELEASE 2.3.11.RELEASE Hoxton.SR8 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-test test com.alibaba.cloud spring-cloud-alibaba-dependencies ${spring.cloud.alibaba.version} pom import org.springframework.boot spring-boot-starter-parent ${spring.boot.version} pom import org.springframework.cloud spring-cloud-dependencies ${spring.cloud.version} pom import org.springframework.boot spring-boot-maven-plugin
alibaba com.wind.springcloud 0.0.1-SNAPSHOT 4.0.0 com.wind.springcloud seata 0.0.1-SNAPSHOT order-seata stock-seata alibaba-order-seata alibaba-stock-seata org.springframework.boot spring-boot-starter-jdbc org.springframework.boot spring-boot-starter-web org.mybatis.spring.boot mybatis-spring-boot-starter 2.1.4 mysql mysql-connector-java com.alibaba druid-spring-boot-starter 1.2.3 com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.cloud spring-cloud-starter-openfeign com.alibaba.cloud spring-cloud-starter-alibaba-seata
新建子模块alibaba-order-seata和alibaba-stock-seata
pom分别是
seata com.wind.springcloud 0.0.1-SNAPSHOT 4.0.0 alibaba-order-seata org.projectlombok lombok 1.18.20 compile
seata com.wind.springcloud 0.0.1-SNAPSHOT 4.0.0 alibaba-stock-seata org.projectlombok lombok 1.18.20 compile
分别如下,可以看到呼应上面的guangzhou配置
server: port: 8200 spring: application: name: order cloud: nacos: server-addr: 101.37.23.0:80 discovery: username: nacos password: nacos alibaba: seata: tx-service-group: guangzhou datasource: username: root password: nymi@2023 driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://101.37.23.0:3306/seata_order?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false&serverTimezone=Asia/Shanghai mybatis: mapper-locations: classpath:/mappers/**/*Mapper.xml seata: registry: type: nacos nacos: server-addr: 101.37.23.0:80 application: seata-server username: nacos password: nacos group: SEATA_GROUP config: type: nacos nacos: server-addr: 101.37.23.0:80 username: nacos password: nacos group: SEATA_GROUP
server: port: 8201 spring: application: name: stock cloud: nacos: server-addr: 101.37.23.0:80 discovery: username: nacos password: nacos alibaba: seata: tx-service-group: guangzhou datasource: username: root password: nymi@2023 driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://101.37.23.0:3306/seata_stock?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false&serverTimezone=Asia/Shanghai mybatis: mapper-locations: classpath:/mappers/**/*Mapper.xml seata: registry: type: nacos nacos: server-addr: 101.37.23.0:80 application: seata-server username: nacos password: nacos group: SEATA_GROUP config: type: nacos nacos: server-addr: 101.37.23.0:80 username: nacos password: nacos group: SEATA_GROUP
分别建立对应yml配置的数据库,
两个数据库均执行 seata\script\client\at\db中的mysql.sql的脚本
开发库存相关的接口
核心代码如下
分别是Stock.class
package com.wind.model;
import lombok.Data;
/**
* @author dongguanghui
* @date 2023/6/29 14:20
*/
@Data
public class Stock {
private Integer id;
private Integer productId;
private Integer count;
}
StockServiceImpl.class
package com.wind.service.impl;
import com.wind.mapper.StockMapper;
import com.wind.service.StockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author dongguanghui
* @date 2023/6/29 14:19
*/
@Service
public class StockServiceImpl implements StockService {
@Autowired
StockMapper stockMapper;
public void reduct(Integer productId) {
stockMapper.reduct(productId);
System.out.println("更新商品:"+productId);
}
}
stockMapper.xml
update stock set `count` = `count` -1
where product_id = #{productId}
实现类的方法上有全局事务的注解,启动类配置开启全局事务注解
订单模块调用库存
主要代码
OrderSeataApplication.class
package com.wind;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.client.RestTemplate;
/**
* @author dongguanghui
* @date 2023/6/29 14:17
*/
@SpringBootApplication
@EnableFeignClients
@EnableTransactionManagement
public class OrderSeataApplication {
public static void main(String[] args) {
SpringApplication.run(OrderSeataApplication.class,args);
}
}
OrderController.class
package com.wind.controller;
import com.wind.model.Order;
import com.wind.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author dongguanghui
* @date 2023/6/29 11:53
*/
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
OrderService orderService;
@RequestMapping("/add")
public String add() {
Order order = new Order();
order.setProductId(9);
order.setStatus(0);
order.setTotalAmount(100);
orderService.create(order);
return "下单成功";
}
}
OrderServiceImpl.class
package com.wind.service.impl;
import com.wind.api.StockService;
import com.wind.mapper.OrderMapper;
import com.wind.model.Order;
import com.wind.service.OrderService;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
/**
* @author dongguanghui
* @date 2023/6/29 14:19
*/
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
OrderMapper orderMapper;
@Autowired
StockService stockService;
@GlobalTransactional
public Order create(Order order) {
// 插入订单
orderMapper.insert(order);
// 扣减库存
stockService.reduct(order.getProductId());
int a=1/0;
return order;
}
}
Order.class
package com.wind.model;
import lombok.Data;
/**
* @author dongguanghui
* @date 2023/6/29 14:20
*/
@Data
public class Order {
private Integer id;
private Integer productId;
private Integer status;
private Integer totalAmount;
}
StockService.class
package com.wind.api;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @author dongguanghui
* @date 2023/6/29 17:26
*/
@FeignClient(value = "stock",path = "/stock")
public interface StockService {
@RequestMapping("/reduct")
public String reduct(@RequestParam("productId") Integer productId);
}
OrderMapper.xml
insert into `order` (product_id,`status`,total_amount)
values
(#{productId},#{status},#{totalAmount})
在int a=1/0;打断点。
当执行到时,数据库将会改变数据,undo_log表会存原始数据和修改后数据的日志。
继续执行代码下一行,报错。
此时,事务回滚,回滚后undo_log数据清空。
如果int a=1/0; 改为int a=1;
则代码执行到这一行与前面一致,
继续执行下一行,正常。
此时,提交事务,undo_log数据清空。
至此,分布式事务环境搭建完毕