我们的服务器从单机发展到拥有多台机器的分布式系统,各个系统之前需要借助于网络进行通信,原有单机中相对可靠的方法调用以及进程间通信方式已经没有办法使用,同时网络环境也是不稳定的,造成了我们多个机器之间的数据同步问题,这就是典型的分布式事务问题。
在分布式事务中事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。分布式事务就是要保证不同节点之间的数据一致性。
1、2PC(二阶段提交)方案 - 强一致性
2、3PC(三阶段提交)方案
3、TCC (Try-Confirm-Cancel)事务 - 最终一致性
4、Saga 事务 - 最终一致性
5、本地消息表 - 最终一致性
6、MQ 事务 - 最终一致性
这里重点关注下使用消息队列实现分布式的一致性,上面几种的分布式设计方案的具体细节可参见文章最后的引用链接
本地消息表-最终一致性
消息的生产方,除了维护自己的业务逻辑之外,同时需要维护一个消息表。这个消息表里面记录的就是需要同步到别的服务的信息,当然这个消息表,每个消息都有一个状态值,来标识这个消息有没有被成功处理。
发送放的业务逻辑以及消息表中数据的插入将在一个事务中完成,这样避免了业务处理成功 + 事务消息发送失败
,或业务处理失败 + 事务消息发送成功
,这个问题。
举个栗子:
我们假定目前有两个服务,订单服务,购物车服务,用户在购物车中对几个商品进行合并下单,之后需要清空购物车中刚刚已经下单的商品信息。
1、消息的生产方也就是订单服务,完成了自己的逻辑(对商品进行下单操作)然后把这个消息通过 mq 发送到需要进行数据同步的其他服务中,也就是我们栗子中的购物车服务。
2、其他服务(购物车服务)会监听这个队列;
1、如果收到这个消息,并且数据同步执行成功了,当然这也是一个本地事务,就通过 mq 回复消息的生产方(订单服务)消息已经处理了,然后生产方就能标识本次事务已经结束。如果是一个业务上的错误,就回复消息的生产方,需要进行数据回滚了。
2、很久没收到这个消息,这种情况是不会发生的,消息的发送方会有一个定时的任务,会定时重试发送消息表中还没有处理的消息;
3、消息的生产方(订单服务)如果收到消息回执;
1、成功的话就修改本次消息已经处理完,也就是本次分布式事务的同步已经完成;
2、如果消息的结果是执行失败,同时在本地回滚本次事务,标识消息已经处理完成;
3、如果消息丢失,也就是回执消息没有收到,这种情况也不太会发生,消息的发送方(订单服务)会有一个定时的任务,定时重试发送消息表中还没有处理的消息,下游的服务需要做幂等,可能会收到多次重复的消息,如果一个回复消息生产方中的某个回执信息丢失了,后面持续收到生产方的 mq 消息,然后再次回复消息的生产方回执信息,这样总能保证发送者能成功收到回执,消息的生产方在接收回执消息的时候也要做到幂等性。
这里有两个很重要的操作:
1、服务器处理消息需要是幂等的,消息的生产方和接收方都需要做到幂等性;
2、发送放需要添加一个定时器来遍历重推未处理的消息,避免消息丢失,造成的事务执行断裂。
该方案的优缺点
优点:
1、在设计层面上实现了消息数据的可靠性,不依赖消息中间件,弱化了对 mq 特性的依赖。
2、简单,易于实现。
缺点:
主要是需要和业务数据绑定到一起,耦合性比较高,使用相同的数据库,会占用业务数据库的一些资源。
MQ 事务-最终一致性
下面分析下几种消息队列对事务的支持
RocketMQ 中的事务,它解决的问题是,确保执行本地事务和发消息这