本地事务
1、事务的基本性质
数据库事务的几个特性:原子性(Atomicity)
、一致性(Consistency)
、隔离性或独立性(isolation)
、持久性(Durability)
,简称就是 ACID
。
- 原子性:一系列的操作整体不可拆分,要么同时成功,要么同时失败。
- 一致性:数据在事务的前后,业务整体一致。
转账:A:1000; B:1000; 转 200 事务成功; A:800; B:1200 - 隔离性:事务之间互相隔离
- 持久性:一旦事务成功,数据一定会落盘在数据库。
比如买东西业务,扣库存,下订单,账户扣款,是一个整体,必须同时成功或失败。本地事务的适用非常简单,使用spring家在方法上加上@Transactional
注解就可以了。
2、事务的隔离级别
(1)read_uncommitted
(读未提交)
可读取未提交事务的操作数据,最低的隔离级别,一般都没有用的。这种情况会出现脏读。
(2)read_committed
(读已提交)
一个事务等另一个事务提交之后才可进行读取,解决了脏读问题,但会出现不可重复读
(3)repeatable_read
(可重复读)
读取事务开启的时候不能对数据进行修改,解决了不可重复读问题,但是存在幻读问题;
(4)serializable
(序列化):是最高的事务隔离级别,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用;
MySQL默认第三隔离级别。
Oracle默认第二隔离级别。
在代码中我们可以指定事务的隔离级别:
@Transactional(isolation=Isolation.READ_COMMITED)
3、事务的传播行为
-
1、支持当前事务:
TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)。 -
2、不支持当前事务:
TransactionDefinition.PROPAGATION_REQUIRED_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGETION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。 -
3、其他
TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
示例:
@Transactional // a事务的所有设置就传播到了和他共用一个事务的方法。
public void a() {
// b,c是否和a共用一个事务?
b(); // b需要一个事务,这时b和a共用一个事务,即在a事务里边
c(); // c是一个新事务
int i = 10 / 0; // 哪个会回滚呢?
}
@Transactional(propagation=Propagation.REQUIRED)
public void b() {
}
@Transactional(propagation=Propagation.REQUIRED_NEW)
public void c() {
}
如果上边的代码 int i = 10 / 0;
执行,哪个会回滚呢? 由于b和a是在同一个事务里边,所以,两个都会回滚,而c是一个新事物,所以,不会回滚。
我们还可以给事务添加超时时间 @Transactional(timeout =20)
,如果共用事务,则子事务的设置就不起作用了,会用父类的事务。
4、SpringBoot事务关键点
4.1、事务的自动配置
TransactionAutoConfiguration
4.2、事务的坑
在同一个类里面,编写两个方法,内部调用的时候,会导致事务设置失效。原因是没有用到代理对象的缘故。
事务使用代理对象来控制的,上边的b,c和a是相同的对象,同一个对象内事务方法互调默认失败,原因是绕过了代理对象,
解决:
1)、导入spring-boot-starter-aop
,引入了 aspectj
2)、@EnableTransactionManagement(proxyTargetClass=true)
3)、@EnableAspectJAutoProxy(exposeProxy=true);
开启 aspectj 动态代理功能。以后所有的动态代理都是 aspectj 创建(即使没有接口也可以创建动态代理),对外暴露代理对象。
4)、本类互调用调用对象(OrderServiceImpl obj = AopContext.currentProxy())
如果有本类互调的可以使用上边的解决方案。
小结:@Transactional
是本地事务,在分布式系统,只能控制住自己的回滚,控制不了其它服务的回滚,所以,是有局限性的,如果方法里边需要调用其它服务的操作,则要使用分布式事务。
分布式事务
一、为什么会有分布式事务
分布式系统经常出现的异常,如机器宕机、网络异常、消息丢失、数据错误、不可靠的TCP、存储数据丢失等等。
二、分布式事务
分布式事务是指事务的参与者,支持事务的服务器,资源服务器分别位于分布式系统的不同节点之上,通常一个分布式事物中会涉及到对多个数据源或业务系统的操作。
典型的分布式事务场景:跨银行转操作就涉及调用两个异地银行服务
三、分布式理论
1、CAP理论
CAP理论:一个分布式系统不可能同时满足一致性,可用性和分区容错性这个三个基本需求,最多只能同时满足其中两项
一致性(Consistency)
:数据在多个副本之间是否能够保持一致的特性。
可用性(Avaliability)
:是指系统提供的服务必须一致处于可用状态,对于每一个用户的请求总是在有限的时间内返回结果,超过时间就认为系统是不可用的
分区容错性(Partition tolerance)
:分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务,除非整个网络环境都发生故障。
CAP定理的应用
放弃P(CA)
:如果希望能够避免系统出现分区容错性问题,一种较为简单的做法就是将所有的数据(或者是与事物先相关的数据)都放在一个分布式节点上(属于本地都放一个系统),这样虽然无法保证100%系统不会出错,但至少不会碰到由于网络分区带来的负面影响
放弃A(CP)
:其做法是一旦系统遇到网络分区或其他故障时,那受到影响的服务需要等待一定的时间,应用等待期间系统无法对外提供正常的服务,即不可用
放弃C(AP):
这里说的放弃一致性,并不是完全不需要数据一致性,是指放弃数据的强一致性,保留数据的最终一致性。
分布式系统中实现一致性的 raft
算法:
任何一个节点都有三种状态:随从,候选者,领导
CP面临的问题:
对于多数大型互联网应用的场景,主机众多,部署分散,而且现在集群的规模越来越大,所以,节点故障、网络故障是常态,而且要保证服务可用性达到 99.999999%(N个9),即保证P和A,舍弃C。
2、BASE理论
BASE是基本可用
,软状态
,最终一致性
。是对CAP中一致性和可用性权限的结果,是基于CAP定理演化而来的,核心思想是即使无法做到强一致性,但每个应用都可以根据自身的业务特定,采用适当的方式来使系统达到最终一致性
强一致性、弱一致性、最终一致性:
对于关系型数据库,要求更新过的数据能被后续的访问都能看到,这就是强一致性。
如果能容忍后续的部分或者全部访问不到,则是弱一致性,如果经过一段时间能访问到更新后的数据,则是最终一致性。
三、分布式事务几种方案
1、2PC提交(刚性事务)
二阶段提交协议是将事务的提交过程分成提交事务请求和执行事务提交两个阶段进行处理。
2、3PC提交(刚性事务)
三阶段提,也叫三阶段提交协议,是二阶段提交(2PC)的改进版本。
与两阶段提交不同的是,三阶段提交有两个改动点。引入超时机制。同时在协调者和参与者中都引入超时机制。在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。
三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。
3、柔性事务-TCC事务补偿型方案
刚性事务:遵循 ACID 原则,强一致性。
柔性事务:遵循 BASE 理论,最终一致性。
与刚性事务不同,柔性事务允许一定时间内,不同节点的数据不一致,但要求最终一致。
TCC是服务化的两阶段编程模型,其Try、Confirm、Cancel,3个方法均由业务编码实现
TCC要求每个分支事务实现三个操作:预处理Try,确认Confirm,撤销Cancel。
Try操作做业务检查及资源预留,
Confirm做业务确认操作
Cancel实现一个与Try相反的操作即回滚操作。
TM首先发起所有的分支事务Try操作,任何一个分支事务的Try操作执行失败,TM将会发起所有分支事务的Cancel操作,若Try操作全部成功,TM将会发起所有分支事务的Confirm操作,其中Confirm/Cancel操作若执行失败,TM会进行重试。
4、柔性事务-最大努力通知型方案
最大努力通知,发起通知方尽最大的努力将业务处理结果通知为接收通知方,但是消息可能接收不到,此时需要接收通知方主动调用发起通知方的接口查询业务,通知可靠性关键在于接收通知方
5、柔性事务-可靠消息+最终一致性方案(异步确保型)
实现:业务处理服务在业务事务提交之前,向实时消息服务请求发送消息,实时消息服务只记录消息数据,而不是真正的发送。业务处理服务在业务事务提交之后,向实时消息服务确认发送,只有在得到确认发送指令后,实时消息服务才会真正发送。