1、第三方代收款系统功能设计
第三方代收款系统按需求可以划分为 5 个子系统,系统功能结构如图1所示。本系统总体分为入金子系统、出金子系统、公共子系统、对账子系统、运营子系统。资金处理有入有出,但流程完全不同,因此分出入金和出金两个子系统。
而两个子系统的流程有公共部分处理,如账务处理、流水号生成等,因此独立出公共子系统。对账与交易无关,独立成子系统便于解耦和维护。运营管理则负责数据展示,独立成子系统便于统一数据管理。
入金子系统包括入金产品模块、交易中心模块、支付工具模块;出金子系统包括出金产品模块、结算中心模块、委托付款模块;公共子系统包括银行网关模块、账务中心模块、流水号服务模块;对账子系统包括银行对账模块、商户账单模块;运营子系统包括交易管理模块、银行对账模块、商户账单模块。
图1 系统功能结构
2、第三方代收款系统逻辑架构
采用面向对象的分析与设计方法,建立对应的系统逻辑结构,系统的逻辑结构如图2所示,带“*”部分表示本文涉及到的内容,分别是“入金产品(401)”、“出金产品(402)”、“交易中心(300)”、“支付工具(200)”、“结算中心(210)”、“委托付款(211)”、“账务中心(100)”、“银行网关(620)”、“流水号服务(630)”、“银行对账(131)”、“商户对账(132)”、“运营管理(510)”。
支付机构对接众多银行,各银行的接口和通信方式不同,抽象出银行模块,如编号 621、622 等。多个模块会调用银行请求金融数据交换,如果第三方代收款根据银行提供不同的接口,会使得内部系统耦合度增加,增加或减少某一个银行带来大面积的升级维护,抽象出银行网关层,同一业务采用统一接口,实现系统解耦和简化维护工作难度。
引入账务中心,在资金交易后对账户进行记账,而记账的账户余额非增即减,抽象出支付工具模块,对入金和出金操作进行封装。交易中心封装具体的业务处理,比如代收、快捷支付等支付业务,每一类业务都对应一套流程,如第三方代收款会请求风控、会员、计费、路由等服务,而快捷支付绑卡业务不会请求计费服务。
入金(出金)产品模块,是在交易中心之上,面向具体业务场景的实现,如商户模式代收和市场模式代收,均属于第三方代收款,不同的是,在代收之外,市场模式还有对市场订单的处理,这也是交易中心成为高度复用组件的关键所在。通过采用面向对象的分析方法分析支付业务,完成交易流程主干的设计。
在主干的基础之上,采用微服务架构的设计思想,从入金(出金)产品模块剥离出缓存服务;从交易中心剥离出风控服务、路由服务、计费服务、会员服务、协议服务等公共服务;从银行网关剥离出流水号服务;从账务中心剥离出会计中心等。形成多个粒度细小、功能单一的服务群,各模块维护自己的数据库,限制访问权限,通过 API 接口提供数据操作,形成微服务架构。
系统采用微服务架构,服务之间通过 RPC 的方式通信,一般各服务采用的数据库不是一个实例或品牌,无法采用传统的数据库事务来保证调用前后的数据一致性,调用过程中发生的重复调用、请求响应超时、服务端数据库操作异常等问题将会十分常见,因此子系统设计、模块设计遵循以下原则:
(1)主子单:微服务架构的特点是应用模块多、通信链路长,在网络不稳定时就会产生超时现象,此时如果被调系统是无状态系统,主调系统就无法通过“同步状态”的方式来使数据达到最终一致性,造成“人为”的异常失败交易,因为对于外部的商户请求而言,不关心支付系统内部如何设计,只关心交易是否能够正常处理。
因此,在构建微服务系统时,除了查询功能的模块以外,涉及交易状态处理的模块都会有接收请求时的“主单”记录,并在处理完毕后更新记录和返回记录结果;
当一个模块分别与很多模块交互时,采用“子单”记录各请求的状态,为方便查询该模块每笔交易的详情,尤其在排查问题时作用突出,一是能够确认请求的数据是否有误,二是能够作为“数据同步”的依据,三是模块多造成数据表多,而数据表都要用可视化界面展示,当一笔交易出现问题后,如果凭着交易流水号找遍所有菜单,耗费的人力成本将是巨大的,采用子单的方式能够直观地、快速地展现结果。
图2 系统逻辑结构
(2)幂等性:系统是以交易请求为驱动的,从整体上分为两部分,一是支付系统与商户请求之间的幂等性,称为“外部幂等性”;二是支付系统内部各模块之间的幂等性,称为“内部幂等性”。外部幂等性是由提供给商户的外部接口约定,每笔交易上送唯一流水号,如果支付系统重复受理请求,只处理一次,通过数据库唯一约束控制。
内部幂等性是基于模块的主子单设计实现的,主调模块子单的数据库主键作为被调模块主单的唯一约束,也是利用数据库底层的约束进行控制,如果出现重复请求,将数据库中记录的状态返回给调用方。
(3)数据一致性:对于一笔商户交易,在支付系统各模块的状态应当始终保持一致,微服务中的模块之间无法通过传统的数据库事务进行强一致性控制,而是允许某段时间内的状态不一致,通过“状态同步”机制保障数据的最终一致性。
这是在主子单、幂等性的基础上实现的,针对各种通信异常场景建立完善的补全机制,以交易中心、支付工具、银行网关、银行前置 4 层为例进行描述,简易版交易示意如图3所示:
图3 简易版交易流程示意
场景1,对于客户端,请求服务端可能会发生未建立连接、请求超时或者响应超时等没有返回正常结果的情况,客户端对以上情况的异常处理如图4所示:
图4 客户端请求服务端异常处理
Step1. 交易中心同步请求支付工具发生异常(网络中断、连接超时等);
Step2. 交易中心推送异常消息至 MQ 服务器,并同步返回上一层(状态处理中);
Step3. 交易中心监听 MQ 消息,自身消费该消息并执行步骤 4;
Step4. 同步调用支付工具,若仍发生异常则继续步骤 2,此时步骤 2 因异步不需要返回,最多进行 3 次。
场景 2,对于服务端,能够正常处理并返回响应,但服务端并不确定客户端能否正常接收到消息,服务端响应异常处理如图5所示:
图5 服务端响应异常处理
Step1. 银行网关正常接收银行前置响应;
Step2. 银行网关正常响应返回支付工具,支付工具进行业务处理,把结果推送到 MQ 让交易中心消费,交易中心、外部产品的处理以此类推,若步骤 4 先完成则不处理;
Step3. 银行网关推送异步消息至 MQ(无论步骤 2 是否异常);
Step4. 支付工具接收到 MQ 消息后进行业务处理,也把结果推送到 MQ 让交易中心消费,交易中心、外部产品的处理以此类推,若步骤 2 先完成则不处理。
场景 3,银行网关是支付系统最后一道屏障,之后是报文转换的前置机,不负责业务处理,当银行网关与银行前置出现超时,或者银行前置与银行端出现超时,支付交易的最终状态结果无法实时得到,需要一个定时批处理请求银行同步状态,银行网关异常推进如图6所示:
图6 银行网关异常推进
Step1. 银行网关定时进行状态同步;
Step2. 银行网关推送异步消息至 MQ;
Step3. 支付工具接收到 MQ 消息后进行业务处理,也把结果推送到 MQ 让交易中心消费,交易中心、外部产品的处理以此类推。
场景 4,支付交易的状态以银行网关为准,银行网关成功,则支付工具、交易中心、外部产品也应当成功,考虑到场景 3 中银行网关因 MQ 中间件在某段时间不可服务,因此比对支付工具的订单状态,采取内部交易状态同步的措施,内部交易状态同步如图7所示:
图7 内部交易状态同步
Step1. 批处理程序监控支付工具与银行网关数据不一致;
Step2. 银行网关推送异步消息至 MQ;
Step3. 支付工具接收到 MQ 消息后进行业务处理,也把结果推送到 MQ 让交易中心消费,交易中心、外部产品的处理以此类推;
场景 5,在场景 4 中,只解决了银行网关与支付工具状态不一致的问题,而支付工具与交易中心不一致,交易中心与外部产品不一致则由场景 5 的批处理程序比对每两层之间数据,并推送微信消息提示人工干预处理,内部交易状态监控处理如图8所示:
图8 内部交易状态监控处理
Step1. 批处理程序监控支付工具与交易中心数据不一致;
Step2. 系统报警,人工介入进行处理。
(4)日志规范:分布式系统产生的日志一般也是分布式(提高日志打印效率),而一笔交易可能在不同服务器上不同模块之间流转,规范的日志能够提供高效准确地线索,便于生产上线运营过程中问题排查,日志打印包含如下信息:
RootID:交易全局 ID,由交易发起模块生成,即外部产品模块,该笔交易在支付系统所有系统运转的业务流程日志都需记录此 ID,便于根据 ID 查到交易的所有记录,包括系统与人工的操作,通过 RootID 日志搜索效果如图9所示;
图9 RootID 日志搜索效果
ContextID:两个模块间交互的上下文 ID,用于幂等性控制,业务流程日志都需记录此 ID,便于根据 ID 查到关联系统的所有记录;
IP 地址及端口号:用于记录接口调用时的源 IP 地址和端口号,用于定位客户端来源及日志所在服务器,便于抓取生产日志分析问题。