支付系统设计入门:核心账户体系架构

👉目录

1 账户记账理论

2 账户设计

3 账户性能问题

4 账户核心架构

5 小结

第三方支付作为中立的第三方,截断了用户和商户的资金流,资金先从用户账户转移到第三方支付平台账户,得到双方确认后再从支付平台账户转移到商户账户。

支付平台为客户提供了资金流转以及结算等服务,必须建立自己独立的账户体系,以此来保证每个客户资金的准确性以及资金变动的可追溯性,这套账户体系在支付平台中成为"核心",整体类似于银行的账户核心,但会比银行账户核心简单一些。

关注腾讯云开发者,一手技术干货提前解锁👇

鹅厂程序员面对面新一季直播继续,每周将邀请鹅厂明星技术大咖深入讲解技术话题,更有精美周边等你来拿,记得提前预约直播~👇

01

账户记账理论

1.1 会计恒等式

资产=负债+所有者权益

举个可能不是特别恰当的例子,比如腾讯滨海大厦,在等式的左边他体现为公司的资产,在等式的右边,负债可以表现公司为了兴建大厦从银行的贷款,所有者权益可以表现为公司股东的投入。等式右边,负债可以理解为外债,所有者权益可以理解为内债(欠股东的钱)。这里都是针对腾讯公司的法人而言,资产,负债,所有者权益都是这个法人的权利和义务。

另外,也可以理解为等式的左边资产是可用的资源,是公司的权利,而等式的右边是为了解释左边而存在,用来说明资产的来源,是公司的义务。

1.2 复式记账法

上面的会计恒等式中,除了强调相等,还强调左右,任何一笔交易,都必须保证这个等式成立,所以要么两边同时加或同时减(比如左边资产增加,右边负债也增加),要么等式单边内部平衡(比如等式左边,有资产增加,对应就有资产减少)。所以所谓的"复式记账法"简单理解就是一笔交易写两遍,使得左边和右边互相匹配,这样做的好处很明显,容易核对不容易出错。在会计中把这种复式记账法成为"借贷记账法",强调"有借必有贷,借贷必相等"。

go 复制代码
"借贷"的理解两个字意思都有歧义,不要尝试从字面意义去理解,在会计中借贷只是两个记账符号,表示两个相反的方向,不绝对代表数量的增减,所有的账户都是左借右贷,资产类账户增加是借,减少是贷,负债类账户刚好相反,增加是贷,减少是借。

上面这些概念对于没有会计基础的人来说,有时会比较难以理解,对于程序员来说,只需要知道一笔数至少记两次,至少会在两个相关的账户中记录金额相等,借贷相反的记录,两个账户的类型可以是相同的(比如都是负债类账户),也可以是不同的。

02

账户设计

对第三方支付平台而言,也同样存在资产类账户和负债类账户:

  • 资产类账户:在上一篇文章中提到,交易过程中资金会被截断,用户支付时,是先从用户银行账户入账到了支付平台的账户,而没有直接到商户的银行账户,支付平台账户其实就是财付通开在央行的备付金账户(备付金的具体解析可以参考另外一篇文章)。这个是财付通的资产,对央行来说这笔钱是财付通的,所以这个是资产类账户。

  • 负债类账户:当这笔钱入账到了财付通备付金账户后,财付通内部必须要知道这笔钱真正要归属到那个商户(央行备付金只是一个所有资金的总额,不会记录具体某一笔钱归属于谁),这就对应到前面提到的为了保障每个用户的资金清晰明了和可追溯,财付通内部建立了一套账户体系。这种账户对财付通来说是负债,因为这些钱是别人的,最终要转移到其他主体,所以财付通内部账户是负债类账户。

2.1 内部账户体系

内部账户在用户看来就是微信支付里面的零钱,当然这个只是整体账户体系的一部分。

按照属性分类,可以将内部账户分成以下几种:

  • **个人账户,**一个人一个账户,这个被叫做是C账户,是现金账户,个人可以完全支配。

  • **商户账户,**商户账户因为需要结算等,所以一个商户有两个账户,一个B账户(中介账户,理解为待结算账户),一个C账户(现金账户)。当用户支付时,实际支付到了商户的B账户,这个账户里面的资金商户不能自由支配,待平台结算后(扣除手续费等),资金被转移到C账户,这个账户商户是可以自由支配的,比如可以提现到商户自己的银行卡。

  • **银行账户,**第三方支付公司为各个银行设置的账户,这个账户是一个总账账户,记录与银行之间的资金变动,一般不记录余额,而只是记录流水,方便跟各个银行进行对账。

