对于目前的的分布式架构来说,存在三种调用方式:
1: rpc方式
prc方式也是目前比较常用的方式
比较成熟的rpc调用框架dubbo,HSF,spring cloud 等框架,这这类框架的调用中,调用方的事物和实现方的事物是分开的,在这种情况下,如果要实现事物一致性,需要如何做呢?
针对这个问题从两个方面来考虑:
1: 本地事物一致性
本地事物一致性这个就不说了,针对单数据库的jdbc事物,jpa事物,真多多数据源的JTA事物都可以做到严格的一致性!
2: 远程事务一致性
我理解的远程一致性是:
1) 调用方业务调用成功,远程实现调用失败或其他异常的情况下,本地事物回滚
2)调用方部分业务失败,远程调用方业务成功,本地事物回滚,远程事务回滚!
场景举例:
本地服务A,B 远程服务C 调用顺序为A-->C-->B
1) A 服务执行异常,需要回滚,则本地事物一致性可以支持!
2)A 服务执行成功,C服务调用异常需要回滚,则本地事物一致性可以支持!
3)A 服务执行成功,C服务调用成功,B 服务调用失败在这种情况下,本地事物一致性则不能满足
针对1,2场景来说,系统实现都是没问题的!
针对3情况来说 目前存在的rpc框架中,都没有支持这种事物的,为什么没有呢? 按照搜集到的资料来说,因为调用的复杂性,调用的耗时,实现跨应用的事物太复杂,而且耗时,降低系统的吞吐量,算是一个得不偿失的事情了! 那么这种情况没有其他办法解决了嘛?有,肯定有,程序的世界里没有什么是做不到的! 具体怎么做呢? 补偿事务法! 这个时候应该存在远程服务D,D的作用就是在C调用成功但是却需要回滚的话,做一个反向操作! 这时候存在另外一个问题,D调用失败怎么办? 解决办法是生成日志表, 调用方和远程实现方都同时存在一个日志表,记录调用情况!后台另起进程扫描日志表,指导严格的调用成功后,从日志表删除!(当然,服务D调用失败的情况,严格来讲,失败的可能应尽量低)
2: rest接口方式
rest方式就不在多说了,仅仅是调用方式不一样,但是实现方式和rpc方式完全一样
3: mq消息方式
mq调用方式是:消息的发送方和消息的接收方归属不同的系统,通讯方式是通过mq消息进行通讯!
mq 调用方式需要考虑的问题是:
1: 业务逻辑 和发送消息的先后顺序问题
2: 消息重复发送和消息重复消费的问题
3: 消费方业务失败的问题
针对问题1,2来说,对目前的消息中间件的解决方案分为两种:
1) 不支持事物消息的mq
如kafka,activitymq 等消息中间件
解决方案如下:
(1)Producer端准备1张消息表,把update DB和insert message这2个操作,放在一个DB事务里面。
(2)准备一个后台程序,源源不断的把消息表中的message传送给消息中间件。失败了,不断重试重传。允许消息重复,但消息不会丢,顺序也不会打乱。
(3)Consumer端准备一个判重表。处理过的消息,记在判重表里面。实现业务的幂等。但这里又涉及一个原子性问题:如果保证消息消费 + insert message到判重表这2个操作的原子性?
消费成功,但insert判重表失败,怎么办?关于这个,在Kafka的源码分析系列,第1篇, exactly once问题的时候,有过讨论。
通过上面3步,我们基本就解决了这里update db和发送网络消息这2个操作的原子性问题。
但这个方案的一个缺点就是:需要设计DB消息表,同时还需要一个后台任务,不断扫描本地消息。导致消息的处理和业务逻辑耦合额外增加业务方的负担。
2) 支持事物消息的mq
目前支持事物消息的mq只有阿里开源roketMq,目前是apache的项目!
具体实现:
具体来说,就是把消息的发送分成了2个阶段:Prepare阶段和确认阶段。
具体来说,上面的2个步骤,被分解成3个步骤:
(1) 发送Prepared消息
(2) update DB
(3) 根据update DB结果成功或失败,Confirm或者取消Prepared消息。
详细处理这里不再多说,感兴趣的可以看下具体实现!
问题3的解决方案:
目前对于问题三,目前没有中间件去解决这个问题,从工程实践角度讲,这种整个流程自动回滚的代价是非常巨大的,不但实现复杂,还会引入新的问题。比如自动回滚失败,又怎么处理?
解决这个问题的方法是:手工介入! 如消费者消费失败,则消费者发送另外的业务消息告知生产者去恢复数据!
总结
对于分布式架构来说,达到强一致性基本上是不可实现的,或者是得不偿失,cap原则也告诉我们在业务处理中一致性,分区,和可用性不可能同时达到! 退而求其次,就只能根据base原来来达到基本一致性了(通过异步等方式)。对于架构师而言,如果合理的处理事务,架构怎么实现,还是多多思考吧!