【分享】

1、系统职能

先介绍下我设计的账务系统和会计系统的职能,因为很多术语在不同的公司里含义差别很大。

1. 账务系统
分为账户管理和账务核心。账户管理主要负责虚拟账户的维护,包括开户、冻结、销户等等。账务核心主要负责交易系统驱动的账户余额变化和账户流水的登记。在我眼中账务系统承载了我们虚拟账户体系。

2. 会计系统

会计记账、借贷平衡检查、科目结转、明细账加工。

以上只是我设计的账务与会计系统的职能,目前会计系统只完成了【会计记账】。

2、设计概述

我再来解释一下,为什么要将在账务和会计系统放在一起讨论。因为在我的设计里,我将账务系统作为会计系统的前置设计的。在我的理解中账务和会计的主要功能其实都是在管理账户(科目)的余额和流水。会计系统的账户就是会计科目,账务的账户是会计系统中叶子科目的子节点,看起来账务的账户有点像会计记账时的辅助核算项,有些像但不等同,辅助核算项根据实际的记账场景可能会设计的很复杂,但账户一般我们不会设计的这么复杂。

说的有些拗口,我举个例子介绍下:

比如A、B用户分别在X平台充值了10、20元,会计可能会如下设置账户进行记账:

image

其中【预收账款】是一级科目(账户),【平台充值】是二级科目(账户),【用户充值】是三级科目(账户),目前也是叶子节点账户。 (某用户充值)是辅助核算项,但不会记到这个丁字台账上,为了便于理解我自己给加上的。

财务人员只要记录下这个【预收账款-平台充值-用户充值】账户上有30元就OK。但我们账务系统要更详细一级,我们要知道A用户的账户有10元,B用户的账户有20元。所以我就为A和B各设立一个分账户,并将它们作为 【用户充值】的子账户。这样看起来,我们账务中的所有账户其实都是会计系统中的叶子科目的子账户了。

到这里就提出了2个问题:

这么做有什么好处么?

  1. 这样做使我们账务系统中的每个账户都与会计科目有了对应关系,相应的每个账户也就继承来了【借贷】属性。这样对于每种交易类型,我们可以借助借贷记账法(复式记账中的一种)的规则来判断交易中各主体账户的记账规则
  2. 因为账务账户与会计科目有了对应关系,账务记账后,驱动会计的规则也很简单。

既然职能都是管理账户为什么要分两个系统呢?

这是因为这两套账户服务的对象不同。账务系统主要为客户服务,主要对外。会计账户主要服务的是财务人员,主要对内。这两类人对账户的关注点不同。客户对余额实时性和流水的顺序关注多一点。而财务关注的是余额的时间快照,而且在一个会计周期内流水的顺序要求不高,他们关注期初余额、期间发生额、期末余额。

因为这些特点,所以这两个系统更新余额与记录流水的方式不同。账务是实时修改余额,而会计是期末批量修改余额。

这里以一个简化了的充值例子(不考虑服务费)介绍下我是怎么记账的。

用户A用通过B渠道充值到我们平台100元,会计可能会这么记账:

借:  应收账款  100  -- 账务系统中【B渠道充值账户】的根账户(渠道欠我们)  
贷:  预收账款  100  --  

账务系统中【A用户的余额账户】的根账户(我们欠用户)

实际中会计很少记录到一级科目,而是根据业务实际情况开多级账户,记到子账户上,这里为了便于理解就直接写了一级科目。 我账务记账时,就是直接根据会计记账规则在【B渠道充值账户】和【A用户的余额账户】各+100,并登记相应的余额变动流水。

在实际操作中发现将账务账户作为会计账户子账户的设计并不太好,外部账户还好,内部账户这么设计成本挺高的。我再准备将直接作为子账户的方法改为不直接关联,而是建立映射关系。

下面介绍下账务中主要表的主要字段:

账户表

  1. 账户所属机构
  2. 账号,这个与客户1:1的关系,但只出现在账务系统中,在账务系统中有一个客户Id与账号映射表。除账务系统标识客户都用custom_id,账务用account_id。起一层内外信息隔离的作用。
  3. 账户类型: 现金、理财、保证金、营销等等
  4. 币种。 以上4个字段构成一个分账户的唯一标识。
  5. 状态
  6. 会计类型:资产、负债、共同类、收入、费用。
  7. 权限
  8. 借贷方向
  9. 余额
  10. 冻结余额
  11. 上次余额
  12. 上次冻结余额
  13. T冻结余额。这个比较特殊,暂时没用上,一旦使用了TCC事务,这个值就有用了。实现方式侵入库表设计也是很忧伤…
  14. 透支额度。预留字段,暂时不用
  15. 业务最后修改时间
  16. 业务修改签名
  17. 业务最后修改交易id

