阿里云「飞天发布时刻」2024来啦!新产品、新特性、新能力、新方案,等你来探~ 了解详情
写点什么

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

  • 2020-12-09
  • 本文字数:4032 字

    阅读完需:约 13 分钟

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

引言:在日常生活中,从线下的超市购物到线上的外卖点餐、电商网购等,支付无时无刻不在发生,不论是通过现金、pos 机刷卡还是微信支付宝等第三方支付。线上支付有着及时便捷一气呵成的极致体验,当然也有少数的时候体验不够丝滑,比如早期我们在 PC 版 12306 买火车票,当支付完成后,订单的支付状态却经常不能及时更新,会有一段时间的延迟,有时甚至会等待很长时间处在未支付状态。


在支付的过程中由于各种各样的原因(比如外部渠道处理出了问题,异步回调迟迟不来)导致流程走了一半停了下来,用户看到订单依然是未支付状态,会不知所措,此时就需要一种机制来推动完成这笔交易。本文就以三方支付系统中的补单机制为例,来介绍一种较为通用的单据补偿模式。


三方支付系统简介


1.1 什么是三方支付


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


1.2 三方支付中的交易 &支付系统


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


支付是交易处理资金流的工具,目的是清偿债权和债务关系;支持多种支付方式(如银行卡支付、余额支付、优惠券组合支付、类似花呗的信用支付等),负责对接账务、会计、计费系统等资金处理能力,接收支付指令,驱动完成资金交换。将实际的支付行为(实际资金)与内部的记账(虚拟资金)相结合,保证虚实一致。


三方支付整体业务架构如图 1 所示,其中交易核心与支付核心在业务划分上处于"收单支付域",具备普通交易的收款、付款、退款及充值、转账与提现等常见功能,还包括了支撑电商业务的合单支付、担保与分账的能力。其中交易与支付核心都有一个异常查补模块,它囊括了所有业务的补偿流程,也是本文主要介绍的部分。



三方支付业务架构

2. 什么是补单 &为什么需要补单


一笔交易在支付过程中由于链路中的各种异常而中断,此时的交易处在一种中间状态,这种情况俗称"卡单",即卡在那里不动了,没有继续向下推进。还有一种情形,支付核心向渠道发起了扣款,渠道受理后,银行卡扣款成功,但由于种种原因没有向支付核心发起回调,导致这笔支付没有完成,用户没有享受到相应权益,但银行卡的钱已经扣了,这种情况称为“掉单”。


不管是卡单还是掉单,都是处在中间态的订单。补单就是将处在中间态的订单进行补偿,直到推进到终态(成功或失败)。补单一般有两个关键点,一个是补偿的有效性,极端情况下可能补偿多次都不成功,不能就此放弃了,需要有兜底的机制;另一个是补偿的及时性,因为交易挂起的时间越长,用户的体验越差。


交易核心和支付核心的补单相得益彰,具有一定相似程度的设计与实现,我们就以支付核心的补单为例介绍下异常补单机制。


3. 补单是如何实现的


本章首先了解一下业务流程,说明一下实现补单需要的前提基础,然后介绍一下补单机制的演进路线,每个版本存在的问题以及在下一个版本是如何解决的。


3.1 有限状态机与幂等性

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


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



余额提现流程


首先生成支付订单,然后请求账务系统,扣减用户账户下的余额,接着向外部渠道发起付款操作,资金操作完成后统一处理结果并更新单据信息,最后还有一些对上下游的异步通知,形式上包括消息和 RPC 回调。


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



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



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

可重入与幂等性保证


发起一次支付会涉及到多个系统间调用,由于网络原因导致的通信超时是常见的问题。同时上游系统也可能会重新发起请求,这需要我们的系统保证操作结果的幂等性。少数用户也可能会有多个终端同时操作带来的并发请求,需要我们保证接口的可重入性。除了服务自身,我们的下游依赖同样也需要保证其接口具有同样的能力。


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


  • 可重入:在并发请求下可以保证正确性。

  • 幂等性:重复多次相同的输入,获得相同的输出。幂等性在技术上其实也包含了可重入的要求。


