关于程序员:基于有限状态机与消息队列的三方支付系统补单实践

10次阅读

共计 4409 个字符,预计需要花费 12 分钟才能阅读完成。

作者:字节跳动技术团队
链接:https://juejin.cn/post/690332…

  1. 引言

======

在日常生活中,从线下的超市购物到线上的外卖点餐、电商网购等,领取无时无刻不在产生,不论是通过现金、pos 机刷卡还是微信支付宝等第三方领取。线上领取有着及时便捷零打碎敲的极致体验,当然也有多数的时候体验不够丝滑,比方晚期咱们在 PC 版 12306 买火车票,当领取实现后,订单的领取状态却常常不能及时更新,会有一段时间的提早,有时甚至会期待很长时间处在未领取状态。

在领取的过程中因为各种各样的起因(比方内部渠道解决出了问题,异步回调迟迟不来)导致流程走了一半停了下来,用户看到订单仍然是未领取状态,会手足无措,此时就须要一种机制来推动实现这笔交易。本文就以三方领取零碎中的补单机制为例,来介绍一种较为通用的单据弥补模式。

  1. 三方领取零碎简介

============

1.1 什么是三方领取

所谓第三方领取,就是和各大银行签约,独立于商户和银行,具备肯定实力和信用保障的,为商户与消费者提供领取结算服务的第三方独立机构。它是处于买方和卖方之间具备公信力的第三方,承当担保人和资金托管人的角色。三方领取也能够称为虚构账户领取,由消费者在第三方领取机构开设虚构账户,并用虚构账户中的资金进行领取。业界常见的三方领取有支付宝、微信领取、美团领取、京东领取等等。

1.2 三方领取中的交易 & 领取零碎

交易是什么,最直观的形容就是“一手交钱、一手交货”,交易会使买卖双方造成债务和债权关系。交易的存在是领取产生的前提,用户通过应用某种领取形式去实现交易。交易是领取流程的驱动者,依据具体场景组合不同的领取指令,来实现交易资金的转移。

领取是交易解决资金流的工具,目标是清偿债务和债权关系;反对多种领取形式(如银行卡领取、余额领取、优惠券组合领取、相似花呗的信用领取等),负责对接账务、会计、计费零碎等资金解决能力,接管领取指令,驱动实现资金替换。将理论的领取行为(理论资金)与外部的记账(虚构资金)相结合,保障虚实统一。

三方领取整体业务架构如图 1 所示,其中交易外围与领取外围在业务划分上处于 ” 收单领取域 ”,具备一般交易的收款、付款、退款及充值、转账与提现等常见性能,还包含了撑持电商业务的合单领取、担保与分账的能力。其中交易与领取外围都有一个异样查补模块,它囊括了所有业务的弥补流程,也是本文次要介绍的局部。

图 1. 三方领取业务架构

  1. 什么是补单 & 为什么须要补单

=================

一笔交易在领取过程中因为链路中的各种异样而中断,此时的交易处在一种中间状态,这种状况俗称 ” 卡单 ”,即卡在那里不动了,没有持续向下推动。还有一种情景,领取外围向渠道发动了扣款,渠道受理后,银行卡扣款胜利,但因为种种原因没有向领取外围发动回调,导致这笔领取没有实现,用户没有享受到相应权利,但银行卡的钱曾经扣了,这种状况称为“掉单”。

不论是卡单还是掉单,都是处在两头态的订单。补单就是将处在两头态的订单进行弥补,直到推动到终态(胜利或失败)。补单个别有两个关键点,一个是弥补的有效性,极其状况下可能弥补屡次都不胜利,不能就此放弃了,须要有兜底的机制;另一个是弥补的及时性,因为交易挂起的工夫越长,用户的体验越差。

交易外围和领取外围的补单井水不犯河水,具备肯定类似水平的设计与实现,咱们就以领取外围的补单为例介绍下异样补单机制。

  1. 补单是如何实现的

============

本章首先理解一下业务流程,阐明一下实现补单须要的前提根底,而后介绍一下补单机制的演进路线,每个版本存在的问题以及在下一个版本是如何解决的。

3.1 无限状态机与幂等性

标识资金操作的无限状态机

