一,前言
大家好,我是小墨。
这一章我们介绍下分布式情况下进行事务的处理的方法----消息队列异步解决方案,因为我主要是使用rabbitmq这种MQ的,现在淘宝开源的RocketMQ已经支持分布式事务了,但没调研过,不做分析。也通过这个方案我们聊一聊分布式中的BASE理论在这里的体现。
对于事务我不多加介绍,分布式事务有几种解决方案如:2PC(两阶段提交),3PC(三阶段提交),补偿事务(TCC),消息队列异步。但是考虑到分布式下各种异常情况考虑下,常见生产方案一般都是消息队列异步解决方案,我们结合分布式理论聊聊消息队列异步解决方案。
二,分布式理论
对于分布式一致性对于计算机科学一直是个思考的话题,在分布式环境下需要考虑:
1,通信异常
从集中式转变到分布式后,就需要网络来进行沟通,同时间带来网络因素的不可靠性,网络带来了一定的延时,而且还可能网络的硬件设备异常带来的无法顺利完成一次网络通信,所以会遇到消息丢失和延迟。
2,网络分区
网络分区指的是 :当网络由于发生异常情况,导致分布式系统中部分节点之间的网络延时不断增大,最终导致组成分布式系统的所有节点中,只有部分节点之间能够正常通信,而另一些节点则不能。当网络分区出现时,分布式系统会出现局部小集群,极端情况下需要让小集群扛起全部功能
3,三态
因为我们谈到了分布式环境下,网络会出现各种各样问题,所以每次分布式系统的请求和响应都有三种状态:
成功,失败,超时,对于超时的话,因为网络是需要一反一复的,会发生:
- 发送时Server端请求没有被client端接受。
- 请求被接收后,client端处理后在答复的时候发生消息丢失
4,节点故障
指的是分布式系统的server节点出现宕机或者“僵死”现象。
CAP理论
CAP理论告诉我们:一个分布式系统不可能同时满足一致性(C:Consistency)、可用性(A:Availability)和分区容错性(P:Partition tolerance)这三个基本需求,最多只能同时满足其中两项
1、一致性
一致性是指数据分布式环境下存在的多个副本之间能否保持一致的特性。
对于有多个分布式节点的系统来说,存在某一个节点数据更新成功但是其他节点仍然为老数据,称为“分布式数据不一致”。
需要时间才能完全复制完,称为最后一致性
2、可用性
可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果
3、分区容错性
分区容错性约束了一个分布式系统具有如下特性:分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。
由于这三者无法全部实现,可以实现为:
选择 | 说 明 |
CA | 放弃分区容错性,加强一致性和可用性,其实就是传统的单机数据库的选择 |
AP | 放弃一致性(这里说的一致性是强一致性),追求分区容错性和可用性,这是很多分布式系统设计时的选择,例如很多NoSQL系统就是如此 |
CP | 放弃可用性,追求一致性和分区容错性,基本不会选择,网络问题会直接让整个系统不可用 |
BASE理论
BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)
BASE理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性
- 基本可用:指分布式系统在出现不可预知故障的时候,允许损失部分可用性
- 软状态:允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性
- 最终一致性:最终一致性强调的是所有的数据副本,在经过一段时间的同步之后,最终都能够达到一个一致的状态
三,消息队列异步实现分布式事务
我按照我们公司使用的积分充值系统举例,如下图
当用户充值后,支付系统得到了了微信/支付宝的支付成功回调后,更新了订单流水状态,然后由于积分账户系统
和支付系统解耦,所以会发送MQ消息到消息中间件,然后用户积分去消费消息实现用户积分增加操作。
所以这里对于整个系统而言,需要保证---支付系统确保订单流水更新成功后余额充值消息成功发送到MQ,积分系统一定
处理成功,实现最终一致性。
所以我们使用分布式事务需要实现“支付系统--MQ服务--用户积分系统”三种的数据一致性,我举了目前我们系统的实现方案:
接下来我分几部分解释这个核心流程:
1,支付系统如何确保更新订单状态后消息一定投递到消息队列中?
我们这个系统加了一个消息确认服务,作为本地消息表。用于借助数据库事务保证异步消息的数据的一致性。
支付系统进行本地数据库事务前,会先发送消息到“消息确认服务”,入库格式为{id:"订单id:用户id",创建时间:?,状态:待确认},
直到支付系统完成本地订单的事务后,修改消息为{id:"订单id:用户id",创建时间:?,修改时间:?,状态:已成功}。然后投递消息,注意这里修改消息和投递消息必须在事务内,如果投递失败,我们会回滚消息状态为->“待确认”,警告人员进行处理,一般是MQ问题,网络问题
至于上游失败的话我们则会删除消息确认服务中的数据库的消息,然后支付系统重新处理。
线程扫描的作用?
各位在看这张图的时候应该会注意到下面的线程扫描,我们讲解起到的用途。其实还是因为在分布式系统下存在三态:“成功,失败,超时”。
我们为了保证本地消息表和消息队列中状态如何保持一致,采用 2PC 的方式。在发消息之前落库,然后发消息,在得到同步结果或者消息回调的时候更新本地数据库表中消息状态为“已发送”。然后只需要通过定时轮询的方式对状态未已记录但是未发送的消息重新投递就行了
2,用户积分系统如何确保对MQ服务消费100%完成?
用户积分系统比较简单,就基本按照rabbitmq的消息可靠性做即可:
- 手动ACK
- 失败NACK,重试原则
- 如果成功,回调消息确认服务接口,将该消息置为“已完成”
注意“线程扫描”也会比较已发送消息是否超过我们设置的超时时间仍然没流转状态为已完成,超时再投递,甚至告警。
当然这时幂等性都是得考虑,防止重复。
四,总结
本文我们介绍了分布式理论,然后讲述了分布式事务一种常见实现方式:消息队列解决方案,这里的关键是使用
本地消息表,实现 异步消息队列消息与本地消息的一致性。欢迎观看,觉得有收获可以点赞评论支持一下
参考文章