具体到业务中,幂等性是针对一笔已经到达终态的支付而言的,对于初次未能拿到最终业务结果的请求,再次发起调用的结果可以是不同的(处理中->处理成功或失败)。那么我们如何保证业务流程的可重入与幂等性呢?我们分别拆解每一步来看:


  1. 生成支付单:首先支付单据可以将业务方传递的可保证唯一性的外部订单号作为唯一索引,插入数据库时若发生唯一索引冲突,则将查询已有数据进行幂等参数校验,若与当次请求的参数完全一致说明是重复请求,可使用 DB 中的支付单继续推进后续流程;若不一致则返回错误。

  2. 资金处理流程:账户和渠道系统各自保证其接口幂等性。我们也维护了每个下游操作的状态,根据状态机决定是否要继续推进,尽量不向下游输出重复流量。比如支付单已经完成了所有资金处理,状态机已经是终态,那么接口可以直接返回相应结果。

  3. 更新支付单信息,先将支付单加行级排他锁,再进行更新,保证多个并发请求只会有一个成功。

  4. 异步通知,在支付单推进到终态后进行。


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


3.2 初始版


一般来说最常见的补单形式是设置一个定时任务,定时去扫表完成业务补偿,实现比较简单,但是及时性不够,对于收款转账类的交易而言用户体验不佳。我们采用了通过消息队列进行即时补偿的方式,如图 4 所示。补偿消费者并没有去做补偿工作,而是解析消息然后通过 RPC 调用支付核心暴露的补偿接口。为什么不在消费者中直接补偿?这么做主要是考虑将逻辑收敛到一处便于维护。



余额提现发生异常时的补单流程


当然,补单可能依然失败,我们可以再次发送补偿消息。但不能一直这样循环下去,所以需要设置一个最大重试次数,超出后不再发送。当补偿多次都不成功时,一般是下游系统出了问题,这时我们需要放缓补偿的频次,随着重试次数增加,会让每次补偿时间间隔逐渐增大,通过 RocketMQ 的延时消息实现。


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


  1. 如果异常消息发送失败,上游也没有重试机制,这笔订单就可能一直挂在这里不了了之了,如图 5 所示。

  2. 补偿消费者请求支付核心补单时不成功,可能是超时但实际补偿成功了,或者是请求压根没有过去,如图 6 所示。

  3. 如果重试达到最大次数依然没有成功,这笔单子该怎么处理。



补偿消息发送失败



补偿消息消费失败

3.3 改进版


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


针对消息生产/消费异常的改进版 - 1


对于第 2 个问题,如果补偿消费者调用支付核心失败,补偿消费者 HandleMessage 会向上层抛出 error,利用 RocketMQ 的梯度重试机制,当消费重试次数达到一定上限后会进入死信队列。如图 8 所示,这种情况一般是服务或网络出了问题,待恢复之后,可以从死信队列拉取这些消息再统一处理。



针对消息生产/消费异常的改进版 - 2


当然还有更极端的情况,请求 MQ 和 DB 都失败了咋办?以目前 MQ 和 DB 的可用性来说,同时失败这种基本可以不用考虑,报警人工介入即可。


针对问题 3,如果重试超过最大次数,依然补偿不成功,一般是下游依赖出了问题。这种情况我们也将它放进异常表中。


对于这两类漏网之鱼,需要支持单条/批量支付单补偿的运营能力以供人工介入;最好有一个在业务低峰期运行的兜底任务,扫描业务单据表,将一段时间内还未完成的订单进行补偿。


另外,兜底任务可能造成消息的短暂堆积,影响线上的实时补偿流程推进,对此可以使用独立的队列隔离开来。


3.4 最终版


其实如果只是异步通知类的操作出现了异常,并没有必要每次都重新走一遍完整业务流程,缺啥补啥即可。因此我们将异常划分为多种类型,将一些异步操作和业务处理划分开来,进行精细化的处理:


  • 通知下游的 MQ 失败,就将这个消息体重发一次即可;

  • 通知交易的回调 RPC 失败,可将 RPC 请求序列化到消息体中,补偿时通过反序列化消息体中的 RPC 请求,直接再发起一次 RPC 即可;

  • 对于更新 DB 失败,将更新参数序列化到消息体中,补单时再次发起一次更新;

  • 如果在业务处理时遇到了异常情况,需要再走一遍业务补偿;


下图以这几种异常为例,说明了每种补偿类型的消息参数。



分类补偿


