导读
在上一篇文章中我们通过场景举例的方式,讨论了一套相对通用的互联网业务账户系统,从业务模型上应该如何定义。那么除了从业务模型上进行定义外,在具体系统实现上又该如何设计?又有哪些需要注意的地方呢?在本篇内容中小码农就和大家一起讨论下账户系统的实现细节,希望可以和大家一起交流进步。
事实上账户系统的业务逻辑是比较复杂的,对数据的一致性要求很高,特别是记账动作涉及强事务特性;另外,性能问题也是常常制约账户系统稳定性的一个比较突出的方面。在这种情况下,我们还需要考虑系统的业务通用性设计问题,而这必然也会涉及很多配置项的设计,增加系统的逻辑复杂度。但是,也只有处理好了这些问题,账户系统才能保证业务的持续扩张,否则再好的理念也只是空中楼阁。然而,处理好这些复杂的问题,事实上并不只是某一点的设计就可以达成的,既需要逻辑流程设计上的优化,也需要采用合适的技术方案,更需要一个合理的系统结构。
下面我们就从系统结构、整体流程、数据模型、记账规则,以及日终对账这几个方面与大家探讨从系统层面应该如何设计。另外对于账户系统中制约性能最常见的热点账户问题,也会和大家一起探讨。
系统结构
在之前的内容中,我们提到要设计一套可以满足互联网业务扩展的账户系统,所以账户是这套系统的基础,为了更好地支持不同业务、或同一业务不同账户的开户,我们需要将开户逻辑设计成独立的子系统,独立地提供包括开户、账户信息查询、余额查询在内的服务,以便逻辑复杂到一定阶段后可以更容易的扩展。在完成开账户动作后,就需要根据业务规则,设计好逻辑体系下不同交易类型的记账规则了,而这种规则配置是否智能,则是账户系统是否通用的关键;配置完规则后账户系统就可以接收业务发起的交易请求,并根据规则的配置完成业务资金流的处理了,所以,从系统结构层面也需要将记账核心服务设计成独立的子系统;此外,为了适配业务层不同的交易类型,主要是隔离记账逻辑与交易逻辑,还需要前置账户交易系统。
最后,为了确保账户余额与流水之间的平衡,我们还需要在日终时对主要资金账户进行对账核算,确保账户流水发生额与账户余额的一致性。所以从系统结构层面,整个系统主要可以分为四个部分:
之所以在账户系统、核心记账系统之上设置一层账户层交易系统,是为了将多变的业务交易逻辑与相对通用的记账逻辑进行隔离,避免在核心记账系统中冗余过多的业务逻辑导致后续出现臃肿的情况。例如业务层的交易类型可能是复杂多变的,如车费充值、押金支付之类,而这样的逻辑是没有必要让记账系统感知的,记账系统只需要根据交易系统传递的记账规则,根据会计分录完成资金流处理即可。
整体流程
为了确保系统能够正常Run起来,需要对系统整体的流程进行规划,这部分流程即包括线下流程,也包括线上流程,它是确保整个系统闭环的基础。例如,以账户系统需要支撑A公司打车业务为例,假设账户系统已经存在的情况下,那么它需要支撑这个业务应该经历以下两个阶段的流程:
(一)、线下规划配置流程
按照正常的流程设计,在业务开展之前,需要根据实际的业务资金流设计好具体的账户及交易资金逻辑,这部分逻辑一般是由PM与资金部门线下确认后形成正式的产品规格文档。之后,由具有权限的运营人员通过后台,或者前期在没有完善配置系统的情况下,由技术人员初始化到系统中,由于这部分配置关系到交易核心流程,所以在流程及操作规范的制定上要严格把控,避免配置错误导致的严重系统逻辑错乱问题。
在这部分流程中我们首先需要配置业务主体,这里需要为A公司配置客户开户信息,之后需要按照之前业务模型定义的结构,在客户下为其开通表示打车业务线网约车用户,至此在系统中就完成了“谁?要干什么?”的定义。而具体“怎么干?”,则是在后面我们要重点配置的内容。
那么具体需要配置什么内容呢?
因为,账户系统本身是为交易逻辑服务的,所以我们需要明确业务中涉及账户逻辑的交易有哪些类型,例如在约车业务中主要涉及到司机端开户、乘客开户、现金支付车费、余额充值、余额支付车费、司机提现等这些交易类型,所以我们需要为这些交易类型定义交易编码(tradeCode),并将其与之前开通的网约车业务用户进行关联,这样在后续的系统交易流程中,就可以进行交易权限控制,各业务线逻辑各自关联自身的交易类型,以免互相干扰了。
而定义了这些交易类型以后,账户层交易系统具体接收到这样的交易请求后应该怎样执行逻辑呢?在互联网公司早期业务发展的过程中,很多都是将账户逻辑与交易逻辑耦合在一起的,这样会导致各个业务账户逻辑陷入要么继续耦合,要么各自定制、重复开发的怪圈。
而要让这种逻辑变得通用,就需要将其规则化,即账户层交易系统接收到指定的交易请求类型后,会根据系统用户交易规则配置,获取开户、记账交易规则信息,然后记账系统和开户系统就会按照规则指定的逻辑执行了,这种执行逻辑识别规则,不感知具体业务逻辑。在上述流程中,我们将“平台层开户”也设计成了规则,只是这种开户动作接口并不对实时交易接口开放,一般是通过后台设置,即通过后台调用开户系统机构(平台)开户接口,开户逻辑根据网约车平台层开户规则,自动开立“服务费账户”、“代收付平台账户”、“结算账户”、“市场营销账户”这类开展网约车业务所需的平台层账户体系。
其他开户交易类型,如司机端开户、乘客开户由于需要在具体用户注册、司机入驻时通过实时交易接口自动调用Api开通,所以这里需要配置好开户规则即可;至于,各个涉及资金变动的交易类型,如车费支付、余额充值之类,涉及到具体的记账规则的逻辑,也需要通过配置相应的交易记账规则,关于记账规则的配置设计涉及一点会计知识的细节,会在后面的内容中介绍到。
(二)、线上系统交易流程
完成系统级的数据定义及规则配置后,整个账户系统就会通过开放Api,为各个业务交易系统提供线上账户交易接口服务了。
业务层交易系统向账户层交易系统发起交易请求后,系统会首先根据传递的客户、用户ID对请求权限进行识别,只有在(一)流程中设置了客户、用户主体信息的交易请求才被允许,之后账户层交易系统会根据传递的交易编码(tradeCode)识别交易数据开户交易类型,还是交易记账类型。开户交易类型则被转发至开户子系统进行开户处理,开户子系统根据tradeCode设置的开户规则,完成注册用户账户体系的开通,如:乘客张三,会依次为其开通客户身份、打车用户身份、以及打车用户涉及的余额账户,余额返现账户,押金账户的开通。
而如果为交易记账类型,假设这里为乘客使用现金支付车费,则请求被转发至记账子系统,记账子系统根据业务线客户、用户ID、乘客业务用户ID以及tradeCode获取记账规则,完成资金逻辑记账处理。
规则涉及的账户逻辑如下:
记账规则
在账户系统中,记账规则逻辑的设计是最为复杂的一项设计,需要在兼顾会计逻辑的情况下,还需要将其设计成较为通用的规则,以上面用户支付车费的账户资金逻辑为例,如何将其设计成规则配置呢?
在以上记账规则表中,定义了业务线用户ID(merchUserId),表示该业务模式在系统中的唯一编码;记账交易类型(tradeCode)由具体的业务线交易模式定义,例如打车业务用户现金支付车费。这两个字段由定义客户用户信息、用户交易类型,可根据实际业务定义。
后面的字段主要定义了账户交易逻辑的情况,例如changeType中定义的记账,表示按照规则正常的借贷方向进行余额更新,而冻结、解冻则是对账户余额进行冻结、解冻操作,增加、减少是根据规则直接对账户进行增加及减少操作,之所以定义上述不同类型,主要是为了适应不同账户操作逻辑,具体定义及含义,大家也可以根据自身公司的实际业务情况进行定义。
可能这么解释大家会有比较大的疑问,我们以线上交易流程中涉及的网约车用户现金支付车费的资金逻辑为例:
根据规则表的设计,以上规则描述了各账户资金流的变动逻辑,其中涉及账户类型、借贷方向、资金类型、记账科目以及记账步骤。当业务层交易发起至账户层交易系统后,账户层交易系统会获取以上记账规则,并根据规则描述的账户类型,找到普通消费用户、平台层用户、普通服务用户对应的账户信息,并按照规则逐条进行记账逻辑执行。
通用数据模型
在上面的流程及规则涉及中,以网约车业务为例,通过两个流程说明了账户系统应该如何支撑着项业务,虽然,看着并不是特别复杂,但是从系统设计上看却是涉及了很多实体信息,接下来我们从数据建模的角度,看看如何设计系统的数据模型。
在模型中我们根据逻辑,抽象了客户、用户、账户相关实体,同时也抽象了账户流水、科目信息、记账规则、开户规则,交易类型等信息。系统通过这些实体设计相关表结构,系统就初步具备了运转能力了,大家可以根据实际情况增加其他实体信息。
会计科目
会计科目是账户系统中比较基础的概念,它的定义决定了账户的一些属性特征,例如是否可透支,属于资产类or负债类,可以根据不同公司财务的需求进行设计。
记账策略
大家知道记账动作是强事务的,按照正常记账逻辑以上规则执行过程中涉及的4个账户更新需要具有原子性,要么都执行成功,要么全部回滚,而对于普通消费账户、普通服务账户,这些账户都属于个人账户,在线上实时交易中的并发度是有限的。而对于平台层账户,包括代收付账户、服务费账户等,平台所有的交易都涉及这些账户的资金变动,所以如果在某一个交易过程中对其加锁,会导致该账户记录的加锁-更新动作非常频繁,成为热点账户,影响系统性能。
所以在规则中我们加入了是否缓冲记账的配置,一旦配置为缓冲记账,则在执行该规则时,只是把该记账逻辑放入缓冲队列的逻辑与其他规则在一个事务中,而具体账户更新逻辑则是由缓冲记账系统完成,该逻辑可设置为日间完成,或日终完成。
从而缓解热点账户问题导致系统性能瓶颈,但是需要注意,这种方案也对缓冲记账逻辑提出了比较高的要求,需要缓冲记账系统尽量保证记账动作执行成功,一旦执行失败前面同步执行成功的记账逻辑回滚起来会比较麻烦;另外,如果之前的同步记账逻辑在发送缓冲队列成功后,自身逻辑又失败了,则需要及时发送冲正机制,取消该缓冲记账动作。
日终对账
为了确保账户余额始终处于相对正确地状态,需要对日终账户流水进行各种试算核对,确保所有流水发生额累加后的余额+期初余额能够与当前余额匹配,这里会涉及到比较复杂的对账逻辑,需要大家在实际系统研发实践中加以考虑。
技术点拓展
在账户系统的研发设计过程中,还会涉及很多其他问题,例如账户流水数据量非常大,同时数据的留存时间又要求比较长,所以需要考虑数据的分布式存储,目前小码农所在公司,采用了TIDB这种分布式数据库,大家可在实践中根据自身情况进行选择。
另外,账户的频繁更新,在系统并发量非常高的情况下,还会遇到性能瓶颈,如何在保证用户体验及数据正确性的情况下,采取更多的技术手段,如采用Redis/Codis进行缓存记账,也需要在实践应用场景中进行探索。
后记
由于账户系统逻辑相对比较复杂,涉及很多会计知识及细节逻辑,本文只是描述了一种理念与思路,真正做好这套账户系统还需要大家根据自身场景进行取舍与裁剪。由于作者水平有限,不足之处,还请多多包涵!
— 完 —