总体的关系如下:

其实在内部账户中,除了用户和商户账户外,还有支付平台自身的账户,当 B 账户进行结算时,一部分资金进入商户自己的 C 账户,另外手续费等进入支付平台的账户,另外,上图中省略了银行账户。

2.2 账户内容

账户体系中有三个主要内容,账户余额,交易凭证以及交易流水:

账户余额就是账户中的资金数目,任何余额的变动都需要记录流水,而凭证是用来记录交易过程的信息。

凭证单理论上可以放到业务层,不放在账户核心层,这样账户层就只有余额和流水。三者主要内容包括:

余额

账户余额记录用户的资金数目,当发生交易时,会对余额进行 update 操作,增加或减少资金的数据。一般包括:

  • 账户 ID,这个 ID 有的平台会有编码规则,比如某些位用来区分个人或者商户,区分币种以及校验位等。

  • 币种。

  • 余额。

  • 冻结余额,在途交易,还不能确定是否要扣减余额,就可以通过冻结余额来反应。

  • 账户状态,比如是正常态、支付或者冻结等。

流水

当余额发生变更时,需要记录流水,以此来跟踪余额的变化,一般包括如下内容:

  • 流水 ID。

  • 凭证 ID,就是下面凭证中的 ID。

  • 发生金额。

  • 借贷方向。

  • 对方账户 ID。

凭证

凭证用来记录交易过程中的信息,是用户交易的依据。凭证对应到支付平台内部的各种单类,比如充值单,体现单,交易单等等。一般包括:

  • 凭证 ID。

  • 交易参与方,可能是两方,也可能是多方。

  • 交易金额、交易类型。

  • 交易状态,比如支付中,支付成功,转退款等。

  • 交易渠道。

03

账户性能问题

账户性能问题由以下两方面引入:

  1. 复式记账法,一笔交易需要在两个账户中进行记录,在海量支付系统中,两个账户大概率上不在同一台 DB 上,甚至也极有可能不在同一 IDC,这就需要引入分布式事务,分布式事务本质上是进程间的同步调用,性能会大幅下降,同时引入了较高的复杂性,比如需要考虑事务一半成功一半失败时的补偿机制或者回滚机制。所以复式记账法带来的分布式事务导致可靠性以及性能都会受到影响;

  2. 热点账户,某些账户的交易十分频繁(比如京东账户,拼多多账户),而每笔交易都会对应到余额字段的 update 操作,更新时需要对账户进行加锁操作,频繁加锁释锁会对 DB 造成极大的性能压力,可能会超过 DB 的能承载的极限。压测显示 MySQL 单条记录 update 最大性能500次/s(after_commit 模式)。

解决这些问题有以下一些方案:

  • 先借后贷。

  • 余额更新简化。

  • 合并入账。

  • 多账户。

3.1 先借后贷

通常情况下,复式记账的借贷需要在同一个事务里面完成,但分布式事务引入了性能和可靠性问题,所以可以将一个分布式事务拆分成两个本地事务,即"先借后贷",一个是"借"的实时事务,加一个"贷"的异步事务。当完成实时事务后就可以对外返回成功,异步事务后续进行,这样简化了事务复杂性,当如也会引入其他的问题,后面会讲到。

举两个例子来说明整个过程,分别是快捷支付和余额支付:

  • 快捷支付:用户银行账户支付给商户。借贷账户分别是财付通备付金账户(资产类账户)以及商户的内部 B 账户(负债类账户),T 型账如下图所示:

简化的交互序列如下所示:

  • 余额支付:用户的财付通零钱账户支付给商户。借贷账户都是财付通内部账户,分别是用户的内部 C 账户以及商户的内部B账户,T 型账如下图所示:

简化的交互序列如下图所示:(这个图可能有点问题,其实是核心发出的异步消息和处理)。

可以看到,原则是:

  • **先:**增加资产,或减少负债。

  • **后:**减少资产,或增加负债。

引入的问题

