什么是事务:
事务逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败.
事务的特性(ACID):
原子性(atomicity):强调事务的不可分割,事务包含的所有操作要么全部成功,要么全部失败回滚(使用undo log,从而达到回滚)
一致性(consistency):事务执行的前后数据的完整性保持一致
隔离性(isolation):一个事务执行的过程中,不应该受到其他事务的干扰
持久性(durability):事务一旦结束,数据就持久到数据库(使用redo log,从而达到故障后的恢复)
sring事务的传播机制
package org.springframework.transaction.annotation;
public enum Propagation {
REQUIRED(0), //必须的 : 默认的Spring事务传播级别,当前若存在事务,则加入该事务,则新建一个事务
SUPPORTS(1),//支持: 支持当前事务,若当前不存在事务,以非事务的方式执行
MANDATORY(2), //强制的: 强制事务执行,若当前不存在事务,则抛出异常
REQUIRES_NEW(3), //要求_new: 若当前没有事务,则新建一个事务。若当前存在事务,则新建一个事务,新老事务相互独立。外部事务抛出异常回滚不会影响内部事务的正常提交
NOT_SUPPORTED(4),//not_不支持: 以非事务的方式执行,若当前存在事务,则把当前事务挂起
NEVER(5), //决不: 以非事务的方式执行,如果当前存在事务,则抛出异常
NESTED(6);//嵌套: 如果当前存在事务,则嵌套在当前事务中执行。如果当前没有事务,则新建一个事务,类似于REQUIRE_NEW
private final int value;
private Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
REQUIRED(0), //必须的 : 默认的Spring事务传播级别,当前若存在事务,则加入该事务,则新建一个事务
SUPPORTS(1),//支持: 支持当前事务,若当前不存在事务,以非事务的方式执行
MANDATORY(2), //强制的: 强制事务执行,若当前不存在事务,则抛出异常
REQUIRES_NEW(3), //要求_new: 若当前没有事务,则新建一个事务。若当前存在事务,则新建一个事务,新老事务相互独立。外部事务抛出异常回滚不会影响内部事务的正常提交
NOT_SUPPORTED(4),//not_不支持: 以非事务的方式执行,若当前存在事务,则把当前事务挂起
NEVER(5), //决不: 以非事务的方式执行,如果当前存在事务,则抛出异常
NESTED(6);//嵌套: 如果当前存在事务,则嵌套在当前事务中执行。如果当前没有事务,则新建一个事务,类似于REQUIRE_NEW
spring事务的隔离级别:
package org.springframework.transaction.annotation;
public enum Isolation {
DEFAULT(-1), //默认
READ_UNCOMMITTED(1),//读未提交
READ_COMMITTED(2),//读已提交
REPEATABLE_READ(4),//可重复读
SERIALIZABLE(8);//可串行化
private final int value;
private Isolation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
DEFAULT(-1), //默认:使用底层数据库的默认隔离级别对于大多数的数据库来说,默认的隔离级别都是read commited
READ_UNCOMMITTED(1),//读未提交:脏读,不可重复读,幻读都有可能发生
READ_COMMITTED(2),//读已提交:避免脏读。但是不可重复读和幻读有可能发生
REPEATABLE_READ(4),//可重复读:避免脏读和不可重复读.但是幻读有可能发生.
SERIALIZABLE(8);//可串行化:避免以上所有读问题.最严格的事务隔离级别,要求所有事务被串行执行,不能并发执行,可避免脏读、不可重复读、幻读情况的发生。
脏读:一个事务读到另一个事务未提交的数据
不可重复读:一个事务读到另一个事务已经提交的数据导致第一个事务对同一条数据多次查询结果不一致(例如:A事务查看id=1的数据年龄显示为18,在A事务还没结束的时候,创建了事务B对id=1的数据进行修改为20,并进行了提交,然后事务A逻辑处理中又需要再次查询id=1的数据,发现年龄变成了20,这样的情况称为不可重复读)
幻读:一个事务读到另一个事务已经提交的数据,导致一个事务多次查询结果不一致。(例如:当事务A读取id>5时的数据有10条,在A事务还没有提交的时候,创建了应一个事务B新增了一条数据,当事务A再读取该id>5时的数据条数时,发现数据多了几条,这种情况称为幻读)
幻读和不可重复读的区别:
不可重复读:重点是update
幻读:重点是insert或delete数据
事务的隔离级别要得到底层数据库引擎的支持,而不是应用程序或者框架的支持
Oracle支持的2种事务隔离级别:READ_COMMITED,SERIALIZABLE
Mysql支持的4种事务隔离级别:READ_UNCOMMITTED,READ_COMMITTED,REPEATABLE_READ,SERIALIZABLE
分布式事务:
一般来所在微服务中,每个服务都会有自己一个独立的数据源。因此在微服务中,存在多个数据源操作的时候,就会存在分布式事务。
分布式事务的解决方案:
方法一:2PC(准备阶段 - 确认阶段)
阶段二可能是提交数据,但也有可能是回滚
2PC可能存在的问题:
1、事务管理器挂掉的话接收不到信息
2、第一阶段如果请求一直没接通,那么其他请求一直等待;或者响应一直未接收到,一直等待,导致长时间处于事务资源锁定,造成阻塞(事务操作是要加锁的)
3、第二阶段,如果事务管理器发出commit执行后宕机,一部分参与者接收到消息提交事务,而一部分没有消息无法做出事务提交操作,导致数据不一致。
等等。。。
方案二:seata的两段式方案。
seata的使用性能较好,且不长时间占用连接资源,它以高效并且对业务0入侵的方式解决微服务场景下面临的分布式事务问题,它目前支持AT模式(2PC)及TCC模式的分布式事务解决方案。
Transaction Coordinator(TC):事务协调器,它是独立的中间件,需要独立部署运行,它维护全局事务的运行状态,接收TM指令发起全局事务的提交与回滚,负责与RM通信协调各个分支事务的提交或回滚。相当于是一个软件需要单独部署
Transaction Manager(TM):事务管理器,TM需要嵌入应用程序中工作,它负责开启一个全局事务,并最终向TC发起全局提交或全局回滚的指令
Resource Manager(RM):控制分支事务,负责分支注册,状态汇报,并接收事务协调器TC的指令,驱动分支(本地)事务的提交和回滚。
seata实现AT分布式式事务的工作的原理
涉及到多个服务多个数据源的操作
TC:事务协调器,管理状态,管理分支事务和全局状态,发布事务提交或者回滚命令
TM:事务管理器,管理全局事务
RM:资源管理器,管理分支事务
1、TM向TC注册一个全局事务,并生成一个全局事务XID,然后A服务向TC注册分支事务,并纳入XID这个全局事务管理,
2、A服务完成操作,提交分支事务,并告知TC,同时插入一条数据插入seata中的undo log中(该条数据含有XID)
3、调用B服务,把XID传给B,同时B服务向TC注册分支事务,并纳入XID全局事务管理
4、B操作数据,提交本地事务,告知TC,同时插入一条数据进入seata的undo log中
5、如果seata的TC收到的分支事务的通知都是成功,此时发布提交全局事务的指令,由TM去删除XID对应的undo log
6、如果TC收到的分支事务通知有失败的,此时发布回滚全局事务指定,由成功的分支事务根据XID去回滚。
seata的使用:
1、下载seata的安装包,解压安装
2、启动seata
3、项目中加入pom依赖
<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
<version>2.1.0.RELEASE</version>
<exclusions>
<exclusion>
<artifactId>seata-all</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.1.0</version>
</dependency>
4、yml中配置seata的参数,并在resource下创建file.conf文件,registry.conf文件
spring:
cloud:
alibaba:
seata:
tx-service-group: fsp_tx_group #这里和file.conf中事务组名一样
file.conf
transport {
# tcp udt unix-domain-socket
type = "TCP"
#NIO NATIVE
server = "NIO"
#enable heartbeat
heartbeat = true
# the client batch send request enable
enableClientBatchSendRequest = true
#thread factory for netty
threadFactory {
bossThreadPrefix = "NettyBoss"
workerThreadPrefix = "NettyServerNIOWorker"
serverExecutorThread-prefix = "NettyServerBizHandler"
shareBossWorker = false
clientSelectorThreadPrefix = "NettyClientSelector"
clientSelectorThreadSize = 1
clientWorkerThreadPrefix = "NettyClientWorkerThread"
# netty boss thread size,will not be used for UDT
bossThreadSize = 1
#auto default pin or 8
workerThreadSize = "default"
}
shutdown {
# when destroy server, wait seconds
wait = 3
}
serialization = "seata"
compressor = "none"
}
service {
#transaction service group mapping
vgroupMapping.fsp_tx_group = "default"
#only support when registry.type=file, please don't set multiple addresses
default.grouplist = "127.0.0.1:8091"
#degrade, current not support
enableDegrade = false
#disable seata
disableGlobalTransaction = false
}
client {
rm {
asyncCommitBufferLimit = 10000
lock {
retryInterval = 10
retryTimes = 30
retryPolicyBranchRollbackOnConflict = true
}
reportRetryCount = 5
tableMetaCheckEnable = false
reportSuccessEnable = false
}
tm {
commitRetryCount = 5
rollbackRetryCount = 5
}
undo {
dataValidation = true
logSerialization = "jackson"
logTable = "undo_log"
}
log {
exceptionRate = 100
}
}
registry.conf
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "file"
nacos {
serverAddr = "localhost"
namespace = ""
cluster = "default"
}
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"
session.timeout = 6000
connect.timeout = 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、springCloudConfig
type = "file"
nacos {
serverAddr = "localhost"
namespace = ""
group = "SEATA_GROUP"
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
app.id = "seata-server"
apollo.meta = "http://192.168.1.204:8801"
namespace = "application"
}
zk {
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
username = ""
password = ""
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}
5、添加seata连接数据库的连接池配置
mybatis版本
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
//使用seata对DataSource进行代理
@Configuration
public class DataSourceProxyConfig {
//mapper.xml路径
@Value("${mybatis.mapper-locations}")
private String mapperLocations;
//手动配置bean
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource(){
return new DruidDataSource();
}
@Bean
public SqlSessionFactory sessionFactory(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSourceProxy);
sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
//事务管理工厂
sessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
return sessionFactoryBean.getObject();
}
@Bean
public DataSourceProxy dataSource() {
return new DataSourceProxy(druidDataSource());
}
}
mybatisPlus版本
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean;
import io.seata.rm.datasource.DataSourceProxy;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
@Configuration
public class DataSourceConfiguration {
//mapper.xml路径
@Value("${mybatis-plus.mapper-locations}")
private String mapperLocations;
//手动配置bean
@Bean
@ConfigurationProperties("spring.datasource")
public DataSource druidDataSource(){
return new DruidDataSource();
}
@Bean
public MybatisSqlSessionFactoryBean sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
//处理MybatisPlus
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSourceProxy);
factory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
//事务管理工厂
factory.setTransactionFactory(new SpringManagedTransactionFactory());
return factory;
}
@Bean
public DataSourceProxy dataSource() {
return new DataSourceProxy(druidDataSource());
}
}
#配置别名
mybatis-plus:
mapper-locations: classpath:com/gao/xxx/mapper/*Mapper.xml
6、打注解,并声明在那种异常下进行回滚。
在存在分布式事务的方法上变打上@GlobalTransaction()注解
@GlobalTransactional(rollbackFor = {NullPointerException.class})
7、创建seata的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;
8、关闭mybatisPlus的事务注解 @EnableTransactionManagement
//@EnableTransactionManagement
@Configuration
public class MybatisPlusConfig {
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
9、在通过feign调用的provide方也加入如上的3、4、5、7、8步骤。直接复制粘贴
什么式TCC?
TCC(try-confirm-cancle),tcc是基于补偿事务的AP系统的一种实现,具有弱一致性。
分布式一致性:
强一致性:在数据发生变化的时候,各个服务数据都达到一致。对服务系统的性能影响很大
弱一致性:在数据发生变化时,不要求立刻达到一致,而是在某一个时间级别后(比如毫秒级)数据达到一致。
最终一致性:最终一致性是弱一致性的一个特例。系统会保证在一定时间内达到一致状态。比如积分,系统在多次尝试增加积分后还是不能成功,通过采用一些其他方案,例如人工手动增加积分,最终达到数据一致。
CAP理论:
C:consistency 一致性
A:availability 可用性
P:partition tolerance 分区容错性
一致性:数据在多个副本中能够保持一致的特性,当数据在数据一致的状态下执行更新操作后,应该保证系统的数据仍然处于一致的状态。
可用性:可用性是指系统提供的服务一致处于可用状态,对于用户的操作请求总能够在有限的时间内返回结果。有限时间是指在系统设计时间范围内的。如果超过了,系统就不可用了
分区容错性:分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务,除非整个网络不可用。
网络分区是指:在分布式系统中,不同节点分布在不同的子网络(机房或异地网络)中,由于一些特殊原因导致这些网络出现网络不通的情况,但各个子网络的内部网络是正常的,从而导致整个系统的环境被切分成了若干个孤立区域。
BASE理论:
basically available(基本可用)
soft state(软状态)
eventually consistent(最终一致性)
基本可用:分布式系统在出现不可预知的故障的时候,允许损失部分可用性
软状态:系统中存在中间状态,并认为该中间状态存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
最终一致性:最终一致性是所有的数据副本,在经过一段时间的同步之后,最终都能达到一个一致的状态。因此最终一致性的本质需要系统保证最终数据达到一致,而不需要实时保证系统数据的强一致性。