财务数据报表的跨天之苦

数据报表,尤其是金钱相关的财务报表,对数据的准确性犹为敏感。而服务系统间的处理时间点的存在差异的客观事实(网络时延,失败重试等原因),导致在以天(或月/年)为维度的数据报表中,或对账中,常常出现数据不平的问题。

业务描述

以用户充值加金币的业务为例

充值请求---------------->  充值服务A
                 (支付成功的时间x:2020-07-08 23:59:59)
                 ----------------> 金币服务B
                           (加币成功的时间y:2020-07-09 00:00:01)

现在,有以下数据报表的需求:输出每一天支付金额以及对应的加金币数目。

  • 从上图可以看出,时间x和时间y不在同一天,在出数据报表的时候,这条充值记录应该归为7月8日还是7月9日呢?
    1. 从用户的角度看,充值成功的行为是一个原子操作,用户不关心服务方底层有区分支付时间和加币时间;
    2. 从公司财务的角度看,他们同样不关心1描述的问题,只关心报表是否平帐(支付金币=加币数目)

解决方案

要解决以上问题:需要统一充值请求的业务时间

充值请求---------------->  充值服务A
(生成当前时间addTime并传入)
                 (支付成功的时间x:2020-07-08 23:59:59)
                 (接收addTime参数)
                 ----------------> 金币服务B
                           (加币成功的时间y:2020-07-09 00:00:01)
                           (接收addTime参数)
  • 充值请求在发起时就会生成一个当前时间addTime,并一直透传到底层服务,那么统一以addTime作为充值的时间点,就不会出现跨天差异导致的数据不平问题。

注意事项和监控重跑

  • 上述的方案有一个问题需要解决:addTime 时间的合法性
    1. 传入的addTime大于当前时间
    2. 传入的addTime远远小于当前时间

问题分析

  • 问题1显然是错误的,是不合法的请求,但由于系统间可能存在微小差异,可以在逻辑上拒绝addTime大于当前时间超1分钟的请求
  • 问题2是客观存在的。
    1. addTime是一开始生成的时间,当传到每个服务之后,理论上服务的当前时间必然大于addTime
    2. 由于网络超时,重试等原因,相差的时间可能达到分钟级;而由于链路上下层服务因为故障等原因暂时无法处理,那么相差的时间可能到小时级以上。
    3. 一般情况下,一个充值请求正常情况下,一分钟之内就能完成。所以有一个定时任务会在凌晨生成昨天的报表,以供财务人员第二天查看。
      如果报表已经生成之后,addTime是昨天的充值记录才重试成功,那么将导致该条数据没被统计到!

解决方案

针对上述问题2导致的数据报表错误

  1. (程序阻断)当数据报表已经生成之后,拒绝addTime<=昨天的请求,避免充值数据和数据报表不一致。同时增加告警,支持解除限制和重跑报表。
  2. (告警重跑)当数据报表已经生成之后,接收到addTime<=昨天的请求,且执行成功时告警,通知相关人员重跑报表。

扩展

有人可能会问,上述的解决方案始终依赖告警和人工补偿。有没有更加自动化的手段?

  1. 二八法则:避免将资金、精力和时间花在琐碎的多数问题上。上述的方案其实已经解决了大多数场景的问题,如果要完美的解决,必然需要花费更大的精力(可能花费时间比实现原功能更多)。
    当然可以想办法解决,如果确实有必要的话。
  2. 针对一些不常发生的异常问题:增加限制、增加告警、增加人工补偿,是安全快捷、高效成本低的手段。