1、基本知识概念
(一)数据库事务4大特性:ACID
数据库事务须要实现下面4大特性:sql
- A-Atomicity-原子性,指的是事务要么成功提交,要么失败回滚
- C-Consistency-一致性,指事务操做先后,数据库的完整性不会产生变化,由于事务不可能执行一半,致使数据库内容不正确
- I-Isolation-隔离性 事务不能互相影响,简单来讲就是事务的中间状态不该该被其余事务察觉
- D-Durability-持久性 一旦事务提交则会永久保存在数据库中
其中,只有隔离性根据数据库不一样的隔离级别有不一样的标准,其余三点都是数据库事务必须具有的。数据库
(二)分布式事务
单机事务机制能够有数据库本地事务实现良好的控制,但分布式事务因为涉及多个数据源。这种状况主要有两种:缓存
- 一种是分库分表致使了一台服务器多数据源,这种状况能够经过二阶段提交实现简单的事务控制。
- 一种是应用的微服务化,一个请求由多个服务协做完成,而多个服务又配置了不一样的数据源。这种状况若是是支付等严格要求数据一致性的场景能够用TCC事务模型控制,若是是要求并发性能,但对数据一致性要求不高能够用最终一致性方案控制。
在介绍事务控制模型以前先介绍一下针对分布式事务,咱们须要关注的指标:服务器
1.CAP理论
- C-Consistency -一致性,一个节点数据更新,其余节点保存的数据也应该同步更新
- A-Availability-可用性,节点收到请求,合理时间内必须返回基于请求合理的响应
- P-Partition tolerance-分区容错性 可以容许部分节点网络故障 CAP理论表示这三个指标必然不能同时知足,最多只能知足其中两个。这里简单推论一下:
同时知足这三个指标的状况至关于认可当一个节点没法与其余节点通讯的时候,还能保证在合理时间返回与其余节点一致的合理响应。这显然是不可能的。由此能够得出3个指标不能同时知足。网络
由于P是分布式的基础,咱们设计分布式系统不可能不考虑P,若是不考虑P那么这个分布式系统是极其不稳定的,因此针对CAP理论,通常选择知足CP或AP。架构
- 选择CP意味着当出现部分节点通讯挂了,为了一致性必须阻塞请求,等待数据同步。
- 选择AP意味着当出现部分节点通讯挂了,为了可用性,针对请求,节点只能返回一个不能保证一致性的返回给请求方。
顺带一提,基于此ZooKeeper集群是注重的是CP,当50%的节点挂了以后,那服务发现功能也就挂了。EureKa集群注重CA,注册中心只要有一台可以使用,则不会影响正常的服务发现,另外客户端还会缓存服务、消费者数据。并发
2.BASE理论
BASE理论算是对CAP理论的一种指导性意见。框架
- BA-Basically Available-基本可用,分布式系统出现不可预料故障的时候,要尽可能将故障减少到不影响核心功能。
- S-Soft state-软状态,容许部分节点的数据存在必定的延时,这个延时不影响可用性,这是对强一致的一种妥协,由于真正的强一致是全部节点数据时时刻刻都保持一致,而CAP理论要求的C忽略了网络通讯延迟。
- E-Eventually consistent -最终一致性,最终一致是指通过一段时间后,全部节点数据都将会达到一致。
整体上来讲BASE理论强调的是用最终一致代替了CAP理论的强一致,用基本可用代替了CAP理论的彻底可用。异步
2、分布式事务解决方案模型
(一)二阶段提交-2PC- XA Transactions
1.角色
在二阶段提交中涉及的角色有两个:分布式
- 事务管理器,指的是调度者,通常来讲是单点服务器。
- 本地资源管理器,通常指的是数据库服务,如Oracle,Mysql。
2.应用场景
单点应用,多数据源状况下。
3.原理
原理主要是利用了本地资源管理器自带的事务机制。
4.过程
- 第一阶段,线程开启每一个数据源的事务并执行业务逻辑,并阻塞等待每一个数据源undo,redo日志写入完毕,就差提交
- 第二阶段,若是每一个数据源都没有发生异常,表示能够提交,则线程commit全部数据库的事务,若是有数据源异常了,则回滚全部事务。
强调一点,二阶段提交主要针对于,单点服务器多数据源,若是是多点服务,将引入事务管理器之间的分布式问题,因此二阶段提交模型只适用于单点服务,多数据源,强烈依赖数据库的事务支持。
5.缺点
缺点主要有:
- 单点问题,若是事务管理器宕机,资源管理器将阻塞而且锁住资源
- 同步阻塞,准备就绪到事务管理器发送请求这段时间对于资源管理器来讲是没必要要的阻塞与占用资源时间(资源管理器经过锁机制保证隔离性),对于性能上是一种浪费。
- 没有彻底解决数据不一致,不能保证事务管理器发出的commit命令操做正确到达本地资源管理器,若是通讯失败了,事务管理器是没法察觉的,结果仍是不一致的。
(二)TCC模型
1.角色
涉及的角色有微服务应用,不一样数据源,同一个TCC管理框架,之因此要引用TCC管理框架的缘由在于对于Try操做失败成功的感知,若是涉及微服务调用链是极其复杂的。
1.应用场景
微服务应用,不一样数据源,不要求高并发,一致性要求较高。
2.过程
(1) 第一阶段-Try
根服务调用各个服务的Try方法,各个服务根据业务锁定资源-业务层面上的,没有引入数据库事务,就是将业务涉及的资源存入各个服务的本地方便以后回滚的时候恢复,而后返回给根服务是否执行成功。
(2) 第二阶段-Confirm或Cancel
- Confirm,根服务获取各个服务try是否执行成功,若成功则调用各个服务的Confirm方法,confirm开始把锁定的资源正式刷到业务数据中,若是confirm不成功,则一直重试,务必保证confirm成功。
- Cancel,若是各个服务的try有一个不成功,则调用各个服务的cancel方法,释放锁定的资源,若是调用不成功,也会重试。
注意:Try阶段成功意味着数据库层面的操做大几率是没有问题的,后续的异常只会发生在极端状况下,因此这是tcc根据try操做,去不停重试confirm或cancel的缘由。
3.缺点
- confirm和cancel方法由于有可能被重复调用,因此得从代码上保证幂等,对代码侵入性有点强,复杂度搞。
- 因为整个TCC过程须要锁定资源因此对高并发场景不太友好。
(三)本地消息表方案模型
1.角色
2.思路
不关注MQ丢不丢消息,只关注根服务的本地事务,若是本地事务执行成功,那么记录所需发送的消息表也被插入成功。
此时惟一须要关注的只是消息可以被消费者消费掉,用轮询本地消息表的方式重复发送消息保证最终消息一致性便可。
3.具体方案
根服务完成事务后,将须要与其余服务协调的请求消息存入本地消息表并将状态置为待确认。而后有一个后台线程定时轮询消息表,将待确认消息推送给MQ,MQ再推送给消费者,消费者收到消息后,再消费消息。当消费完消息后,消费者经过必定方式如zookepper或接口回调根服务,而后根服务修改消息表状态为已完成。
3.缺点
强依赖于本地的消息表,数据库压力较大。
(四)最终一致性方案模型
1.角色
- 上游根服务
- 可靠消息服务
- MQ消息中间件
- 下游服务(可能有多个)
2.应用场景
主要应用于不要求强一致,并发要求度高,微服务异步调用场景中。
3.思路
这个模型主要关注三点:
- 在根服务本地事务完成和失败的状况下,确保消息可以正确投递到消费者或取消投递。
- 在下游服务本地事务失败的状况下,确保消息可以被重发。
- 不关注各个优秀MQ框架消息可靠性的保证,即默认消息可以被丢失。
为了解决上述问题,引入了可靠消息服务,用以协调上游服务的事务状态和下游事务状态。经过维护消息的三种状态:待确认、已发送、已成功,确保事务的最终一致性。
3.过程
(1) 上游根服务调用可靠消息服务。
上游根服务同步调用可靠消息服务,参数包含调用下游服务的参数,这些数据由可靠消息服务存入本身的表中,并将这条记录的状态置为待确认。
(2) 上游根服务执行完本身的业务以后在再次调用可靠消息服务。
上游根服务执行完本身的业务以后,将业务执行结果传入可靠消息服务,可靠消息服务根据业务失败结果,若失败则删除消息表中关于这个业务的全部消息表记录,若成功则经过MQ发送消息给下游服务,而后更新刚刚存入的记录将状态置为已发送。注意可靠消息服务的这个方法中,判断业务成功失败,需安排在同一个本地事务(由于发送消息和更新记录必须保证原子性)。
(3) 下游服务接收消息以后消费消息,执行业务完成后回调可靠消息服务。
下游服务接收消息以后消费消息,执行业务完成后回调可靠消息服务。可靠消息服务受到请求后,将本地记录状态置为已完成。
4.注意
(1) 如何保证上游服务对可靠消息服务的100%可靠投递
- 第一次调用若是失败,这种状况上游根服务会收到可靠消息服务错误返回,这种状况上游根服务直接放弃流程。
- 第二次调用若是失败,这种状况可靠消息服务后台定时检查记录中为待确认消息的记录,若是待确认状态时间较长,则对上游服务发起请求,看上游服务是否完成了本身的业务逻辑。若完成,则自动更新记录状态,反之删除记录。(上游服务须要维护本身的事务执行状态在本地表中)
(2) 如何保证下游服务对可靠消息服务的100%可靠投递
可靠消息服务定时检查记录中已发送状态维持时间超时的记录,而后重复发送请求给下游服务。因此这里同时也要求下游服务方法的幂等性。
(4) 另外
这个模型和本地消息表服务很像,区别在于抽离了本地消息表,简化了根服务的操做,虽然消息表仍是存在在可靠消息服务当中,但与业务库相独立,必定程度提升了性能。另外RocketMQ针对这种模型作了优秀的架构实现,至关于集成了上述的可靠消息服务的功能,而且没有消息表的存在,消息的状态由RocketMQ维护,因为我也没有实际使用过RocketMQ,因此就不展开介绍了。基于其余没有提供可靠消息确认机制,
(五)另外几种想到的解决办法
使用RabbitMQ的消息确认机制和消费者开启手动ACK是可以确保消息必定被消费。惟一须要关注的问题在于如何处理本地事务失败、消息者成功消费形成的数据不一致的状况。
对于这种状况,能够这么作:
- 在发送消息的时候,绑定两个监听队列到一个交换机上。一个监听队列负责对这种状况进行补偿,补偿操做为判断本地事务是否成功,若不成功则进行补偿。一个监听队列负责完成下游服务。
3、小结
其实这些模型有不少都有类似的地方,也能作不少变通,不少经过回调、轮询、长链接监听、同步调用的方式其实均可以根据具体场景互相替换或选择不一样的优秀架构。另外须要注意的就是,引入分布式事务是须要维护成本和性能成本的,不少状况下,须要根据业务来看需不须要引入分布式事务。不是很严格的状况,用监控、日志记录的方式采起人工补偿的策略有可能成本更小。