所有将分布式事务拆分成本地事务后引入的问题都是类似的,在事务理论中的"ACID"中,拆分后牺牲了C(一致性)强一致性,但每个本地事物是一致的,另外也缺乏"I"(isolation)隔离型,缺乏隔离型会导致比如"脏读"的异常,即其他事务读到了尚未完全完成事务的更新,这个也很好理解,比如用户本该加100块,但由于异步导致这100块没有马上加上。这个看起来是个大问题,但具体分析:

  • 业务层面而言,这个是可以接受的,因为实行"先借后贷",所以商户或者用户的可用额度只会小于实际的可用额度,不会出现短款,整体的风险是可控的。另外,从用户的角度来说,短暂的余额不一致也是可以接受的,因为用户可能根本感知不到。

  • 从**"事务回退"**的角度来看,对支付平台来说,资产增加和负债减少都是可以回退的,因为资产在支付机构账上,即使做错账了,可以进行退款等逆向操作。但如果先减少平台资产,比如先给客户打款了,这个是不可回退的,因为客户可能马上就把钱给用完了。"先借后贷"规避了拆分成本地事物的风险。

虽然风险可控,但还是一定要考虑好程序异常时对业务的回滚、重试以及对账机制,保障整个过程结果的最终一致性。

3.2 余额更新简化

特定应用场景下,可以不记余额,这其实违背了记账原则,但在某些场景下,可以通过其他手段来补偿和保障,典型的应用比如 C2B2C 交易,中间的 B 账户可以不记余额,它的存在只是为了作为中间交易的对手方。

3.3 合并入账

合并入账,就是先将余额更新操作 hold 住,暂存入待入账队列(可以使用 DB 或者 KV),累积到一定数量或者到一定时间时,将发生金额进行汇总合并,更新一次余额,从而大幅提升入账效率。合并入账借贷先后原则和前面类似,为了防范风险,只对入账(负债类账户的"贷")进行合并,出账仍然需要实时完成。

3.4 多账户

先借后贷以及合并入账只能解决入账时的热点问题,而出账都是需要实时更新(为避免透支风险)。为了解决出账热点,需要引入多账户体系,即通过新建多个账户,把业务均摊到多个账户上,从而解决热点问题。

多账户不仅可以解决出账热点问题,对入账热点问题也有效,本质上多账户就是把一条 DB 记录的更新均分到了多个 DB 记录上。

按照账户的承载功能不同,多账户方案可以分成两种:

  • 功能分离型多账户,出入帐功能分离,有的子账户承载入帐,有的子账户承载出账,有的承载两者。

  • 功能完整性多账户,所有的分账户都同时承载出账和入帐。

功能分散型多账户

功能分离型有两个出发点,一是业务特性,出入帐性能压力不一样;二是风险和管理上的考虑,一个完整账户比多个完整账户的风险和管理难度要小很多。功能分散型子账户有以下几类:

  • 入账子账户,只承载入帐请求。

  • 出账子账户,只承载出账请求。

  • 完整子账户,可以同时承载出入帐,这个账户也可以认为是一个主账户。

出账和入账的账户个数根据业务特性设置。

入账子账户完成入账操作后,定时将资金向上归集到主账户,之后主账户将资金调拨到出账子账户,供出账操作,某些情形下。根据业务特性,资金调拨有一下一些场景:

  • 一定周期内只有一笔出账,则不需要往下调拨了。直接由主账户完成出金。

  • 针对 2B 业务,特性是低频大额,此时可以将资金绝大部分留在主账户上,由主账户完成大额出金操作。

  • 针对 2C 业务,特性是高频小额,此时可以将资金尽量分散到多个出账子账户,由出账子账户完成出金操作。

功能完整型多账户

每个子账户都会同时承载入账业务和出账业务,子账户之间时并列的关系:

  • 入账时,应该尽量均衡的选择子账户。

  • 出账时,应该选择余额大于出账金额的账户,如果没有,则选择余额最大的子账户,目的是减少资金调拨的次数。

多账户设计要点

  • 需要保存用户和多个账户的逻辑对应关系,这个对应关系可以放在业务层,也可以放在核心账户层。

  • 子账户的部署模式可以是同 DB 分布,同 IDC 多 DB 分布,或者跨 IDC 分布,实现难度依次增加,需要根据性能要求以及容灾要求选择合适的部署方式。

  • 分账户后资金调拨是个难点,需要仔细设计,尽量避免资金的来回调拨。

  • 如果是跨 DB 或者跨 IDC 部署时,资金总额的查询会很麻烦,可以考虑采用 CQRS(命令查询指责分离)的设计模式,设置一个集中化的查询副本。

04

账户核心架构

4.1 处理逻辑