咱们首先以用户发动一笔余额提现为例,阐明下业务流程,简化后如图 2 所示。

图 2. 余额提现流程

首先生成领取订单,而后申请账务零碎,扣减用户账户下的余额,接着向内部渠道发动付款操作,资金操作实现后对立处理结果并更新单据信息,最初还有一些对上下游的异步告诉,模式上包含音讯和 RPC 回调。

咱们将每个要害资金操作的状态记录落库,如下表所示。其中出款即钱从哪里来,入款即钱到哪里去,冲正即回滚交易。在提现场景下就是从用户领取账户出钱,到用户的银行卡去。其余场景比方充值(银行卡 -> 用户领取账户),会有不同的资金流向。表中最初两行加粗的场景还没有达到终态,是咱们须要去弥补的。

为了便于了解,咱们在这里省略了冲正的相干操作,一次余额提现流程的状态机转换如图 3 所示。

图 3. 附带状态机转换的余额提现流程

可重入与幂等性保障

发动一次领取会波及到多个零碎间调用,因为网络起因导致的通信超时是常见的问题。同时上游零碎也可能会从新发动申请,这须要咱们的零碎保障操作后果的幂等性。多数用户也可能会有多个终端同时操作带来的并发申请,须要咱们保障接口的可重入性。除了服务本身,咱们的上游依赖同样也须要保障其接口具备同样的能力。

先说一说什么是可重入与幂等性。

  • 可重入:在并发申请下能够保障正确性。
  • 幂等性:反复屡次雷同的输出,取得雷同的输入。幂等性在技术上其实也蕴含了可重入的要求。

具体到业务中,幂等性是针对一笔曾经达到终态的领取而言的,对于首次未能拿到最终业务后果的申请,再次发动调用的后果能够是不同的(解决中 -> 解决胜利或失败)。那么咱们如何保障业务流程的可重入与幂等性呢?咱们别离拆解每一步来看:

  1. 生成领取单:首先领取单据能够将业务方传递的可保障唯一性的内部订单号作为惟一索引,插入数据库时若产生惟一索引抵触,则将查问已有数据进行幂等参数校验,若与当次申请的参数完全一致阐明是反复申请,可应用 DB 中的领取单持续推动后续流程;若不统一则返回谬误。
  2. 资金解决流程:账户和渠道零碎各自保障其接口幂等性。咱们也保护了每个上游操作的状态,依据状态机决定是否要持续推动,尽量不向上游输入反复流量。比方领取单曾经实现了所有资金解决,状态机曾经是终态,那么接口能够间接返回相应后果。
  3. 更新领取单信息,先将领取单加行级排他锁,再进行更新,保障多个并发申请只会有一个胜利。
  4. 异步告诉,在领取单推动到终态后进行。

有了可重入与幂等性保障,咱们就能够大量地复用正向流程来实现补单接口。

3.2 初始版

一般来说最常见的补单模式是设置一个定时工作,定时去扫表实现业务弥补,实现比较简单,然而及时性不够,对于收款转账类的交易而言用户体验不佳。咱们采纳了通过音讯队列进行即时弥补的形式,如图 4 所示。弥补消费者并没有去做弥补工作,而是解析音讯而后通过 RPC 调用领取外围裸露的弥补接口。为什么不在消费者中间接弥补?这么做次要是思考将逻辑收敛到一处便于保护。

图 4. 余额提现产生异样时的补单流程

当然,补单可能仍然失败,咱们能够再次发送弥补音讯。但不能始终这样循环上来,所以须要设置一个最大重试次数,超出后不再发送。当弥补屡次都不胜利时,个别是上游零碎出了问题,这时咱们须要放缓弥补的频次,随着重试次数减少,会让每次弥补工夫距离逐步增大,通过 RocketMQ 的延时音讯实现。

这里有三个很容易想到的问题:

  1. 如果异样音讯发送失败,上游也没有重试机制,这笔订单就可能始终挂在这里不了了之了,如图 5 所示。
  2. 弥补消费者申请领取外围补单时不胜利,可能是超时但理论弥补胜利了,或者是申请压根没有过来,如图 6 所示。
  3. 如果重试达到最大次数仍然没有胜利,这笔单子该怎么解决。

