什么是微服务?微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务内聚不同的业务模块。但是这样的分布式架构风格存在事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上,导致系统的一致性(Consistency)、可用性(Availability)、分区容错(Partition-tolerance)难以实现。
今天我们就来了解一下,微服务架构的分布式事务的解决方案
普通的单机事务,一个服务连接一个数据库,进行一系列数据操作和事务管理,也称为本机事务。
分布式事务-夸库事务,通过夸库产生了多个节点的事务,出现了无法统一管理的问题。
分布式事务-微服务。微服务多个服务之间分别连接不同节点的数据库,服务与服务之间调用也是无法控制服务之间的事务问题。如果中间某个过程出现了问题,就会出现数据不一致。
两阶段提交(2PC)
两阶段提交是通过引入协调者,由协调者来判断最终事务是否真正提交。
第一次prepare阶段,各个参与者向协调者发起预提交的请求。由协调者来做出提交是否成功的判断,如果所有参与者全部预提交成功了,才可进行commit阶段,如果出现其中一个参与者没有成功,则协调者发送通知,让参与者进行回滚事务。
第二次commit阶段,当prepare阶段所有参与者全部预提交成功后。协调者通知参与者提交事务,当然前者没有全部成功的情况下,通知参与者回滚事务也是在这一个阶段
XA/JTA规范的两阶段提交所存在的隐患
由这些隐患可以看出来,所谓的分布式事务并不能像本机事务那样做到完美的数据一致性。它只是尽可能的解决了99%的事务问题,还是会出现一些偶发情况导致数据的一致性问题。XA/JTA的两阶段提交分布式事务只能通过binlog日志,查看问题采取人工回滚事务来解决问题。
开发大规模的分布式系统时会遇到三个特性:一致性(Consistency)、可用性(Availability)、分区容错(Partition-tolerance),而一个分布式系统最多只能满足其中的两项。
分区容错性是分布式系统必然需要面对和解决的问题,因此系统架构师往往需要把精力花在如何根据业务特点在C(一致性)和A(可用性)之间寻求平衡。而前面我们提到的JTA 两阶段提交协议的分布式事务方案,强调的就是一致性;由于可用性较低,实际应用的并不多。
基本可用(Basically Available)
指分布式系统在出现不可预知故障的时候,允许损失部分可用性。
软状态( Soft State)
指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
最终一致( Eventual Consistency)
强调的是所有的数据更新操作,在经过一段时间的同步之后,最终都能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
最简单的一种柔性事务,适用于一些最终一致性时间敏感度低的业务,且被动方处理结果不影响主动方的处理结果。典型的使用场景:一些短信通知类需求功能方面,并且不影响主动方处理结果的场景。例如银行通知、商户通知等
两阶段补偿型方案Try-Confirm-Cancel简称为TCC,属于应用层的一种补偿型方案
Try阶段
完成所有业务检查(一致性),预留业务资源(准隔离性)
Confirm阶段
确认执行业务做确认提交操作,不做任何业务检查, 只使用Try阶段全部成功预留的业务资源。默认:Confirm阶段是不会出错的,即:只要try成功了,Confirm阶段就基本上不会出现失败,当然也存在失败的可能。就像我们上文所说,分布式事务并不能完美的解决事务问题,只是尽可能达到99%的解决事务问题。在这里我们就认为是成功的。
Cancel阶段
主要是在业务执行错误时取消Try阶段预留的业务资源。
举一个例子吧,A向B转账,系统中业务逻辑思路大概是图中应用层部分
问题来了:当Confirm中一个节点出现失败,另一个确认提交成功了之后同样会出现数据一致性问题。该如何解决
由于这是补偿型方案,所以需要程序员写一些补偿代码,例如本机的链接数据库操作的话,可以记录一下失败原因日志或者sql执行时失败的sql,在补偿方案中进行对其失败信息进行解析并且进行数据回滚,来达到数据一致性。记录一些错误日志,若出现系统没办法补偿回滚的数据,还需要人工去进行数据的回滚。
Atomikos,tcc-transaction,spring-cloud-rest-tcc,支付宝tcc
基于普通的消息队列中间件做的解决方案。也是采用的两阶段提交的方案形式,使用数据中间状态和独立的消息服务,来作为数据协调者,具体执行操作我们来举个例子,相信大家就能够明白了。
下单的业务场景:1.减库存 2.生成订单 接下来我们就使用普通mq的方式保证这两部的事务,达到最终数据一致性
1.prepare阶段,发送初始化减库存消息到独立消息服务中并且把订单号传过去,保证后续的状态确认(也就是最后的定时消息状态确认那步)有查询依据,消息服务并不是直接发送消息到mq去操作减库存,而是记录一个状态为init
第一阶段结束后进行系统的业务逻辑,去生成订单数据。
2.confirm阶段,发送确认消息到消息服务中,将记录状态改为send状态。只有状态为send状态下才发送消息到消息队列,消费者接收到消息,去做减库存的操作,当减库存成功之后进行回调消息服务,修改当前消息的状态为end状态
说明:
消息队列一般情况下都会是部署高可用的mq,消息队列的失败原因暂且忽略。首先是prepare阶段出错。那么直接返回错误信息,不会出现数据不一致的情况。
当在confirm阶段出错,就会出现数据不一致的问题,在这里补充一下,消息服务中的状态有(init,send,end)当消息服务中的定时任务检查到状态不是end状态的数据,就会去调用订单服务中的接口,通过第一阶段传过去的订单号查询数据库,或者缓存中是否存在这个订单,如果存在,则放弃整个流程的操作,删除这条订单,进行回滚。从而保证了数据的最终一致性。
当在减库存的服务中出现问题,也会出现数据不一致的问题。但是回调函数就不会去执行修改状态的回调函数,当定时任务开始检查时同样会检查到消息没有完成,并且去查询订单表中数据是否存在,存在则删除。
分布式解决方案中还有一个使用RocketMQ的最终一致性方案,由于原理大概相同,只是用法上有些不同,在这里就没有拿出来讲,在这里总结了四个解决方案,各有各的优点,各有各的缺点,我认为在不同的场景,应用不同的解决方案灵活使用,是可以带来更大的优化程度的。当然,可能还会有更好的一些解决方案或者基于这些方案中的一些补偿,希望小伙伴们可以多多分享一下,互相学习一下。希望这篇文章能够帮助到大家。