1、首先安装nacos
https://github.com/alibaba/naco ,下载后解压,本地新建数据库nacos-config,执行在naocs/conf下面的mysql-schema.sql,启动前修改bin目录下的startup.cmd中 set MODE = ”cluster“为 set MODE = ”standalone"然后双击启动
2、下载seata服务端
我这里下载的seata 1.7,本地新建数据库seata,然后在解压的seata文件找到script->server->db->mysql.sql,执行这个sql脚本。
- 在nacos上添加新的命名空间
- 修改配置文件config.txt并加载到nacos上
config.txt文件在seata/script/config-center目录下,需要修改的地方如下:
store.mode=db
store.lock.mode=db
store.session.mode=db
store.db 数据库的配置修改成自己的
我的完整配置如下:
在这#For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html
#Transport configuration, for client and server
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableTmClientBatchSendRequest=false
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.rpcRmRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
transport.serialization=seata
transport.compressor=none
#Transaction routing rules configuration, only for the client
service.vgroupMapping.default_tx_group=default
#If you use a registry, you can ignore it
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
#Transaction rule configuration, only for the client
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=true
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
#For TCC transaction mode
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h
#Log rule configuration, for client and server
log.exceptionRate=100
#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
store.publicKey=
#If `store.mode,store.lock.mode,store.session.mode` are not equal to `file`, you can remove the configuration block.
store.file.dir=file_store/data
store.file.maxBranchSessionSize=16384
store.file.maxGlobalSessionSize=512
store.file.fileWriteBufferCacheSize=16384
store.file.flushDiskMode=async
store.file.sessionReloadReadSize=100
#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
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%2B8
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
#These configurations are required if the `store mode` is `redis`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `redis`, you can remove the configuration block.
store.redis.mode=single
store.redis.single.host=127.0.0.1
store.redis.single.port=6379
store.redis.sentinel.masterName=
store.redis.sentinel.sentinelHosts=
store.redis.maxConn=10
store.redis.minConn=1
store.redis.maxTotal=100
store.redis.database=0
store.redis.password=
store.redis.queryLimit=100
#Transaction rule configuration, only for the server
#二阶段提交未完成状态全局事务重试提交线程间隔时间 默认1000,单位毫秒,我改为60000
server.recovery.committingRetryPeriod=60000
#二阶段异步提交状态重试提交线程间隔时间 默认1000,单位毫秒
server.recovery.asynCommittingRetryPeriod=60000
#二阶段回滚状态重试回滚线程间隔时间 默认1000,单位毫秒
server.recovery.rollbackingRetryPeriod=60000
#超时状态检测重试线程间隔时间 默认1000,单位毫秒,检测出超时将全局事务置入回滚会话管理器
server.recovery.timeoutRetryPeriod=60000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
server.xaerNotaRetryTimeout=60000
server.session.branchAsyncQueueSize=5000
server.session.enableBranchAsyncRemove=false
server.enableParallelRequestHandle=false
#Metrics configuration, only for the server
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
里插入代码片
进入seata目录,找到nacos-config.sh,路径为:script->config-center->nacos->nacos-config.sh
进入cmd窗口,执行以下命令:
sh nacos-config.sh -h 本机ip -p 8848 -g SEATA_GROUP -t 7dfa67c0-8296-4fc1-b6a2-a9a9020d7cea -u nacos -w nacos
-h nacos服务IP
-p nacos服务端口
-u nacos登录名
-w nacos登录密码
-g nacos 配置的分组名称,默认设置SEATA_GROUP
-t 上一步配置的命名空间ID
- 修改seata 配置文件
进入seata/conf目录下,有两个配置文件,把application.yml 随意修改一个名字,然后把 application.example.yml修改成application.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: ${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: 7dfa67c0-8296-4fc1-b6a2-a9a9020d7cea
group: SEATA_GROUP
username: nacos
password: nacos
context-path:
##if use MSE Nacos with auth, mutex with username/password attribute
#access-key:
#secret-key:
data-id: seataServer.properties
#consul:
# server-addr: 127.0.0.1:8500
# acl-token:
# key: seata.properties
#apollo:
# appId: seata-server
# apollo-meta: http://192.168.1.204:8801
# apollo-config-service: http://192.168.1.204:8080
# namespace: application
# apollo-access-key-secret:
# cluster: seata
#zk:
# server-addr: 127.0.0.1:2181
# session-timeout: 6000
# connect-timeout: 2000
# username:
# password:
# node-path: /seata/seata.properties
#etcd3:
# server-addr: http://localhost:2379
# key: seata.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: 7dfa67c0-8296-4fc1-b6a2-a9a9020d7cea
cluster: default
username: nacos
password: nacos
#context-path:
##if use MSE Nacos with auth, mutex with username/password attribute
#access-key:
#secret-key:
#eureka:
# service-url: http://localhost:8761/eureka
# application: default
# weight: 1
#redis:
# server-addr: localhost:6379
# db: 0
# password:
# cluster: default
# timeout: 0
#zk:
# cluster: default
# server-addr: 127.0.0.1:2181
# session-timeout: 6000
# connect-timeout: 2000
# username:
# password:
#consul:
# cluster: default
# server-addr: 127.0.0.1:8500
# acl-token:
#etcd3:
# cluster: default
# server-addr: http://localhost:2379
#sofa:
# server-addr: 127.0.0.1:9603
# application: default
# region: DEFAULT_ZONE
# datacenter: DefaultDataCenter
# cluster: default
# group: SEATA_GROUP
# address-wait-time: 3000
server:
service-port: 8091 #If not configured, the default is '${server.port} + 1000'
max-commit-retry-timeout: -1
max-rollback-retry-timeout: -1
rollback-retry-timeout-unlock-enable: false
enable-check-auth: true
enable-parallel-request-handle: true
retry-dead-threshold: 130000
xaer-nota-retry-timeout: 60000
enableParallelRequestHandle: true
recovery:
committing-retry-period: 1000
async-committing-retry-period: 1000
rollbacking-retry-period: 1000
timeout-retry-period: 1000
undo:
log-save-days: 7
log-delete-period: 86400000
session:
branch-async-queue-size: 5000 #branch async remove queue size
enable-branch-async-remove: false #enable to asynchronous remove branchSession
store:
# support: file 、 db 、 redis
mode: db
session:
mode: db
lock:
mode: db
#file:
# dir: sessionStore
# max-branch-session-size: 16384
# max-global-session-size: 512
# file-write-buffer-cache-size: 16384
# session-reload-read-size: 100
# flush-disk-mode: async
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true&useSSL=false
user: root
password: 123456
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
#redis:
# mode: single
# database: 0
# min-conn: 10
# max-conn: 100
# password:
# max-total: 100
# query-limit: 1000
# single:
# host: 127.0.0.1
# port: 6379
# sentinel:
# master-name:
# sentinel-hosts:
metrics:
enabled: false
registry-type: compact
exporter-list: prometheus
exporter-prometheus-port: 9898
transport:
rpc-tc-request-timeout: 15000
enable-tc-server-batch-send-response: false
shutdown:
wait: 3
thread-factory:
boss-thread-prefix: NettyBoss
worker-thread-prefix: NettyServerNIOWorker
boss-thread-size: 1
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login
4.启动
找到seata-server.bat,点击启动,路径为:seata->bin->seata-server.bat,成功后可以在nacos控制台服务列表中看到多了一个服务。
5.客户端集成seata
这里只给出order的代码,并给出可能的坑
- 实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private Long id;
private Long userId;
private Long productId;
private Integer count;
private BigDecimal money;
private Integer status; //订单状态:0:创建中;1:已完结
}
- 返回类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
private Integer code;
private String message;
private T data;
public CommonResult(Integer code, String message) {
this(code, message, null);
}
}
- 配置类
@Configuration
public class DataSourceProxyConfig {
@Value("${mybatis.mapperLocations}")
private String mapperLocations;
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource(){
return new DruidDataSource();
}
@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
return sqlSessionFactoryBean.getObject();
}
}
public class MultipartSupportConfig implements RequestInterceptor {
/**
* 解决服务直接调用请求头不传递的问题
*
* @param template
*/
@Override
public void apply(RequestTemplate template) {
// 解决seata的xid未传递
String xid = RootContext.getXID();
template.header(RootContext.KEY_XID, xid);
}
}
@Configuration
@MapperScan({"com.oetsky.seata.mapper"})
public class MybatisConfig {
}
- 控制层
@RestController
public class OrderController {
@Resource
private OrderService orderService;
@GetMapping("/order/create")
public CommonResult create(Order order) {
orderService.create(order);
return new CommonResult(200, "订单创建成功");
}
}
- 服务层
public interface OrderService {
void create(Order order);
}
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Resource
private OrderMapper orderMapper;
@Resource
private AccountService accountService;
@Resource
private StorageService storageService;
@Override
// @GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class,timeoutMills = 100000)
public void create(Order order) {
System.out.println(RootContext.getXID());
log.info("----->开始新建订单");
//1 新建订单
orderMapper.create(order);
//2 扣减库存
log.info("----->订单微服务开始调用库存,做扣减Count");
storageService.decrease(order.getProductId(), order.getCount());
log.info("----->订单微服务开始调用库存,做扣减end");
//3 扣减账户
log.info("----->订单微服务开始调用账户,做扣减Money");
accountService.decrease(order.getUserId(), order.getMoney());
log.info("----->订单微服务开始调用账户,做扣减end");
//4 修改订单状态,从零到1,1代表已经完成
log.info("----->修改订单状态开始");
orderMapper.update(order.getUserId(), 0);
log.info("----->修改订单状态结束");
log.info("----->下订单结束了,O(∩_∩)O哈哈~");
}
}
@FeignClient(value = "account",configuration = MultipartSupportConfig.class)
public interface AccountService {
@PostMapping(value = "/account/decrease")
CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}
@FeignClient(value ="storage",configuration = MultipartSupportConfig.class)
public interface StorageService {
@PostMapping(value = "/storage/decrease")
CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
}
- 启动类
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableFeignClients
@EnableDiscoveryClient
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class,args);
}
}
- mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.oetsky.seata.mapper.OrderMapper">
<resultMap id="order" type="com.oetsky.seata.bean.Order">
<result property="id" column="id" jdbcType="BIGINT"></result>
<result property="userId" column="user_id" jdbcType="BIGINT"></result>
<result property="productId" column="product_id" jdbcType="BIGINT"></result>
<result property="count" column="count" jdbcType="INTEGER"></result>
<result property="money" column="money" jdbcType="BIGINT"></result>
<result property="status" column="status" jdbcType="INTEGER"></result>
</resultMap>
<insert id="create">
insert into t_order(user_id, product_id, count, money, status)
value (#{userId},#{productId},#{count},#{money},0)
</insert>
<update id="update">
update t_order
set status = 1
where user_id = #{userId}
and status = #{status}
</update>
</mapper>
- application.yml
server:
port: 2001
spring:
application:
name: order
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
cloud:
nacos:
discovery:
server-addr: localhost:8848
feign:
hystrix:
enabled: false
logging:
level:
io:
seata: info
mybatis:
mapperLocations: classpath:mapper/*.xml
seata:
enabled: true
application-id: order
# 是否启用数据源bean的自动代理
enable-auto-data-source-proxy: false
service:
vgroup-mapping:
order-seata-service-group: default # 必须和服务器配置一样
disable-global-transaction: false
tx-service-group: order-seata-service-group # 必须和服务器配置一样
registry:
type: nacos
nacos:
# Nacos 服务地址 本机localhost
server-addr: 10.15.35.2:8848
group: SEATA_GROUP
namespace: 7dfa67c0-8296-4fc1-b6a2-a9a9020d7cea
application: seata-server # 必须和服务器配置一样
username: nacos
password: nacos
cluster: default
config:
type: nacos
nacos:
server-addr: 10.15.35.2:8848
group: SEATA_GROUP
namespace: 7dfa67c0-8296-4fc1-b6a2-a9a9020d7cea
username: nacos
password: nacos
client:
rm:
# 是否上报成功状态
report-success-enable: true
# 重试次数
report-retry-count: 5
每个service方法上都加上@GlobalTransactional,访问地址:http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
同时记得在每个参与分布式事务的数据库下新建undo_log表,建表语句为
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;
踩坑
- 如果用的远程调用是openFeign的话,或者事物不生效的情况下检查你其他服务的xid是不是为空或者不一致(其他远程调用框架也一样这样配置请求头加xid)。你的事物生效就是因为你远程调用的时候远程调用框架没有把xid在请求头传到对应的服务去,所以才会导致事物失效,因为想要事物生效一整个服务调用链必须保证xid是一样的。
这里openFeign远程调用的解决方法是这样的
import feign.RequestInterceptor;
import feign.RequestTemplate;
import io.seata.core.context.RootContext;
/**
* openFeign配置类
*
* @author 刘子固
* @version 1.0
* @date 2023/10/9 17:25
*/
public class MultipartSupportConfig implements RequestInterceptor {
/**
* 解决服务直接调用请求头不传递的问题
*
* @param template
*/
@Override
public void apply(RequestTemplate template) {
// 解决seata的xid未传递
String xid = RootContext.getXID();
template.header(RootContext.KEY_XID, xid);
}
}
- nacos 控制台记得添加 vgroupMapping配置 内容都为default
3.全局事务注册超时
修改如下配置为60000,seata默认是1000毫秒
同时可以设置@GlobalTransactional注解的超时时间,时间设长一点