目录
1.事务
1.1 什么是事务
1.2 事务的使用场景
1.3 事务的四大特性
1.4 事务并发带来的问题
1.5 我们可以使用事务的隔离级别来解决
2. 分布式事务
2.1 如何解决分布式问题:
2.2 介绍seata
2.3 搭建seata服务器
(1)下载seata1.3.0(一定要跟自己所用的springcloudalibaba版本对应)
(2)解压压缩包
(3)修改conf/file.conf
(4)创建数据库并导入表结构
(5) 指定seata的注册中心地址和配置中心的内容
(6)使用脚本nacos/nacos-fonfig.sh自动导入配置信息到nacos配置中心
3.配置微服务客户端
事务(TRANSACTION)是作为单个逻辑工作单元执行的一系列SQL操作, 这些操作要么完全地执行,要么完全地都不执行, 它是一个不可分割的工作执行单元。
在日常生活中,有时我们需要进行银行转账,这个银行转账操作背后就是需要执行多个SQL语句,假
如这些SQL执行到一半突然停电了,那么就会导致这个功能只完成了一半,这种情况是不允许出现,
要想解决这个问题就需要通过事务来完成。
原子性:
一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性
一致性:
数据库总是从一个一致性的状态转换到另一个一致性的状态。(在前面的例子中,一致性确保了,即使在转账过程中系统崩溃,支票账户中也不会损失200美元,因为事务最终没有提交,所以事务中所做的修改也不会保存到数据库中。)
隔离性:
通常来说,一个事务所做的修改操作在提交事务之前,对于其他事务来说是不可见的。(在前面的例子中,当执行完第三条语句、第四条语句还未开始时,此时有另外的一个账户汇总程序开始运行,则其看到支票帐户的余额并没有被减去200美元。)
持久性:
一旦事务提交,则其所做的修改会永久保存到数据库。
说明:事务能够保证数据的完整性和一致性,让用户的操作更加安全。
脏读 幻读 不可重复读 丢失修改
读未提交
读已提交
可重复读
串行化
分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。例如在大型电商系统中,下单接口通常会扣减库存、减去优惠、生成订单 id, 而订单服务与库存、优惠、订单 id 都是不同的服务,下单接口的成功与否,不仅取决于本地的 db 操作,而且依赖第三方系统的结果,这时候分布式事务就保证这些操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
分布式事务的问题
使用消息中间件
手写代码解决分布式事务
使用第三方组件--->Seata阿里巴巴的产品
Seata 是由阿里中间件团队发起的开源项目 Fescar,后更名为 Seata,它是一个是开源的分布式事务框架。
Seata部署指南
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
Seata的执行流程如下:
A服务【订单微服务】的TM[事务发起者]向TC[seata服务端]申请开启一个全局事务,TC就会创建一个全局事务并返回一个唯一的XID
A服务开始远程调用B服务【账户微服务】,此时XID会在微服务的调用链上传播
B服务的RM向TC注册分支事务,并将其纳入XID对应的全局事务的管辖
B服务执行分支事务,向数据库做操作
全局事务调用链处理完毕,TM根据有无异常向TC发起全局事务的提交或者回滚
TC协调其管辖之下的所有分支事务, 决定是否回滚
TM:事务发起者【在哪个方法上添加了全局事务注解的】
TC : 事务管理器【seata的服务端】
RM: 每个操作数据库的微服务
XID: 全局事务id
TM和RM都属于微服务代码
TC: seata服务器。
如果有任何一个微服务TM有问题,Tid就会向TC(统一系统管理器seata)发送回滚事务,如果全部没有问题,Tid就会向TC发送提交事务
下载之前一定要查看自己对应的版本版本说明 · alibaba/spring-cloud-alibaba Wiki · GitHub
Release v1.3.0 · seata/seata · GitHub
这里只有三个目录.我们可以从官网提供的源码中把源码中的script中的目录也粘贴到我们的seata中.方便我们之后使用,最后目录结构会变成这样:
GitHub - seata/seata at v1.3.0 记得下载对应得版本,我这里用的是1.3.0,改成你能用得版本.
下载之后解压,把script目录拉到我们的seata目录中
未来我们肯定Seata不会只开单机模式.既然要搭建集群的话,我们就需要给数据持久化到数据库中或者缓存中了.Seata提供了三种持久化方式:file,db,redis.这里我们选择持久化到db中,db选择为mysql.所以我们需要修改这个文件中的mode为db.并且修改后面的db中的信息为你自己的数据库信息.
让seata集群信息可以共享,我们应该修改它的保存位置:
注意:mysql5.7以后的版本,driverClassName为"com.mysql.cj.jdbc.Driver",如果是这之前的版本的数据库,不需要修改,默认就为"com.mysql.jdbc.Driver".url这里,我的是mysql8版本以后,url需要加上时区.改完之后保存并退出.
然后将mysql的jar包放入lib目录下.启动的时候需要mysql的jar包,
Seata的lib目录默认是没有mysql的jar包的,但是这个jar包是在lib/jdbc目录下的.因为mysql5.1和mysql8为两个版本,这里Seata两个版本都提供了jar包,按需引入,把需要的jar包复制到lib目录下即可.
需要进入seata/lib/jdbc目录 复制自己对应的mysql版本到上级目录/lib中
我们file.conf文件中已经写了我们使用的数据库名为seata,如果你改成了别的,对应的数据库名也要改.数据库建好以后,从我们导入的script\server\db目录下,找到我们的sql文件.
复制mysql.sql到我们的seata数据库中,
源码一定要用对应的版本.在1.5版本,数据库中的表就变成了四张,而我们1.3.0版本只有这三张表.一定要注意!
如果你想将配置文件不放在默认的public环境中,把你想放入的环境的id写到namespace中,如果就放在public中就不用管他. 分组不改默认就是SEATA_GROUP
②修改需要放入配置中心的配置内容
其中这个qy151就是分组名.它是seata的分组不是在nacos中的分组,设置分组的作用是可以选择用哪一组的seata服务,以免你这一组的seata宕机了我还可以用别的分组. 记住这里改的分组名,我们后面配置的时候还会用到它.
如果想具体了解分组的意义
查看上边的seata文档
在script\config-center\nacos目录下有一个nacos-config.sh的脚本文件.由于我们目前处于Windows的环境,而.sh文件适合在linux中启动.我们可以用git服务来代替linux.
如果你的nacos的配置都是默认的配置(localhost:8848),直接用git方式打开文件直接运行也可以.如果你像我一样修改了nacos的端口号,那么就需要打开git bash 输入以下命令指定nacos的地址和端口号以及namespace(默认用的public如果没用自定的环境就不用配)
sh nacos-config.sh -h localhost -p 8888 -g 分组名 -t 命名空间id
参数说明:
-h:host 默认值为localhost
-p:port 默认值为8848
-g:配置分组,默认为'SEATA_GROUP'
-t:对应nacos的命名空间ID字段,默认值为空
可以看到我们的配置文件都已经导入到nacos配置中心了.
最后启动seata服务器.
(1) 在每个微服务对应的数据库中创建unlog表.如果你就一个数据库,那就引入一个表就行.
这个表不需要手动写,script\client\at\db目录下的mysql.sql文件里面有.
(2) 给需要的微服务(即整个链路上的微服务)都引入seata依赖.这里要注意,你的seata的依赖版本一定要保证是和你用的版本是一致的.就像我用的是1.3.0,项目中引入的依赖也一定要是1.3.0
com.alibaba.cloud
spring-cloud-starter-alibaba-seata
如果不匹配的话可以
io.seata
seata-spring-boot-starter
1.3.0
com.alibaba.cloud
spring-cloud-starter-alibaba-seata
io.seata
seata-spring-boot-starter
(3)修改每个微服务的配置文件
单个微服务的完整配置:
server:
port: 8002
spring:
# 数据源配置
datasource:
url: jdbc:mysql://localhost:3306/seata_account?serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
application:
name: seata-account
cloud:
nacos:
discovery:
server-addr: localhost:8848
alibaba:
seata:
# 该值必须和config.txt文件中的分组名一致
tx-service-group: qy151
mybatis:
mapperLocations: classpath:mapper/*.xml
logging:
level:
com.ykq.dao: debug
# 指定seata服务器在nacos的注册中心的地址
seata:
registry:
type: nacos
nacos:
server-addr: localhost:8848
username: nacos
password: nacos
# 指定seata服务器所在的注册中心的组名 默认SEATA_GROUP
group: SEATA_GROUP
# 指定seata服务器在注册中心的服务名称 默认seata-server
application: seata-server
config:
type: nacos
nacos:
server-addr: localhost:8848
username: nacos
password: nacos
group: SEATA_GROUP
在事务的发起者所在微服务的方法上添加分布式事务的注解
@GlobalTransactional
package com.ykq.service.impl;
import com.ykq.dao.OrderDao;
import com.ykq.entity.Order;
import com.ykq.feign.AccountFeign;
import com.ykq.feign.StorageFeign;
import com.ykq.service.OrderService;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private AccountFeign accountFeign;//表示调用的远程服务。
@Autowired
private StorageFeign storageFeign;
@Override
@GlobalTransactional
//@Transactional //只是本地事务 它只能回滚本地自己连接的数据库 无法回滚其他微服务连接数据库。
public void saveOrder(Order order) {
log.info("-------->开始创建新订单");
orderDao.saveOrder(order);
log.info("-------订单微服务开始调用账户,做扣减");
accountFeign.increase(order.getUserId(),order.getMoney()); //事务提交
log.info("-------订单微服务开始调用账户,做扣减end");
//int c=10/0;
log.info("--------订单微服务开始调用库存,做扣减");
storageFeign.increase(order.getProductId(),order.getCount());
log.info("-------订单微服务开始调用库存,做扣减end");
log.info("-------修改订单状态");
orderDao.updateStatus(order.getId());
log.info("-------修改订单状态结束");
log.info("--------下订单结束了,哈哈哈哈");
}
}
重启相关的微服务.测试事务是否成功.
正常下单:
现在我们把三张表恢复成一开始的样子.
然后在下单的业务逻辑中故意加一个异常,重启项目.如果分布式事务引入成功,那么会显示下单失败,并且三张表的内容都不会修改.
下单提示下单失败:
查看数据库,如果表的数据没变,说明我们设置成功:
此时分布式事务已经配置并使用成功.