本地事务会出现的问题
1、远程服务假失败
远程服务其实成功了,由于网络故障等没有返回,导致订单回滚,库存扣减
2、远程服务执行完成,下面其他方法出现问题,导致已执行的其他远程请求不能回滚。
伪代码:
//TODO 3、保存订单
saveOrder(order);
//、库存锁定,只要有异常回滚订单数据
//订单号,所有订单项(skuId,skuName,num)
WareSkuLockVo lockVo = new WareSkuLockVo();
lockVo.setOrderSn(order.getOrder().getOrderSn());
List<OrderItemVo> locks = order.getOrderItems().stream().map(item -> {
OrderItemVo itemVo = new OrderItemVo();
itemVo.setSkuId(item.getSkuId());
itemVo.setCount(item.getSkuQuantity());
itemVo.setTitle(item.getSkuName());
return itemVo;
}).collect(Collectors.toList());
lockVo.setLocks(locks);
//TODO 4、远程锁库存
R r = wmsFeignService.orderLockStock(lockVo);
if(r.getCode()==0){
//锁成功
response.setOrder(order.getOrder());
//TODO 5、远程扣减积分
int i=10/0;
return response;
}else{
//锁失败
String msg=(String)r.get("msg");
throw new NoStockException(4L);
//return response;
}
2、本地事务的基本性质
原子性(Atomicity )
、一致性
( Consistency )
、隔离性或独立性
( Isolation)
和持久性
(Durabilily)
,简称就是
ACID
;
原子性:一系列的操作整体不可拆分,要么同时成功,要么同时失败
一致性:数据在事务的前后,业务整体一致。
隔离性:事务之间互相隔离。
持久性:一旦事务成功,数据一定会落盘在数据库。
使用同一条连接操作不同的数据表,一旦有异常, 我们可以很容易的整体回滚
3、事务的隔离级别
READ UNCOMMITTED
(读未提交)
该隔离级别的事务会读到其它未提交事务的数据,此现象也称之为脏读。
READ COMMITTED
(读提交-更新)
一个事务可以读取另一个已提交的事务,多次读取会造成不一样的结果,此现象称为不可重
复读问题,
Oracle
和
SQL Server
的默认隔离级别。
REPEATABLE READ
(可重复读-插入)
该隔离级别是
MySQL
默认的隔离级别,在同一个事务里,
select
的结果是事务开始时时间
点的状态,因此,同样的
select
操作读到的结果会是一致的,但是,会有幻读现象。
MySQL
的
InnoDB
引擎可以通过
next-key locks
机制(参考下文
"
行锁的算法
"
一节)来避免幻读。
SERIALIZABLE
(序列化)
在该隔离级别下事务都是串行顺序执行的,
MySQL
数据库的
InnoDB
引擎会给读操作隐式
加一把读共享锁,从而避免了脏读、不可重读复读和幻读问题。
以上4是事务级别的并发能力依次降低。
4、事务的传播行为
总结一句话就是 子继承父 。不是自己的儿子。可以有自己的特性
1
、
PROPAGATION_REQUIRED
:
如果当前没有事务,就创建一个新事务,如果当前存在事务,
就加入该事务,该设置是最常用的设置。
2
、
PROPAGATION_SUPPORTS
:
支持当前事务,如果当前存在事务,就加入该事务,如果当
前不存在事务,就以非事务执行。
3
、
PROPAGATION_MANDATORY
:
支持当前事务,如果当前存在事务,就加入该事务,如果
当前不存在事务,就抛出异常。
4
、
PROPAGATION_REQUIRES_NEW
:
创建新事务,无论当前存不存在事务,都创建新事务。
5
、
PROPAGATION_NOT_SUPPORTED
:
以非事务方式执行操作,如果当前存在事务,就把当
前事务挂起。
6
、
PROPAGATION_NEVER
:
以非事务方式执行,如果当前存在事务,则抛出异常。
7
、
PROPAGATION_NESTED
:
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,
则执行与
PROPAGATION_REQUIRED
类似的操作。
伪代码:
@Transactional
public void a(){
}
@Transactional(propagation = Propagation.REQUIRED)
public void b(){
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void c(){
}
前面还有一个坑就是
同一个对象内事务方法互调默认失效,原先绕过了代理对象,事务使用代理对象来控制的。
不同XXXservice调用不同方法才生效
@Transactional
public void a(){
bService.b();
cService.c();
}
@Transactional(propagation = Propagation.REQUIRED)
public void b(){
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void c(){
}
解决:使用代理对象来调用事务方法
1)、引入spring-boot-starter-aop,引入了aspectj
2)、@EnableAspectJAutoProxy(exposeProxy = true) ;开启了aspectj 动态代理功能;以后所有的动态代理都是aspectj创建的(即使没有接口也能创建动态代理)
3)、用代理对象调用
分布式事务
1、为什么会出现分布式事务
分布式系统经常出现的异常
机器宕机、网络异常、消息丢失、消息乱序、数据错误、不可靠的
TCP
、存储数据丢失
...
节点之间互相的状态不能同步,网络状况,数据互相感知不到
2、CAP定理和BASE理论
1)、一致性(Consistency)
在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访
问同一份最新的数据副本)
(在分布式系统下。所有的数据在同一时候是否是同样的值)
2)、可用性(Availability)
在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据
更新具备高可用性)
(在分布式系统下。某台机器发生故障,其他服务是否继续相应客户的操作)
3)、分区容错性(Partition tolerance)
大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区(partition)。
分区容错的意思是,区间通信可能失败。比如,一台服务器放在中国,另一台服务
器放在美国,这就是两个区,它们之间可能无法通信。
(一台在北京,一台在上海,网络区不同,可能这两个区不能通信怎么办)
CAP原则指的是,这三个要素最多只能同时实现两个,不可能三者兼顾。
而在分布式系统下一定要满足分区容错性,因为网络肯定会出现问题。
而在P的基础上如果满足A可用想就会出现c的错误
例如:3台服务。如果A,B,C。出现分区容错性.满足可用性的话。客户能同时访问A,B,C。但是由于出现了分区容错性。那么这个三个直接的数据就不是一致性的
同理:3台服务器,如果A,B,C.。出现了分区容错性。满足数据一致性的话。就必须不能让这3台服务器都相应。
分布式系统中实现一致性的
raft
算法、
通俗的讲就是选择领导:随从,候选者,领导。在从随从变为候选者的中间没一个人有一个自旋时间(150-300毫秒)。最先完成的就是候选者,然后投自己一票。再发送消息给还没变成候选者的人,让他们投票给自己为领导。这样候选者就变成了领导。领导发送给随从的时候有一个心跳时间。就这样一直维持心跳。心跳时间不能超过自旋时间。
当领导挂掉。那和自旋时间快那个就成为领导
而数据直接同步是领导者发送消息。在领导接受到消息的后,发下一次发送心跳消息会带上消息去同步他的随从
出现分区容错性后,没个区选择自己的领导,保存客户端发送的数据,当分区回复后。两个领导根据最多轮选择出来的领导当最后恢复后的新领导。而另外一个区的数据去同步新领导区的数据。老领导区的数据新加的回滚。
Raft Consensus Algorithm --更详细的可以查看此网站
3、BASE理论
是对
CAP
理论的延伸,思想是即使无法做到强一致性(
CAP
的一致性就是强一致性),但可
以采用适当的采取弱一致性,即
最终一致性
。
BASE
是指
基本可用(Basically Available
)
基本可用是指分布式系统在出现故障的时候,允许损失部分可用性(例如响应时间、
功能上的可用性),允许损失部分可用性。需要注意的是,基本可用绝不等价于系
统不可用。
响应时间上的损失:正常情况下搜索引擎需要在
0.5
秒之内返回给用户相应的
查询结果,但由于出现故障(比如系统部分机房发生断电或断网故障),查询
结果的响应时间增加到了
1~2
秒。
功能上的损失:购物网站在购物高峰(如双十一)时,为了保护系统的稳定性,
部分消费者可能会被引导到一个降级页面。
软状态( Soft State
)
软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用性。分布
式存储中一般一份数据会有多个副本,允许不同副本同步的延时就是软状态的体
现。
mysql replication
的异步复制也是一种体现。
最终一致性( Eventual Consistency
)
最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状
态。弱一致性和强一致性相反,最终一致性是弱一致性的一种特殊情况。
强一致性、弱一致性、最终一致性
从客户端角度,多进程并发访问时,更新过的数据在不同进程如何获取的不同策略,决定了
不同的一致性。对于关系型数据库,要求更新过的数据能被后续的访问都能看到,这是
强一
致性
。如果能容忍后续的部分或者全部访问不到,则是
弱一致性
。如果经过一段时间后要求
能访问到更新后的数据,则是
最终一致性
分布式事务几种方案
1、)2PC模式
数据库支持的
2PC
【
2 phase commit
二阶提交】,又叫做
XA Transactions
。
MySQL
从
5.5
版本开始支持,
SQL Server 2005
开始支持,
Oracle 7
开始支持。
其中,
XA
是一个两阶段提交协议,该协议分为以下两个阶段:
第一阶段:事务协调器要求每个涉及到事务的数据库预提交
(precommit)
此操作,并反映是
否可以提交
.
第二阶段:事务协调器要求每个数据库提交数据。
其中,如果有任何一个数据库否决此次提交,那么所有数据库都会被要求回滚它们在此事务
中的那部分信息。
XA
协议比较简单,而且一旦商业数据库实现了
XA
协议,使用分布式事务的成本也比较
低。
XA
性能不理想
,特别是在交易下单链路,往往并发量很高,
XA
无法满足高并发场景
XA
目前在商业数据库支持的比较理想,
在
mysql
数据库中支持的不太理想
,
mysql
的
XA
实现,没有记录
prepare
阶段日志,主备切换回导致主库与备库数据不一致。
许多
nosql
也没有支持
XA
,这让
XA
的应用场景变得非常狭隘。
也有
3PC
,引入了超时机制(无论协调者还是参与者,在向对方发送请求后,若长时间
未收到回应则做出相应处理)
2)、柔性事务-TCC事务补偿型方案
就是有回滚的方法
刚性事务:遵循
ACID
原则,强一致性。
柔性事务:遵循
BASE
理论,最终一致性;
与刚性事务不同,柔性事务允许一定时间内,不同节点的数据不一致,但要求最终一致。
一阶段
prepare
行为:调用 自定义 的
prepare
逻辑。
二阶段
commit
行为:调用 自定义 的
commit
逻辑。
二阶段
rollback
行为:调用 自定义 的
rollback
逻辑。
所谓
TCC
模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。
3)、柔性事务-最大努力通知型方案
就是主流程里的各个分支去订阅消息。那个分支错误。就发送消息到消息队列主流程失败。各分支去监听消息,然后各自回滚。怕有分支因为各种原先收不到消息。会隔几秒发送消息到队列。主流程消息错误。等待分支正常。就收到消息后。返回接受到消息了。不需要通知了。就结束。这就是最大努力通知型方案。
按规律进行通知,
不保证数据一定能通知成功,但会提供可查询操作接口进行核对
。这种
方案主要用在与第三方系统通讯时,比如:调用微信或支付宝支付后的支付结果通知。这种
方案也是结合
MQ
进行实现,例如:通过
MQ
发送
http
请求,设置最大通知次数。达到通
知次数后即不再通知。
案例:银行通知、商户通知等(各大交易业务平台间的商户通知:多次通知、查询校对、对
账文件),支付宝的支付成功异步回调
4)、柔性事务+可靠消息+最终一致性方案(异步确保型)
实现:业务处理服务在业务事务提交之前,向实时消息服务请求发送消息,实时消息服务只
记录消息数据,而不是真正的发送。业务处理服务在业务事务提交之后,向实时消息服务确
认发送。只有在得到确认发送指令后,实时消息服务才会真正发送。
防止消息丢失:
/**
* 1
、做好消息确认机制(
pulisher
,
consumer
【手动
ack
】)
* 2
、每一个发送的消息都在数据库做好记录。定期将失败的消息再次发送一
遍
*/