作者:博文视点Broadview
链接:https://www.zhihu.com/question/27606493/answer/2618912929
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

要厘清支付机构的账务系统,首先要梳理清楚支付机构的账户体系。支付机构的账户按照服务的对象可以分为B端账户和C端账户,B端账户是针对支付机构服务的商户开设的账户,C端账户是针对C端消费者开设的账户。B端账户的作用主要是在给商户结算资金的时候根据账户余额判断结算给商户的余额有多少。C端账户是针对储值、预付卡等应用场景开设的账户,用来存储C端消费者在该支付机构体系下可以支配的资金。根据是否需要结算把账户分为内部户、外部户,内部户是支付机构内部用于资金过渡的账户,外部户是开设给B端商户的资金账户。另外,支付机构在央行需要开设备付金账户,所有的资金都要进入备付金账户,结算的时候也是通过备付金账户进行实际资金的划拨。这样能防止支付机构“跑路”,给消费者和商户造成不必要的损失。
账户体系梳理清楚之后,就可以设计账务系统了。账户体系中除了备付金账户,其他的账户都需要由账务系统来管理。

账务架构

账务系统最核心的功能是管理账户余额,账户余额的变更是根据业务来确定的,这里的账务记账与会计是分开的,只是针对账户的管理,以及商户账户资金的管理。账务系统的架构如下图所示。
image.png
账务系统的架构
1. API接口层
账务系统是支付体系里底层的系统,不会有支付以外的系统和账务系统进行交互,账务系统提供的接口也是给内部支付业务系统使用的,主要给支付核心、结算系统、风控系统等提供API接口,针对支付核心提供支付、退款等接口,针对结算系统提供结算接口,针对风控系统提供冻结、解冻等接口,通过这些上游系统一起对账户进行操作,实现记账、结算资金等功能。
2. 业务处理层
要对商户的资金做好管理,首先要管理好商户的账户,商户入驻时支付机构需要给商户开通账户,在账务系统中就要创建对应的账户,商户停止使用支付机构的支付功能时,需要把账户设置为不可用,即对账户的状态要具备管理的能力。账户状态通常有初始化、使用中、已停用、冻结等状态。账户的信息也要支持上游查询。
商户对应的账户是用来记账的,记账分为入账和出账,商户售卖一个商品,收到一笔支付单,账户里就需要有一笔入账,消费者做了退款操作,账户里就需要有一笔出账,账务系统需要定义好入账和出账的逻辑。支付机构需要有反洗钱、反欺诈系统,能够及时检测到商户及C端消费者交易的异常,如果商户存在电信欺诈、洗钱等风险,则需要及时冻结商户余额,避免给社会造成损失,所以账务系统提供了冻结与解冻账户的功能。
商户的资金余额是怎么来的呢?这要有对应的明细,可以在支付核心查询到支付、退款单相关的明细,但是手续费的收取明细需要在账务侧来查看,所以账务系统要提供针对账户操作的明细订单,供商户用来对账。
3. 会计处理层
一般三方支付机构会单独搭建会计系统来管理公司的会计科目、借贷关系、会计分录等内容,账务系统做好记账之后会把信息透传给会计系统,会计系统需要提前配置好会计科目,然后根据借贷关系进行登账。

热点账户处理

  1. 什么是特点账户
    支付、退款等操作都会改变商户的余额账户,针对账户资金的操作都需要加锁、解锁,以保证资金的安全。例如,公司A在支付机构开设账户,账户的存储情况如下表所示。
账户 账户类型 商户名 余额(元)
66666** 余额账户 公司A 1000

假设消费者产生一笔100元的退款,这个时候数据库中的数据如下表所示。

账户 账户类型 商户名 余额(元)
66666** 余额账户 公司A 900

