架构设计 | 基于消息中间件,图解柔性事务一致性
本文源码:GitHub·点这里 || GitEE·点这里
一、最大努力通知
TCC分段提交适用分布式架构中对一致性、实时性要求较高的业务场景,在实际业务中也存在实时性比较低的业务,例如常见的短信通知,客户端消息,运营体系更新等业务,这时候为了减轻核心流程的复杂度和压力,可以采取最大努力通知方式实现柔性事务的管理。
例如常见的第三方支付业务中,本地业务和支付端业务处理完成之后都会生成消息通知,基本流程如下:
- 本地业务预处理完成之后;
- 请求第三方支付服务;
- 支付操作成功对该账号发送消息;
- 支付服务回调本地业务;
- 本地业务生成系统通知消息;
上述流程的消息场景中有一些基础特点,在核心业务处理完成之后,发送消息通知,允许失败,在指定时间段内或者指定重试次数之后,允许消息丢失情况存在,即消息的不可靠性。
在实际的支付系统中,启动每日对账校验时会对当日的流水做校验,如果发现支付流水有未完成的流程,会有状态弥补,后续可以继续处理,这种手段在对账中很常用。
二、可靠消息
分布式事务基于可靠消息最终一致性的实现方案,既然是可靠消息,则要求MQ必须支持事务管理,这样才能保证业务前后一致性。
1、RocketMQ事务消息
RocketMQ在4.3版中开始支持分布式事务消息,采用2PC的思想来实现了提交事务消息,同时增加一个补偿逻辑来处理二阶段超时或者失败的消息,如下图所示:
上图说明了事务消息的大致方案,其中分为两个流程:正常事务消息的发送及提交、事务消息的补偿流程。
1.1 发送及提交
(1)发送消息(half消息,即发送但不被消费);
(2)服务端响应消息写入结果;
(3)根据发送结果执行本地事务,如果写入失败,此时half消息对业务不可见,本地逻辑不执行;
(4) 根据本地事务状态执行Commit或者Rollback(Commit操作生成消息索引,消息对消费者可见)
1.1 补偿流程
(1)对没有Commit/Rollback的事务消息(pending状态的消息),从服务端发起一次“回查”;
(2)Producer收到回查消息,检查回查消息对应的本地事务的状态;
(3)根据本地事务状态,重新Commit或者Rollback;
其中,补偿阶段用于解决消息Commit或者Rollback发生超时或者失败的情况。
1.3 设计原理
在RocketMQ事务消息的主要流程中,一阶段的消息如何对用户不可见。其中,事务消息相对普通消息最大的特点就是一阶段发送的消息对用户是不可见的。那么,如何做到写入消息但是对用户不可见呢?RocketMQ事务消息的做法是:如果消息是half消息,将备份原消息的主题与消息消费队列,然后改变主题为RMQ_SYS_TRANS_HALF_TOPIC。由于消费组未订阅该主题,故消费端无法消费half类型的消息,然后RocketMQ会开启一个定时任务,从Topic为RMQ_SYS_TRANS_HALF_TOPIC中拉取消息进行消费,根据生产者组获取一个服务提供者发送回查事务状态请求,根据事务状态来决定是提交或回滚消息。
2、最终一致性
基于上述RocketMQ事务消息可靠性的特点,即可以实现某类业务下事务的最终一致性。消息发送一致性是指产生消息的业务动作与消息发送一致,也就是说如果业务操作成功,那么由这个业务操作所产生的异步消息一定要发送出去,否则就业务失败回滚,消息也会丢弃。
流程基本如下:
- 发送half事务消息,无法被消费;
- 本地业务代码逻辑处理完成;
- 发送确认消息,标识该消息可以消费;
- 如果消息生产方异常,取消整体动作;
该流程主要针对消息生产方,在实际开发中,消息的消费方也一样很难处理,要保证最终一致性,必然会面对一个问题,消费方异常,消息不断的重试,可能存在部分业务处理成功,部分业务处理失败的情况,这时候就要解决服务接口的幂等性问题。
三、幂等接口
1、幂等简介
编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。就是说,一次和多次请求某一个资源会产生同样的作用影响。
在复杂的异步流程中,尤其注意失败重试问题,通常支付流程中,每次接口被请求,对每一步数据更新的操作,都会前置一步状态查询的流程,用来判断下一步的数据更新是否该执行。
2、幂等接口
在系统服务接口请求中,任何明确的接口响应,例如失败或成功,这样业务流程都好处理,但是例如支付场景如果请求超时,如何判断服务的结果状态:客户端请求超时,本地服务超时,请求支付超时,支付回调超时,客户端响应超时等,或者基于MQ的不断重试机制,在部分业务异常状态下,始终没有返回成功,则消息会一直重试。
这就需要设计流程化的状态管理,尤其在消息重试机制下,很少会再次对重试的业务接口使用重度的事务控制,有些业务被执行完毕,只需要判断一个状态,下次消息重试跳过即可,只需要把未处理的业务补偿处理即可,在重试机制下,在部分业务没有全部执行成功之前,消息会一直重试,直到最终全部完成。
四、源代码地址
GitHub·地址 https://github.com/cicadasmile/data-manage-parent GitEE·地址 https://gitee.com/cicadasmile/data-manage-parent

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
基于 Flink 的典型 ETL 场景实现
作者:买蓉 · 美团点评高级技术专家整理:赵阳(Flink 社区志愿者)校对:苗浩冲(Flink 社区志愿者) 本文将从数仓诞生的背景、数仓架构、离线与实时数仓的对比着手,综述数仓发展演进,然后分享基于 Flink 实现典型 ETL 场景的几个方案。 1.实时数仓的相关概述 1.1 实时数仓产生背景 我们先来回顾一下数据仓库的概念。 数据仓库的概念是于90年代由 Bill Inmon 提出, 当时的背景是传统的 OLTP 数据库无法很好的支持长周期分析决策场景,所以数据仓库概念的4个核心点,我们要结合着 OLTP 数据库当时的状态来对比理解。 面向主题的:数据仓库的数据组织方式与 OLTP 面向事务处理不同。因为数据仓库是面向分析决策的,所以数据经常按分析场景或者是分析对象等主题形式来组织。 集成的:对于数据仓库来说,经常需要去集合多个分散的、异构的数据源,做一些数据清洗等 ETL 处理,整合成一块数据仓库,OLTP 则不需要做类似的集成操作。 相对稳定的:OLTP 数据库一般都是面向业务的,它主要的作用是把当前的业务状态精准的反映出来,所以 OLTP 数据库需要支持大量的增、删、改的...
- 下一篇
Linux进程间通信(中)之信号、信号量实践
上节我们分享了Linux进程间通信的其中两种方式:管道、消息队列,文章如下: Linux进程间通信(上)之管道、消息队列实践 这节我们就来分享一下Linux的另外两种进程间通信的方式:信号、信号量。 1、信号 我们使用过windows的都知道,当一个程序被卡死的时候不管怎样都没反应,这样我们就可以打开任务管理器直接强制性的结束这个进程,这个方法的实现就是和Linux上通过生成信号和捕获信号来实现相似的,运行过程中进程捕获到这些信号做出相应的操作使最终被终止。 信号的主要来源是分为两部分,一部分是硬件来源,一部分是软件来源;进程在实际中可以用三种方式来响应一个信号:一是忽略信号,不对信号做任何操作,其中有两个信号是不能别忽略的分别是SIGKILL和SIGSTOP。二是捕捉信号,定义信号处理函数,当信号来到时做出响应的处理。三是执行缺省操作,Linux对每种信号都规定了默认操作。注意,进程对实时信号的缺省反应是立即终止。 发送信号的函数有很多,主要使用的有:kill()、raise()、abort()、alarm()。 先来熟悉下kill函数,进程可以通过kill()函数向包括它本身在内的...
相关文章
文章评论
共有0条评论来说两句吧...