因为目前用户量还不会对mysql查询有影响,所以这里没有分库。这是按 用户账户、商户账户、内部账户进行分了表。 这样分是因为数量上 用户 > 商户 > 内部,使用频率上正好反过来,再加上我们现在的数量不影响InnoDB的效率。使用的sharing-jdbc

流水表

(冻结解冻业务在另一张表)

1.账户id
2.账户的四个主属性
3.交易id
4.交易step。(在一个复杂的交易中可能一个账户会被修改多次,这个step标识是哪一次。中间账户经常出现在一次交易中被多次修改)。账户id、交易id、step构成唯一索引保证账户修改的幂等性。
5.交易类型。
6.交易标签。
7.交易前余额
8.交易前冻结余额
9.发生额
10.发生冻结额
11.交易后余额
12.交易后冻结余额,之前设计上面这么设计主要是怕出现数据不一致的问题时好回溯问题,但后来发现这么做好处很多,比如在会计系统功能不健全时,财务人员经常要某时刻的账户余额快照。
13.会计日。 各系统内部对账用统一日期
14.期望入账日。 补账时用,比如18号补了笔13日的账,这个记13日。
15.冲正标识
16.可见性。用户可见、运营可见、内部可见。(其实这个字段主要是冻结解冻流水表用)
17.审批单id
18.操作原因

冻结解冻流水表和上面的流水差不多,就不复述了,就有几个特殊点:

1.解冻流水必须关联冻结流水。
2.冻结流水上有还有多少资金没解冻。
3.流水类型目前有: 冻结流水、解冻流水、解冻冲正流水(这个用于消费冻结金的抹账用)

记账规则表

交易类型 交易码 账户类型 借贷方向
001(充值) 001029(现金充值) 渠道充值账户 DR
001(充值) 001029(现金充值) 用户余额账户 DR

整个系统主要难点还是在设计上,因为吞吐量不大技术上难点还没暴露出来,目前考虑到的:

  1. 幂等保证: 入库前靠分布式锁,入库后靠数据库唯一索引
  2. 分库分表:外部用户量太大就分表,TPS高了就分库。目前用sharing-jdbc
  3. 分布式事务:一旦分库就会产生分布式事务的问题。我个人倾向于使用TCC事务。因为目前TCC没有商业实现,调查的是GitHub上比较火的transaction-tcc和 byteTCC。byteTCC我没仔细看,但从帮助文档和demo上看感觉会有关系型数据库事务嵌套远程调用的风险,所以如果以后分库会用transaction-tcc。(蚂蚁云上有个DTX实现。除了TCC实现,阿里云推出了gts,这个还没调研。)
  4. 热点商户:这个很多童鞋都分享过【缓冲记账】了,我就不复述了。就是在本地事务和mq时会有一个一致性的问题,就是常说的 事务消息。这个阿里的RocketMQ(云上用ONS)、qunar的QMQ都支持,只是实现方式不同。

【问答】

1 Q:这一套系统,几个人开发,用了多长时间?电商侧的账户,客户的账户数是非常非常多,百万级别甚至更多。你们会为每个客户的账账户做余额表吗?还是汇总科目做在一起?

A:我们现在的账户数量级还没有那么多。按照我的设计会为每个用户、商户分配一个账号,每个账号根据类型会分配多个账户。如果账户表的数量超过1000w就重新进行分表。系统开发使用的时间不长,因为现在支持的业务类型还不是很丰富。随着新需求的提出,再不断的添加新的功能。


本文档来自支付产品技术交流群的聊天记录整理,由志愿者整理并发布到本网站。如需要及时收到来自支付产品技术交流群的最新消息,请扫码关注“凤凰牌老熊”的微信公众号。 本群面向支付行业的有经验(2年以上)的产品经理、软件工程师、架构师等,提供交流平台。如想加入本群,请在本文评论中留言(不公开),说明所在的公司、负责的工作、入群分享的主题和时间。