我们最终的补单体系见图 10,它既通过即时消息保证了补偿的及时性,是主动出击之茅;也利用延时消息重试、落地失败消息的异常表与兜底任务保证了补偿的有效性,是万无一失之盾。不仅可用于支付单据的补偿,通过保证流程的可重入性,也可作为一种通用解决方案,但不适用于无状态、不可重入的业务形态。



异常补偿体系

4. 总结


本文首先介绍了什么是补单,接着基于三方支付系统的实现完整阐述了补单机制的演进过程,最终演化为一种相对通用的异常处理模式,即基于消息队列、有限状态机与多重任务兜底的业务层最终一致性保障机制,供大家参考指正。


本文转载自: [字节跳动技术团队](ID:toutiaotechblog)


原文链接:基于有限状态机与消息队列的三方支付系统补单实践


2020-12-09 07:058438

评论 2 条评论

发布
用户头像
你好,你们用的是维金系统吗,名次概念上有共性点
2021-01-28 18:15
回复
用户头像
分布式事务的问题,无论做到什么程度,都必须要人工干预来兜底
2020-12-09 10:37
回复
没有更多了
发现更多内容

一个独立开发者,他是如何做到月入 20 万的?

非著名程序员

程序员 独立开发者 副业赚钱 开发者 程序人生

申请鲲鹏920测试机试水+编译nginx

草宝虫

nginx 鲲鹏920 centos7 armv8

凡事必先骑上虎背

Steve

学习 态度 方法论

微服务架构深度解析与最佳实践 - 第六部分

kimmking

微服务 最佳实践 深度解析 高可用

亚马逊云 AWS LightSail 搭建高性能 LNMP 环境并安全部署 Wordpress

蛋仔小蚂蚁

nginx Wordpress 部署 SELinux 安全上下文配置 亚马逊云 AWS Lightsail 安全

黄金思维圈,养成透过现象看本质的能力

非著名程序员

读书笔记 程序员 程序人生 提升认知

两边夹的应用二

孙苏勇

算法 两边夹 重排序 函数式接口 Lambda

【译】Rust 开发者的2019

WasmEdge

程序员 rust

一个运营经理人的工作两周年总结

霍太稳@极客邦科技

高效工作 身心健康 项目管理 自我管理

微服务架构深度解析与最佳实践 - 第七部分

kimmking

微服务 最佳实践 深度解析 高可用

两边夹的应用

孙苏勇

算法 积水问题 两边夹

微服务架构深度解析与最佳实践 - 第三部分

kimmking

微服务 最佳实践 深度解析 高可用

数据分析师应该了解的数据湖

数据社

大数据 数据仓库 数据湖 数据分析

微服务架构深度解析与最佳实践(全篇汇总)

kimmking

微服务 最佳实践 深度解析 高可用

归去来兮:递归

曲镇

算法

微服务架构深度解析与最佳实践-第一部分

kimmking

微服务 最佳实践 深度解析 高可用

聊聊:Python

谢烟客

Python 人工智能 编程

平均响应1000ms到200ms,PHP和Go那家强?

拖地先生

php 架构 性能优化 后台开发 运维

2019 年

贾献华

2020 2019 总结 日历 计划

一文讲清楚 MySQL 事务隔离级别和实现原理,开发人员必备知识点

古时的风筝

MySQL 数据库 事务隔离级别 mysql事务 数据库事务

微服务架构深度解析与最佳实践 - 第四部分

kimmking

微服务 最佳实践 深度解析 高可用

浅谈数据中台

数据社

大数据 数据中台 数据仓库

最近看了两本书:The Rules of Life 和 Make Big Happen

霍太稳@极客邦科技

创业 团队管理 自我管理

程序员职业生涯的八点感想

池建强

程序员 职业

阿里笔记之数据模型

迹_Jason

大数据

小程序的当下和未来可能 | GMTC.2019深圳站演讲文稿

崔红保

小程序 uni-app

你不是迷茫,只是缺乏目标

Steve

学习 身心健康 方法 自我管理

求稳不得

孙苏勇

职业 发展 职场

微服务架构深度解析与最佳实践-第二部分

kimmking

微服务 最佳实践 深度解析 高可用

微服务架构深度解析与最佳实践 - 第五部分

kimmking

微服务 最佳实践 深度解析 高可用

越是困难,越是要做有分析判断能力的人

霍太稳@极客邦科技

创业 团队管理 个人成长

基于有限状态机与消息队列的三方支付系统补单实践_架构_字节跳动技术团队_InfoQ精选文章