在这个过程中数据库都做了哪些操作呢?首先,数据库需要对这条数据中的余额字段做一次更新操作,减去退款的100元。做更新操作的时候为了避免并发问题,在更新数据之前,需要对这条数据进行加锁(Lock),锁定数据后,对余额进行更新,然后释放锁(Unlock)。
那么问题来了,假设数据库对数据进行一次更新操作需要耗费5ms的时间,在业务量小的情况下没有问题,如果商户的交易非常频繁,就会出现频繁地进行资金进出的操作,针对商户对应的账户频繁地出现加锁与解锁,如果商户做活动,QPS达到万级,那么对数据库来说,基本无法承受,这就产生了热点账户的问题。
2. 如何解决热点账户问题
解决热点账户问题可以从两个途径考虑,一是通过业务的手段,二是通过技术的手段。业务的手段通常有两种方式可以作为参考。
(1)汇总入账
当天的交易可以先落入数据库,不做入账出账处理,在T+1日的时候把T日所有的交易做好汇总轧差,然后统一入账,这样其实每天对账户操作一次即可,避免了热点账户问题。汇总入账的处理流程如下图所示。
image.png
汇总入账的处理流程
汇总入账的优点是大大减轻了数据库的压力,日间所有交易先存储在数据库中,等到T+1日计算好入账资金后,一次性入账,数据库几乎毫无压力。这么做存在的业务弊端是商户账户资金没有做到实时入账,对商户有一定的对账成本,并且会产生疑问,为什么我售卖了商品但收入余额没有变化,需要到第二天才能看到余额的变化?
(2)缓冲记账
缓冲记账的处理逻辑是收到记账请求后会先返回受理结果,再处理实际业务逻辑,有点类似NIO的思维,将实时同步的记账行为进行异步化,从而达到记账实时性和系统稳定性平衡的目的,在一定程度上解决了汇总入账时效差的问题。账务系统收到入账请求后,会先入库,不做任何业务处理,然后直接返回上游受理成功,再使用专门的线程来处理入库后的数据并进行入账操作。缓冲记账的流程如下图所示。
image.png
缓冲记账的流程
上游发起记账,账务系统会接收记账请求,然后把记账请求落入数据库或者缓存,并返回给上游记账成功,这个流程就结束了。后续会有缓冲记账的线程从缓存中拉取数据,根据数据库的承受能力来设定拉取数据的处理速度,然后将数据逐一入账。缓冲记账默认也是实时入账的,只不过能接受一定时间的延迟,比如告知商户延迟是10分钟,就是用时效性换取峰值时的稳定性。但不是所有场景都适用缓冲记账,业务量明显过大时,缓冲记账会带来业务问题。假如账务实际处理能力与业务量不在一个量级上,那么缓冲记账的延迟会给业务造成一定的困扰,比如用户会奇怪明明退款成功了,但并没有收到钱。另外,异步处理有一个前提条件是账户状态正常,如果没有这个前提条件,那么后续的处理都会有问题,并且针对出账的场景要保证账户的资金充足。
技术手段解决账户热点问题通常是采用拆分子账户的方式,将原本一个账户拆分为多个子账户,能够倍数级地降低QPS,比如原本一个账户要承受的QPS是100,即每秒处理100个请求,把这个账户拆分成10个账户后,每个账户需要承受的QPS就降低到了10。账户拆分前后对比如下图所示。
image.png
账户拆分前后对比
拆分子账户就是创建与热点账户对应的多个子账户,子账户与账户的数据结构相同,将账户的余额分散至各个子账户。当账务系统收到账务请求的时候,通过Hash分配(根据具体情况来定义具体的Hash函数)选择子账户进行记账,这样就将原来对一个账户的请求分散到多个子账户中,分散了账务热点。拆分子账户虽然缓解了热点账户的问题,但需要额外处理一些拆分后的逻辑。

  • 扣款入账实际操作的是子账户(就是一个单独的账户,只是保存了与原本账户之间的关系),主账户中不存放余额,余额存放在子账户中,所以查询余额时,需要做特殊处理,即求和子账户的余额。
  • 多子账户扣款时会存在子账户余额不足的问题,但所有子账户的余额总和可能是充足的,所以需要在余额不足时做子账户资金的归集。而且若为代付代发的场景,那么对商户账户进行充值时,需要做充值资金拆分,将资金加到每个子账户中。

在实际场景中,我们处理的热点账户问题通常并非为单个热点账户。例如,账户基数为1000万个,其中高并发账户有1万~5万个,在正常入账时,这些热点账户在数据库上产生的锁及占用的数据库连接都是共享资源,会相互影响,以至于很难提升性能。在这种情况下,我们可以从几个方面对数据库的性能进行优化,首先要控制并发数,所有入账和出账操作都进入线程池,以线程池的并发数控制对热点账户操作的并发数。其次要缩小事务范围,对热点账户的操作尽量控制在小的事务范围内,减少时间分片,提高成功率。但这些还解决不了根本问题,要想真正缓解数据库整体的压力,还需要分库分表,这部分内容在支付核心一章已经介绍过,这里不再赘述。
以上内容摘自《支付架构实战》一书,这本书从支付业务入手,剖析了支付业务架构,梳理了支付业务逻辑,实现了支付架构,构建了支付架构体系,深入浅出,干货满满