财付通记账核心的事务处理经历过几个阶段:

  • **本地事务:**早期用户量和业务压力都不大,几台 DB 机器就完全能够处理,所以本地事务即能满足要求;

  • **分布式事务:**随着业务发展,本地事务已经无法满足需求,所以迭代到了分布式事务处理,采用两阶段提交来实现分布式事务,这个是财付通历史上比较重要的一个里程碑;

  • **柔性事务:**微信支付的兴起,对性能以及可靠性的要求都提高了很多。而分布式事务提供的 ACID 保证本质上是多台 DB 的同步调用,是以损害可用性、性能为代价的。因为只有在参与事务的各方都能够正常工作,分布式事务才能够顺利进行,只要有一个工作不正常,整个事务就不能完成。而BASE柔性事务的思想就是将分布式事务分割成多个本地事务,同时通过一定机制串接以达到最终一致性。BASE 理论最早是 eBay 工程师在2008发表的论文中提出的,可以参考《Base: An Acid Alternative》https://queue.acm.org/detail.cfm?id=1394128

目前财付通的记账核心大部分场景是基于柔性事务,少部分是基于分布式事务。事务拆分的依据就是上面提到的"先借后贷"。

下面简单介绍下基本的账务处理流程,以 C2B 支付为例,即个人支付给商户的场景,事务处理如下所示,可以分成三部分,买家账户处理,凭证处理,以及卖家处理:

sql 复制代码
begin
    update 买家余额,余额减少    insert 买家流水        insert 支付凭证交易单        update 卖家余额,余额增加    insert 卖家流水
 commit

柔性事务中事务分成两个子事务,买家事务+凭证合并为第一个本地事务,卖家事务作为第二个事务,基本的处理逻辑如下:

  1. 远程日志:当有一笔支付请求发送到事务管理器 后,事务管理器首先通过本地的远程日志代理 写远程日志,日记代理将日志发往异地的远程日志服务 ,远程日志服务将日志记录到日志文件里面持久化,同时会将事务信息记录到黑名单 kv 中。黑名单校验模块将会校核黑名单 kv,对于已经同步到异地备 DB 中的用户将会消除;

  2. 买家本地事务:远程日志记录完成后,事务管理器 发起第一个事务,调用资源管理器 ,资源管理器将与账务 DB 交互,减买家余额,记录流水,并且记录交易单;完成第一个事务后,事务管理器将发送异步消息给异步入账服务

  3. 卖家本地事务:异步入账服务 收到异步消息后,将发起第二个事务,调用资源管理器,资源管理器与账务 DB 交互,加卖家余额,记录流水。

05

小结

本文介绍了账户记账原理、内部账户基本组成、性能方面的考虑、财付通账户的基本架构以及处理逻辑。核心账户体系比较复杂,关联的内容也有很多,理解有所偏差的地方,敬请指正。

-End-

原创作者|邱凯福

感谢你读到这里,不如关注一下?👇

📢📢Google Pay是怎么设计它的支付钱包系统的?点击下方立刻探索👇

你觉得账户体系中哪一块是最重要的?欢迎评论留言补充。我们将选取1则优质的评论,送出腾讯云定制文件袋套装1个(见下图)。4月16日中午12点开奖。

相关推荐
才思喷涌的小书虫9 分钟前
学术分享:基于 ARCADE 数据集评估 Grounding DINO、YOLO 和 DINO 在血管狭窄检测中的效果
人工智能·yolo·目标检测·计算机视觉·ai·语言模型·视觉检测
liruiqiang0511 分钟前
循环神经网络 - LSTM 网络的各种变体
人工智能·rnn·深度学习·神经网络·机器学习·lstm
XT462514 分钟前
AI 之 LLM(大语言模型)是如何生成文本的!
人工智能·语言模型·自然语言处理
zslefour22 分钟前
解决2080Ti使用节点ComfyUI-PuLID-Flux-Enhanced中遇到的问题
人工智能·comfyui
杰克逊的日记24 分钟前
es的告警信息
大数据·elasticsearch·搜索引擎
L_cl1 小时前
【NLP 58、利用trl框架训练LLM】
人工智能·深度学习·自然语言处理
uncle_ll1 小时前
李宏毅NLP-2-语音识别part1
人工智能·自然语言处理·nlp·语音识别·李宏毅
DWQY1 小时前
pytorch查询字典、列表维度
人工智能·pytorch·python
_一条咸鱼_1 小时前
揭秘 AI 副业:开启财富自由新通道
人工智能