图 5. 弥补音讯发送失败

图 6. 弥补音讯生产失败

3.3 改进版

针对问题 1,如果重试仍然发送失败,咱们通过引入一张异样音讯表,将发送失败的音讯落库来解决。表中记录了订单号、以后的重试次数、异样分类、记录状态、音讯体等字段。如果图 6 中的第 4 步音讯发送失败,就将这笔订单放入 DB 中的一张异样表中,会设置一个定时工作去解决。以目前 RocketMQ 的可用性来说,异样数据很少会呈现。如图 7 所示。

图 7. 针对音讯生产 / 生产异样的改进版 – 1

对于第 2 个问题,如果弥补消费者调用领取外围失败,弥补消费者 HandleMessage 会向下层抛出 error,利用 RocketMQ 的梯度重试机制,当生产重试次数达到肯定下限后会进入死信队列。如图 8 所示,这种状况个别是服务或网络出了问题,待复原之后,能够从死信队列拉取这些音讯再对立解决。

图 8. 针对音讯生产 / 生产异样的改进版 – 2

当然还有更极其的状况,申请 MQ 和 DB 都失败了咋办?以目前 MQ 和 DB 的可用性来说,同时失败这种根本能够不必思考,报警人工染指即可。

针对问题 3,如果重试超过最大次数,仍然弥补不胜利,个别是上游依赖出了问题。这种状况咱们也将它放进异样表中。

对于这两类漏网之鱼,须要反对单条 / 批量领取单弥补的经营能力以供人工染指;最好有一个在业务低峰期运行的兜底工作,扫描业务单据表,将一段时间内还未实现的订单进行弥补。

另外,兜底工作可能造成音讯的短暂沉积,影响线上的实时弥补流程推动,对此能够应用独立的队列隔离开来。

3.4 最终版

其实如果只是异步告诉类的操作呈现了异样,并没有必要每次都从新走一遍残缺业务流程,缺啥补啥即可。因而咱们将异样划分为多种类型,将一些异步操作和业务解决划分开来,进行精细化的解决:

  • 告诉上游的 MQ 失败,就将这个音讯体重发一次即可;
  • 告诉交易的回调 RPC 失败,可将 RPC 申请序列化到音讯体中,弥补时通过反序列化音讯体中的 RPC 申请,间接再发动一次 RPC 即可;
  • 对于更新 DB 失败,将更新参数序列化到音讯体中,补单时再次发动一次更新;
  • 如果在业务解决时遇到了异常情况,须要再走一遍业务弥补;

图 9 以这几种异样为例,阐明了每种弥补类型的音讯参数。

图 9. 分类弥补

咱们最终的补单体系见图 10,它既通过即时消息保障了弥补的及时性,是主动出击之茅;也利用延时音讯重试、落地失败音讯的异样表与兜底工作保障了弥补的有效性,是十拿九稳之盾。不仅可用于领取单据的弥补,通过保障流程的可重入性,也可作为一种通用解决方案,但不适用于无状态、不可重入的业务状态。

图 10. 异样弥补体系

  1. 总结

======

本文首先介绍了什么是补单,接着基于三方领取零碎的实现残缺论述了补单机制的演进过程,最终演变为一种绝对通用的异样解决模式,即基于音讯队列、无限状态机与多重工作兜底的业务层最终一致性保障机制,供大家参考斧正。

举荐浏览:

字节跳动总结的设计模式 PDF 火了,完整版凋谢分享

刷 Github 时发现了一本阿里大神的算法笔记!标星 70.5K

如果能听懂这个网约车实战,哪怕接私活你都能够月入 40K

为什么阿里巴巴的程序员成长速度这么快,看完他们的内部资料我懂了

程序员达到 50W 年薪所须要具备的常识体系。

对于【暴力递归算法】你所不晓得的思路

看完三件事❤️

如果你感觉这篇内容对你还蛮有帮忙,我想邀请你帮我三个小忙:

点赞,转发,有你们的『点赞和评论』,才是我发明的能源。

关注公众号『Java 斗帝』,不定期分享原创常识。

同时能够期待后续文章 ing????

正文完
 0