我举的例子是:在网上购物时,我们支付后,订单微服务会更新订单状态,同时会远程调用购物车微服务清空购物车,和调用商品微服务完成商品库存减一。
我们曾经说的事务是只能在本微服务完成回滚,意思就是如果过程中出现问题需要回滚,只有更新订单状态 这一操作会回滚,而清空购物车和商品库存减一 这俩操作是远程调用其他微服务的操作,是不能回滚的,所以我们就需要分布式事务来解决这种问题。
Seata:致力于提供高性能和简单易用的分布式事务服务,为用户打造一站式的分布式解决方案。
Seata事务管理中有3个重要角色:
TC:事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
TM:事务管理器:定义全局事务范围、开始全局事务、提交或回滚全局事务。
RM:资源管理器:管理分支事务,与TC交谈以注册分支事务和报告分支事务的状态。
seata有俩种模式:
XA:
在2.3步之前的操作都是对数据库进行预处理,只有2.3确定全部成功后,才会commit提交。
优点:事务强一致性,满足acid原则;常用数据库都支持,实现简单 没有代码介入。
缺点:在若干个RM执行sql时,需要锁定数据库资源,不同的RM用的时间也不同,若有一个还没执行完,其余的执行完的RM就只能干等着,性能很差;依赖关系型数据库实现事务。
AT模式:
与XA模式不同的是:AT模式的1.4步执行sql会立刻提交,在1.4执行sql前会基于数据库信息形成一个快照,如果后面有RM失败,就可以把快照信息写回数据库恢复数据。
缺点:在1.4执行提交后,并且在第二阶段事务失败需要回滚时,期间会有短暂(虽然时间很短)的数据不一致,只满足了最终一致性。
下面我来说一说怎么用Java代码去实现它:
首先我们需要去下载seata,我用的是docker 部署的,用的是1.5.2版本。我们需要把seata注册到nacos中。
我们写一下seata的配置文件: 这里面你只需要改nacos的地址、账号密码;mysql地址、账号密码。
server:
port: 7099
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: #seata控制台的账号密码
user:
username: seata
password: seata
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: file
# type: nacos
# nacos:
# server-addr: 192.168.1.105:8848
# group : "DEFAULT_GROUP"
# namespace: ""
# dataId: "shared-seata.yaml"
# username: "nacos"
# password: "nacos"
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos
nacos:
application: seata-server
server-addr: host.docker.internal:8848 #nacos 地址
group: "DEFAULT_GROUP"
namespace: ""
username: "nacos"
password: "nacos"
# 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
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
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
#数据库地址
url: jdbc:mysql://mysql:3306/seata?rewriteBatchedStatements=true&serverTimezone=UTC
user: root
password: 123
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: 192.168.150.101
# port: 6379
metrics:
enabled: false
registry-type: compact
exporter-list: prometheus
exporter-prometheus-port: 9898
bindIpAddr: ${SERVICE_BIND_IP}
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
然后docker启动: 其中SEATA_IP最好与你的nacos启动地址一致,这里查看nacos地址:
如果你nacos和mysql也是用docker部署的,就一定要让它们三个加入同一network。
docker run --name seata -p 8099:8099 -p 7099:7099 -e SEATA_IP=注册到nacos中的地址 -v 你的配置文件的目录:/seata-server/resources --privileged=true --network 你的network名称 -d seataio/seata-server:1.5.2
启动成功后,我们就可以在nacos中看到我们的seata服务:
然后我们需要在nacos中写一些配置来让我们的微服务能注册到seata中:
seata:
registry: #TC服务中心的配置,微服务根据这些配置去注册中心获取TC服务地址
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: ""
group: DEFAULT_GROUP
application: seata-server
username: nacos
password: nacos
# 事务组名称
tx-service-group: hmall
service:
vgroup-mapping: # 事务组与tc集群的映射关系
hmall: "default"
grouplist:
default: 127.0.0.1:8099
#data-source-proxy-mode: XA #不填,默认就是AT
然后我们就可以去稍微改一改我们的Java代码了:
首先为我们的购物车微服务、订单微服务、商品微服务添加seata依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
还要在这三个微服务的配置文件中导入nacos中的seata配置:
spring:
application:
name: *** # 服务名称
profiles:
active: dev # 开发环境
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
config:
file-extension: yaml
shared-configs: #seata配置
- data-id: shared-seata.yaml
discovery:
cluster-name: public
在网上购物时,我们支付后,订单微服务会更新订单状态,同时会远程调用购物车微服务清空购物车,和调用商品微服务完成商品库存减一。 这一操作是订单微服务中的方法,找到改service层方法加上@GlobalTransactional注解
@Override
@GlobalTransactional
public Long createOrder(OrderFormDTO orderFormDTO) {
.......
}
我们还要找到购物车微服务的清空购物车的service方法和商品微服务的库存减一的service方法,为它们加上@Transactional注解.
然后我们就可以启动我们的微服务了,我们每成功启动并注册到seata中,seata的启动日志就会在最下面打印这几行日志:
由此可知我们微服务成功启动了,并注册到了seata中。