当前位置:网站首页>异构交易场景交互流程及一致性保证
异构交易场景交互流程及一致性保证
2022-06-23 06:23:00 【summer_west_fish】
问题如下:
A 系统联机同步调用 B 系统(A 和 B 不是同一公司系统,不能用分布式事务),如何保证系统间数据准实时一致性(聊聊设计思路即可)?
提醒:需要考虑调用超时、并发、幂等、反交易先到等。
各种异常场景怎么处理要考虑更完善些,如事务隔离、并发、反交易先到调用方和服务方约定(前端客户不可能一直等着)
这种聊思路的问题,往往问的都很大,或者说比较唬人,实际上遇到这种问题,我们要做的就是抽象。抽象出场景,抽象出问题的核心要点。我们能够提炼出要点:
- 非同一公司系统(跨网络,异构);
- 反交易先到(我们基本能确定提问者大概想知道的思路与交易有关);
- 服务方预定;
- 前端客户不可能一直等待(交易流程往往是长事务流程,不能简单依靠单个接口调用,基本上是异步流程)。
有了这些前提,我们就可以基本抽象出讨论的背景和模型。
一、模型与背景提炼
因为前提是 A、B 系统分属不同的公司,也就是说 A B 系统是通过公网进行交互的两套异构系统,极有可能实现的技术栈也各不相同,因此互相之间只能通过暴露在外的接口进行交互,我们就认为是通过 http 接口进行的交互。
由于是 A 系统调用 B 系统,因此我们可以抽象为点对点的消息通信场景,其中 A 为主动拉取方,B 为被拉取方。
问题提到说,我们说不能用分布式事务,其实是在说不能使用强一致 / 类 2PC 的事务实现,如 2PC、3PC、SEATA 等,但是可以利用诸如最大努力通知的柔性方式进行数据的同步。
有了方案,我们接着抽象出 交易单 这个模型,并为其指定状态机:
- 交易单创建时,状态为 初始化,
- A 系统向 B 系统发送交易单时改为, 处理中
- 如果 B 系统同步响应收单失败,则 A 系统修改状态为 失败
- 同步 B 系统同步响应收单成功,则 A 系统修改状态为 已提单
- 当 B 系统处理交易完成,通知 A 系统交易完成,则 A 系统修改交易单状态为 成功,(此处的成功为真实成功,因为已经发生了资金扣除 / 积分等标的物的消耗)
- 当 B 系统处理交易失败,则通知 A 系统交易失败,则 A 系统修改交易单状态为 失败 (此时的失败假定为 B 系统在扣钱之前就失败)
二、超时处理方式
我们讨论一下提单请求发生超时应当如何处理。
A 系统的出口网关向 B 系统的入口网关发起提单请求,这是一个同步通信,对于同步请求的失败(如:签名失败,参数异常失败等),A 系统可以发起重试,此时这种请求属于真实的失败,因为压根没有发起交易行为,所以原则上数据是一致的,对于资金而言就是没有发生扣减等行为。
三、掉单查询
如果 A 系统提交提单请求超时,此时未能收到 B 系统回复的同步 收单成功 的响应,则这时候就存在数据不一致的情况。这个场景就是所谓的 掉单,则 A 系统需要对掉单的数据(状态为处理中)发起掉单查询操作,思路就是定时发起查询,获取 B 系统对交易单的处理情况。
一般而言 B 系统都会通知 A 系统发起掉单查询的建议时间,如发起交易单 10 分钟后即可有处理的确定结果,那么 A 系统就可以对已提交 10 分钟以上,状态为 处理中 的交易单发起掉单查询。也就是说,10 分钟后,这类中间态的数据,AB 系统间可以达成一致。
特别的,如果一次掉单查询没能查到确定的结果,则可以设置下一次继续查询,这里推荐采用 时间衰减策略 进行查询,这是交易场景乃至中间件中常用的一种未知数据定时同步的通用思路。
四、结果通知
对于交易场景,处于对数据实时性的考虑,我们常常希望下游系统处理完数据之后能够及时通知我们结果。
在这个场景中就是 A 系统需要对接 B 系统的 交易结果实时通知接口,当交易单被 B 系统处理完成之后,B 系统会对交易单处理结果发起通知,及时回调 A 系统处理的结果。此时,A 系统成为被调用方,B 系统为调用方,相比于掉单查询,结果通知几乎是准实时的,从 B 系统发起通知到 A 系统接收到通知往往都在百毫秒级别(支付宝支付结果通知能够达到数十毫秒)。
简单总结下,对于超时的处理,我们就是通过掉单查询和实时通知方式,通过主动轮询处理结果与被动接受结果通知的方式,通过推拉结合的方式共同保证 AB 系统之间的数据达成最终一致。
五、并发提单
对于并发提单而言,其实属于老生常谈类的话题。其本质在于分布式场景下请求的防重放处理思路。
核心就在于请求串行化,我们往往通过 CAS、加锁等方式进行处理。
具体到具体的实现细节,CAS 方式有数据库状态标识(状态机)、加锁方式其实就是分布式锁,简单的说就是通过分布式锁方式进行处理,通过对一笔交易单加分布式锁,获取分布式锁成功的请求才能发起请求,发起请求后写入幂等记录,完成请求后释放锁,防止并发提交。
六、幂等思路
对于幂等而言,我们通常需要通过幂等校验来进行,比如:
高并发场景下,将幂等标识写入 Redis 缓存,用于对写请求幂等 或者 请求如果量不大,则通过数据库唯一约束进行幂等处理,保证只有一笔交易单落库(如唯一约束 订单号)。
唯一约束是最后一道防线,用于对写请求幂等 低并发场景下,通过先查询,后插入(更新)的方式也可以进行幂等校验,但是高并发场景下会有重复更新 / 新增的风险,因此往往需要配合分布式锁共同作用,将并发请求串行化 单单就幂等来说,查询天然幂等,更新则可以通过上面的方式进行幂等保证。
七、反交易先到
首先明确何为 “反交易”,反交易,顾名思义,反向交易,我们举个例子就好懂了。
比如说,扣款的反交易,就是冲正(比如说,转账操作,扣除 A 的钱,给 B 加钱失败。则 A 扣除的钱需要补回,这个过程就是冲正。实际的冲正涵盖的范围更广,我们只需要简单认为是扣款的反向操作,但是要区别于提现和充值。)
比如说,A 系统请求对交易单支付 100 元,B 系统扣款成功后向 A 系统返回支付成功的通知消息;此时 B 系统后续操作故障,导致该交易无法继续进行下去,则 B 系统对 A 系统扣除的 100 元执行了冲正之后,通知 A 系统交易已冲正退单。
所谓反交易先到,就是说网络发生拥塞,导致冲正退单的消息,先于支付成功的消息先到了。
我们的 A 系统的交易单不是有状态机么,状态机就是处理反交易先到的利器。
我们要求对于交易的处理是串行的,如何串行,其实简单的说,通过状态机就能很好地实现。
当然要说明的一点是,对于实际情况,需要具体业务具体分析,对于我们当前讨论的场景而言,我们通过状态机能够解决问题,具体过程如下,
我们假定,A 系统的交易单的状态机只能按照 处理中 -> 支付成功 -> 退单 这个流程进行流转,当退单先于支付成功到达时,我们需要在一个事务中同时完成流水的插入,交易单状态的更新。对于更新操作而言,我们的 sql 期望如下:
update order set order_status=退单 where order_status=支付成功 and order_id=xxxxx由于状态机只允许固定的订单状态迁移,我们在更新状态的时候带上老状态,实际上当前的 order_status = 处理中,因此 update 失败。最终处理为通知处理失败,A 系统告知 B 系统对该通知进行重发。那么 B 系统就只能老老实实的重新发起通知,这也是一个合格的交易系统所必须具备的能力。否则你的系统不支持通知重复发起,用户体验也太差了。
此时由于状态机的原因,交易单状态还是处理中,当被拥塞的支付成功的通知到达,交易单状态成功更新为 支付成功 此时执行的 update 语句为:
update order set order_status=支付成功 where order_status=处理中 and order_id=xxxxx后续重试的 退单通知(所谓的反交易)到达后,交易单根据状态机便能够成功进行流转,具体的 update 语句如下:
update order set order_status=退单 where order_status=支付成功 and order_id=xxxxx从我们的分析看出,只要有状态机存在,无论如何,交易单的状态只能按照 处理中 -> 支付成功 -> 退单 这个方式流转而不会发生状态的跃迁跳跃。
所以我们说,状态机,就是系统间处理反交易先到的利器,状态机也是交易类系统通用的神兵利器。
八、前端用户不能一直等待处理思路
还有一点就是前端客户不可能一直等着,实际上在上述的过程中我们已经解答了这个问题。
我们本次分析问题采用整体的方案是基于最大努力通知的思路,核心的步骤就是 同步提单,掉单查询,结果通知。通过对这几个步骤进行结合,我们就能够避免前端一直等待,因为交易属于一个长事务业务,上游 / 前端只需要提交成功就可以去干别的事情了,剩下的复杂操作让下游系统慢慢处理,这其实就是体现了异步的思维。
九、通过对账保障数据一致性
最后还要提一下,对于交易系统而言,数据一致性保证的兜底方案就是对账机制,关于对账,我在近期也会单独写一篇文章进行详细讨论(又一个 flag)。
交易系统通常具备 t+1 的对账,简单的说就是,每天生成前一天的对账单,在我们的这个场景中,A 系统每天都向 B 系统请求自己前一天的交易对账单,下载到本地,通过 A 系统自己的渠道流水号 / 交易单号,与 B 系统提供的交易单进行逐条的对账,这个过程往往能够通过定时任务来自动化的执行,把不一致的交易单对平。从而将两个系统之间的数据达成最终一致,比如说,A 系统没收到 B 系统的通知,掉单查询也没有查到的交易单,往往最终通过对账都能够获取到数据的最终状态。
边栏推荐
猜你喜欢

WPF command directive and inotifypropertychanged

Mysql(十一) — MySQL面试题整理

UNET code implementation

Endnote20 tutorial sharing (unfinished

【***数组***】

Analysis of personalized learning progress in maker Education

MySQL(四) — MySQL存储引擎

PSP code implementation

Deeplab V3 code structure diagram

QT designer cannot modify the window size, and cannot change the size by dragging the window with the mouse
随机推荐
318. 最大单词长度乘积
UNET code implementation
junit单元测试报错org.junit.runners.model.InvalidTestClassError: Invalid test class ‘xxx‘ .No runnable meth
300. 最长递增子序列
数据库原理实验测试题,关于图书分类表
322. 零钱兑换
MySQL Redo log Redo log
C language learning summary
Project_ Filter to solve Chinese garbled code
数据统计与分析基础 实验一 基本语法及运算
Initialization layer implementation
MySQL总结
TensorFlow中的数据类型
About Supervision
Traversal of binary tree and related knowledge
技术文章写作指南
312. 戳气球
315. 计算右侧小于当前元素的个数
别找了诸位 【十二款超级好用的谷歌插件都在这】(确定不来看看?)
Database principle experiment test questions, about book classification table