微服务作为当下最火热的服务框架,其给我们开发带来了很多的好处,例如功能复用,独立部署,系统容错等,但是同时也有一些不方便的地方,其中最突出的就是数据一致性的问题,今天我们开始讨论一下这个数据一致性的解决方案:
在没有使用微服务的时候我们一般都是使用同一个数据库,只要我们使用ACID的数据强一致性就可以解决问题,但是微服务下不同的模块使用不同的数据库,这样就无法保证数据的一致性,我们就需要讨论新的方案----分布式下的数据一致性问题。首先我们需要了解的是两个理论:CAP理论和BASE理论
CAP理论:
CAP 是指在一个分布式系统下, 包含三个要素:Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性),并且三者不可得兼。
C:Consistency,一致性,所有数据变动都是同步的。
A:Availability,可用性,即在可以接受的时间范围内正确地响应用户请求。
P:Partition tolerance,分区容错性,即某节点或网络分区故障时,系统仍能够提供满足一致性和可用性的服务。
在分布式系统下,我们保证了分区容错性,所以只能在可用性和数据一致性上面做取舍,现实决定了我们只能选择可用性,所以数据的一致性我们只能通过后期的补偿机制来达到了。
BASE理论:
BASE 理论主要是解决 CAP 理论中分布式系统的可用性和一致性不可兼得的问题。BASE 理论包含以下三个要素:
BA:Basically Available,基本可用。
S:Soft State,软状态,状态可以有一段时间不同步。
E:Eventually Consistent,最终一致,最终数据是一致的就可以了,而不是时时保持强一致。
BASE理论与ACID的数据库强一致性不同,是对CAP理论的支持,通过牺牲强一致性来保证系统的可用性,这样会导致数据在一段时间内可能出现不同步的情况,一旦出现异常,需要进行回滚等操作。但是这样的操作也是可以达到最终的数据一致性的。
介绍完上面的理论,下面的我们看一下我们正在使用的分布式解决方案吧!
方案主要是分为三个模块,上游应用主要是处理业务并发送MQ消息,可靠消息服务和MQ消息组件负责协调上下游消息的传递并保证数据的一致性,下游应用负责监听MQ消息并执行自身的业务。如图所示:
第一阶段:
注意点:上游应用将本地执行和消息发送绑定在同一事务中,要么同时成功,要么同时回滚
步骤:
上游应用发送待确认消息到可靠消息系统----不会导致出现一致性问题
可靠消息系统保存待确认消息并返回----不会导致出现一致性问题
上游应用执行本地业务----不会导致出现一致性问题
上游应用通知可靠消息系统确认业务已执行并发送消息----会导致出现一致性问题
可靠消息系统修改消息状态为发送状态并将消息投递到 MQ 中间件----会导致出现一致性问题
上游应用执行完成,下游应用尚未执行或执行失败时,此事务即处于 BASE 理论的 Soft State 状态
第二阶段:
注意点:
下游应用监听 MQ 消息并执行业务,并且将消息的消费结果通知可靠消息服务。
可靠消息的状态需要和下游应用的业务执行保持一致,可靠消息状态不是已完成时,确保下游应用未执行,可靠消息状态是已完成时,确保下游应用已执行。
步骤:
下游应用监听 MQ 消息组件并获取消息----不会导致出现一致性问题
下游应用根据 MQ 消息体信息处理本地业务----不会导致出现一致性问题
下游应用向 MQ 组件自动发送 ACK 确认消息被消费----不会导致出现一致性问题
下游应用通知可靠消息系统消息被成功消费,可靠消息将该消息状态更改为已完成----会导致出现一致性问题
为了确保上下游数据的最终一致性,在可靠消息系统中,需要开发消息状态确认和消息重发两个功能以实现 BASE 理论的 Eventually Consistent 特性。
分析:
在上游应用与消息中间件交互的时候,如果消息中间件或者上游应用发生宕机,就有可能没有将MQ消息发送出去,一旦发生这种情况,就会导致上游应用已经执行业务操作完毕,但是MQ消息并没有收到,上游应用也无法确认追踪到这一情况,最终导致无法给下游应用正确的反馈,数据最终一致。面对这种情况,我们通常会使用confirm机制和returnMessage机制来确认上游应用已经发送消息成功。总结下就是:
如果消息没有到exchange,则confirm回调,ack=false
如果消息到达exchange,则confirm回调,ack=true
exchange到queue成功,则不回调return
exchange到queue失败,则回调return(需设置mandatory=true,否则不回回调,消息就丢了)
在下游应用与消息中间件交互的时候,如果发生宕机的情况,就会发生下游业务执行完成,但是MQ并未接收到反馈消息,这个时候MQ还是一直会向其发送消息,这个时候就需要保证接口的幂等性了。
上游应用对应的是消息状态确认,下游应用对应的是消息重发。以上就是我们系统解决分布式系统下数据一致性的方案的思路。
Confirm机制的最大优点在于异步,生产者在发送消息以后,即可继续执行其他任务。而服务器返回Confirm后,会触发生产者的回调函数,
生产者在回调函数中处理Confirm信息。如果消息服务器发生异常,导致该消息丢失,会返回给生产者一个nack,表示消息已经丢失,这样生产者就可以通过重发消息,
保证消息不丢失。Confirm机制在性能上要比事务优越很多。但是Confirm机制,无法进行回滚,就是一旦服务器崩溃,生产者无法得到Confirm信息,
生产者其实本身也不知道该消息是否已经被持久化,只有继续重发来保证消息不丢失,但是如果原先已经持久化的消息,并不会被回滚,这样队列中就会存在两条相同的消息,系统需要支持去重。
代码地址:https://github.com/shouchengdai/rabbitmq_test
参考博客:
http://www.cnblogs.com/study-everyday/p/7605619.html?tdsourcetag=s_pctim_aiomsg
https://www.jishux.com/p/9faed5edeec96a46?tdsourcetag=s_pctim_aiomsg