本地事务在分布式下会出现的问题
- 只能各自回滚各自的
- 簇点头那边抛出异常--->全部回滚(ok)
- 簇点第一个远程调用返回code不正确,我们可在主方法这抛异常(ok)
- 簇点非第一个远程调用code不正确,我们只有那个远程调用和主方法会rollback,在其之前的远程调用无法rollback没人通知(NO)
事务观念复习
事务的基本性质:ACID
A:原子性
C:一致性
I:隔离性
D:持久性(一旦事务成功,数据一定会落盘在数据库)
事务的隔离级别
- 读未提交(会出现幻读,可能每次读到的值之后会马上变化)
- 读已提交(oracle和sqlserver默认,只会读到已经commit的数据)
- 可重复读(你先读值,对方改值,你再读值仍然会和没改值一样)
- 序列化(串行化)
事务的传播行为
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 类似的操作。
SpringBoot关于事务的坑
同一个类中,调用自己的方法都是没有用的
因为springboot的@Transactional是用aop切面来实现的
所以a中调用b,c相当于把b和c的代码复制粘贴,同时配置的传播行为和参数也不会起效果,仅在直接调用b,c方法起效
@Transactional(timeout = 30)
public void a(){
b();
c();
}
@Transactional(timeout = 20)
public void b(){
}
@Transactional(propagation = Propagation.REQUIRES_NEW,timeout = 555)
public void c(){
}
解决方法:
0)、导入 spring-boot-starter-aop
1)、@EnableTransactionManagement(proxyTargetClass = true)
2)、@EnableAspectJAutoProxy(exposeProxy=true)
3)、AopContext.currentProxy() 调用方法
@Transactional(timeout = 30)
public void a(){
OrderServiceImpl orderService = (OrderServiceImpl) AopContext.currentProxy();
orderService.b();
orderService.c();
//用this.b()也没用,他仍然是复制粘贴的效果
}
CAP理论
专指在一个分布式系统中
- C:一致性(分布式系统中所有数据备份,在同一时刻是否是同样的值)
- A:可用性(在集群中一部分节点故障后,集群整体能否响应客户端的读写请求)
- P:分区容错性(大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区(partition)。分区容错的意思是,区间通信可能失败。比如,一台服务器放在中国,另一台服务器放在美国,这就是两个区,它们之间可能无法通信。)
分区容错性是一定要解决的
毕竟我们刚开始选择分布式就是不想要,一台机器挂就完全不可用吧?
假如一台现在网络连接出现问题
那么现在出现数据不一致,你要么选择数据一致性,要么选择可用性,毕竟你选了使用这台机器,那么就得忍受这台机器的数据是不一致的
所以我们只能选择
CP OR AP
保持数据一致性的算法Raft
leader election
在现任领导挂后或者无法取得联系后,在我们从节点自旋等待时间一过,哪个过的快直接转变身份成为候选人(并且自己投自己一票),同时向自己能通信的其他节点发出了拉票请求,如果自己票数达到大多数,那么光荣成为下一位领导
如果出现了多位候选人,且这轮票数不分伯仲,那么进入下一轮选举!!!!
每个节点中都会统计自己目前参与过多少轮选举,即使一直是从节点,这是有目的的
例如东汉末年分三国,原先汉-->刘,之后即使是三个国家,如果势均力敌的情况下,这时候谁都commit不了,例如我们发送的所有要保存到刘的数据都无法commit(顺便一提,发送数据是通过heartbeat实现的,从节点收到后就会返回数据),毕竟获取不到原先票数的大多数return,如果有一方势力达到了大多数,那么在下次融合时,他会成为新领导,也就是自身存经历多少轮选举的目的
(我说的自旋时间)The election timeout is randomized to be between 150ms and 300ms.
不过候选人长时间收不到其他节点的心跳,他会自己成为leader
CP可行吗?
对于多数大型互联网应用的场景,主机众多、部署分散,而且现在的集群规模越来越大,所以节点故障、网络故障是常态,而且要保证服务可用性达到 99.99999%(N 个 9),即保证P 和 A,舍弃 C。
总不能导致不可用吧?
BASE理论
即使做不到强一致性(CAP的一致性),但可以采用适当的采取弱一致性,即最终一致性
Base(基本可用)的相关理念
基本可用指分布式系统出现故障时,允许损失部分可用性(如响应时间,功能上的可用性)
- 响应时间的损失:比以往延长1~2s
- 功能上的损失:部分消费者被引导到一个降级页面
软状态:
软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用性。分布式存储中一般一份数据会有多个副本,允许不同副本同步的延时就是软状态的体现。mysql replication 的异步复制也是一种体现。
说白了,就是在所有机器在极短时间内都一致和很长一段时间不一致的中间值
最终一致性
最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。弱一致性和强一致性相反,最终一致性是弱一致性的一种特殊情况
强一致性、弱一致性、最终一致性
从客户端角度,多进程并发访问时,更新过的数据在不同进程如何获取的不同策略,决定了不同的一致性。对于关系型数据库,要求更新过的数据能被后续的访问都能看到(相当于立即要看到),这是强一致性。如果能容忍后续的部分或者全部访问不到,则是弱一致性。如果经过一段时间后要求能访问到更新后的数据,则是最终一致性
分布式事务的方案
2PC--XA
就是一个簇点里的其他服务,都暂时不提交,等到整个簇点链路都没问题后统一提交
缺点:占用资源呗,根本应付不来高并发
3PC,引入了超时机制(无论协调者还是参与者,在向对方发送请求后,若长时间未收到回应则做出相应处理)
柔性事务TCC(事务补偿型方案)
- 刚性事务:遵循ACID原则,强一致性
- 柔性事务:遵循BASE理论,最终一致性
一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
二阶段 commit 行为:调用 自定义 的 commit 逻辑。
二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。
所谓 TCC 模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。
与3PC区别在于我们在写原先的业务时,顺便手写了撤销业务的代码,例如insert 对应delete
柔性事务-最大努力通知型方案
不保证数据一定能通知成功,但会提供可查询操作接口进行核对
还记得,在食堂用的云闪付吗?就是没通知成功,通常这方案结合MQ实现,例如:通过MQ发送http请求,设置最大通知次数.达到通知次数后即不再通知
类似支付宝的支付,我们在支付后,他会隔几秒就给我们服务器发送信息,直到我们回复他我们已经收到的信息(未达到通知次数下)
柔性事务-可靠消息+最终一致性方案(异步确保型)
实现:业务处理服务在业务事务提交之前,向实时消息服务请求发送消息,实时消息服务只记录消息数据,而不是真正的发送。业务处理服务在业务事务提交之后,向实时消息服务确认发送。只有在得到确认发送指令后,实时消息服务才会真正发送。
就是服务出问题后会先给mq发个回滚信息,但是mq只会保存这条信息,而不会马上发送给其他服务
可靠消息如何确保
最终一致性由业务来保证
可靠消息的丢失
- 防止producer由于mq服务器故障或者网络问题没有发送到mq
- 使用try catch,在catch那边需要用while多次测试,同时我们需要开启producer这边的确认机制(也可用来打破while,但是不完善,因为可能网断了不是短时间会恢复的,一直while可能雪崩),可记录到数据库,采用定期扫描重发的方式
- 做好日志记录,每个消息状态是否都被服务器收到都应该记录
- 做好定期重发,如果消息没有发送成功,定期去数据库扫描未成功的消息进行重发
- 消息抵达Broker,Broker要将消息写入磁盘(持久化)才算成功。此时Broker尚 未持久化完成,宕机。
- publisher也必须加入确认回调机制,确认成功的消息,修改数据库消息状态
- 自动ACK的状态下。消费者收到消息,但没来得及消息然后宕机
- 一定开启手动ACK,消费成功才移除,失败或者没来得及处理就noAck并重 新入队
可靠消息的重复
- 防止消息重复导致我们业务重复进行
- 把业务涉及成幂等性是最直接有效的
- 同时还有一个是message.properties.redelivered字段,可以获取是否是被重新投递过来的,而不是第一次投递过来的(但是我们无法确认他上次发送是否成功运行业务但没有ack or 没有成功运行业务,也即不确定这次是否要运行业务)
- 使用防重表(redis/mysql),发送消息每一个都有业务的唯 一标识,处理过就不用处理
消息积压
-
消费者宕机积压
-
消费者消费能力不足积压
-
发送者发送流量太大
-
上线更多的消费者,进行正常消费
-
上线专门的队列消费服务,将消息先批量取出来,记录数据库,离线慢慢处理
-