目录:
对账的目的
在会计中,对账的目的是为了保证账簿记录的真实、准确,主要是为了保证资金的正确可靠。
在财务、支付系统中都会引入对账系统。
一个扣款系统的示例
这里先举个例子,供下文使用。
【系统A】调用【系统B】对用户进行扣款操作。
【系统A】用 MySQL 表 table_a 记录扣款流水,字段如下:
字段
说明
user_id
用户标识
amount
扣款金额
deduct_sn
扣款单号,用一个全局唯一ID生成系统生成,要求全局唯一,会传到系统B。
status
状态。1初始化,2扣款成功,3扣款失败
create_time
创建时间
update_time
更新时间
【系统B】用MySQL 表 table_b 存储扣款流水。字段设计恰巧和 table_a 一致。
对于【系统A】的构建者而言,能保证系统 95% 无逻辑问题。也就是 5%的可能性有隐式逻辑问题。
显式逻辑问题,比如【系统A】一运行就报错;
隐式逻辑问题, 比如【系统A】运行时不报错。但是预期是对用户1扣款,结果调用【系统B】时,变成对用户2扣款,扣款正常,但是扣错人了。
【系统A】如何提升业务正确性?
为什么要对账?
引入【对账系统】后,【对账系统】对【系统A】和【系统B】的数据进行核对。对于【对账系统】的构建者而言,能保证该系统 95% 无隐式逻辑问题,就是存在 5% 的可能性有逻辑问题。
我们计算下各种概率:
系统A是否有隐式逻辑问题
对账系统是否有隐式逻辑问题
概率
情况1
是
是
5%✖️5% = 0.25% 。
情况2
否
否
95%✖️95% = 90.25% 。
情况3
是
否
5%✖️95% = 4.75%
情况4
否
是
95%✖️5% = 4.75%
情况1、2 中,我们看到的是系统稳定运行,但不知道有没有问题。
情况3、4 中,对账系统会报错,我们要检查是【系统A】出了问题,还是【对账系统】出了问题。通过问题定位和修正,我们将每个系统出现隐式逻辑问题的概率从 5% 降到更低,例如降低到了 3% 。
对账能发现什么问题
对于扣款系统能发现下面的异常。
系统间异常
【系统A】有扣款成功流水,【系统B】没有对应的扣款成功流水。
【系统B】有扣款成功流水,【系统A】没有对应的扣款成功流水。
系统内异常
deduct_sn 出现重复。
对账的使用场景
对账的本质是通过引入一个【旁观者】,提升当前流程、业务的正确性。
既然是为了【提升当前流程、业务的正确性】,那么财务、支付外的系统,也应该尽可能的引入对账系统。但此时处理的数据与钱无关,也就不是【账】。此时,可以称之为核对、数据核对,或者说广义的对账。
在财务系统中,引入对账系统,会尽可能保证钱没有问题。
在券系统中,引入对账系统,会尽可能保证券没有多发,或者没有少发,或者没有一券扣多次,或者没有券未扣钱。
甚至,在个人发布文章时,通过引入一个旁观者帮忙review,可以尽可能的保证文章的一些数据、引用等内容的正确性。
对账的实时性
对账在大部分场景中是事后行为。
根据实时性可分为:
准实时对账。比如业务发生后,5分钟内完成对账。
离线对账。比如每天进行一次全量对账。
如何对账
还是以【系统A】调用【系统B】对用户进行扣款操作为例,两个系统都有【扣款流水】。扣款单号是全局唯一的,两个系统都会记录。
离线对账
假设是每日核对,
将两个系统的 DB(最好是从库)数据导入 HIVE 中,每天导入一次,写一个 FULL JOIN 类型的 HQL (类似SQL)核对今天零点之前的数据。比对两边相同单号的成功状态的记录,关键信息是否一致。比如用户ID、金额。
另外,要考虑跨天问题。比如【系统A】中的某条扣款记录创建时间是 23:59:59,【系统B】是第二天的 00:00:01 创建。这种情况数据可能对不上,但可以先忽略。
假如今天日期是 2020-01-30
全量对账代码示例:
增量对账代码示例:
准实时对账
【分钟级】的对账可以称作【准实时对账】。
对账的本质思路是一样的。不一样的是,每次对的是最近几分钟的发生业务。
实现上,可以写一个定时任务,没3分钟跑一次最近6分钟的业务数据对账。
数据源最好是DB从库。
系统内数据核对
如果【系统A】中,扣款记录基于用户ID分库分表。因为要求扣款单号 deduct_sn 全局唯一,所以单表中最好对 deduct_sn 加上唯一索引。但是这只保证了单表中全局唯一,无法保证所有的表放在一起后全局唯一。
此时,关于唯一性问题,我们两个选择:
选择1:完全相信扣款单号生成系统。
选择2:不完全相信扣款单号生成系统。
对于选择2,我们可以加一个数据的离线核对,核对SQL如下:
如何处理对账异常数据
总的原则是,先将异常数据记录下来,找到原因并处理好后,将结论和处理流程记录下来。
事前、事中、事后
系统可以通过事前、事中、事后3个阶段来增强系统正确性,其中对账属于【事后】。
以【扣款系统】为例:
事前:代码 review
单元测试
测试环境测试
线上灰度验证
事中:数据库唯一索引约束
【系统B】收到扣款请求后,将金额和用户ID与反查【系统A】该扣款单对应的金额和用户ID比对。不一致,则拒绝扣款。
事后:对账。
留一个问题,在分库分表的场景下,deduct_sn 如何在【事中】保证全局唯一?
(待完善)