银行系统分布式对账 设计需要注意的问题总结

0、常识

 

分布式系统数据不一致性问题是不可避免,于是对于重要交易(跟钱打交道的,操作不具有“幂等性”的)的都需要对账,对账就有个对账日期(对账日期必须是对账各方共享的,一致认可的),这个日期不能采用自然时间,因为各个子系统的本地自然时间总存在一定的差异,再者交易还有时延,跨国交易还有时区问题。因此,对账日期希望由某个子系统分配,然后在交易请求或响应时发给其他子系统,实现共享。于是,分布式应用中,一笔交易往往需要: 发送方自然时间; 接受方自然时间; 财务时间(对账时间)。

 

1、具体场景
第三方支付机构(比如UMPAY,支付宝)要给银联发送一个扣款请求,银联顺利扣掉用户银行卡中的钱,给第三方支付机构发送“成功”的响应,并在响应中给出了该笔交易的对账日期。
要知道银联的报文格式都是ISO8583的,一般在第15域给出个格式为MMdd的对账日期。(注意:不是yyyyMMdd,省了个年份,跨年的时候就容易出错)。
而第三方支付机构由于是后来发展的,意识到类似千年虫问题的存在,于是在数据库设计时对账日期是以yyyyMMdd存放的。
于是接下来一个很自然的工作是:当银联给出MMdd的对账日期,第三方支付机构应该弄出个yyyyMMdd的对账日期。

 


2、款年时可能出现的问题
前提:银联和第三方支付机构的结算是按日结算
(1)财务日期超前自然日期(提前日切)
    【边界情况描述】
    当前自然时间2008-12-31 23:00:00,但是银联的财务日期提前切到2009-01-01;第三方支付机构给银联发一个扣款请求,并收到成功响应,财务日期给的是“0101”(MMdd格式);
    【第三方支付机构补齐年份】
    第三方支付机构依据本地系统时间2008-12-31 23:00:00,对“0101”补齐年份,得到SettlementDate=20080101,而当前自然日期是NaturalDate=20081231;
    由于双方协商的结算周期是“按日”,那么正常情况下SettlementDate与NaturalDate最多相差一天,现在却出现:Distance = |SettlementDate - NaturalDate| >> SettlementPeriod,于是判断本次计算出现了因跨年导致的年份不对的情况。if(SettlementDate < NaturalDate) SettlementDate.year += 1;最终得到的对账日期是:20090101
   
(2)财务日期滞后自然日期(滞后日切)
    【边界情况描述】
    当前自然时间2009-01-01 00:10:00,但是银联的财务日期尚未做日切,财务日期是2008-12-31;第三方支付机构给银联发一个扣款请求,并收到成功响应,财务日期给的是“1231”(MMdd格式);
    【第三方支付机构补齐年份】
    第三方支付机构依据本地系统时间2009-01-01 00:10:00,对“1231”补齐年份,得到SettlementDate=20091231,而当前自然日期是NaturalDate=20090101;
    由于双方协商的结算周期是“按日”,那么正常情况下SettlementDate与NaturalDate最多相差一天,现在却出现:Distance = |SettlementDate - NaturalDate| >> SettlementPeriod,于是判断本次计算出现了因跨年导致的年份不对的情况。if(SettlementDate > NaturalDate) SettlementDate.year -= 1;最终得到的对账日期是:20081231

(3)第三方支付机构得到“补齐年份”的算法
    String-yyyyMMdd SettlementDateNormalization(String MMdd-BankUnion) {
        String SettlementDate = System.currentTime("yyyy") + MMdd-BankUnion;
        String NaturalDate = System.currentTime("yyyyMMdd");
        long Distance = |SettlementDate - NaturalDate|;
        long SettlementPeriod = 1天;//双方协议约定的对账周期常量
        if(Distance >> SettlementPeriod) {//当时间差远远大于对账周期,则说明出现跨年的问题了
            if(SettlementDate < NaturalDate)  SettlementDateNormalized = SettlementDate.yyyy + 1;
            else SettlementDateNormalized = SettlementDate.yyyy - 1;
        }

        return SettlementDateNormalized;
}

(4)分析(3)中的算法
虽然双方协议约定的对账周期常量是1天,但是银联日切工作可能是人工干预的,这样可能因为认为因素导致好几天都没做日切,比如:当前自然时间已经是20090710号了,但是银联的财务日期却还是20090707号。按照上面的算法,第三方支付机构在补齐年份时,输入财务日期:“0707”,初步补齐是SettlementDate=“20090707”,本地自然日期是NaturalDate=“20090710”,两者之差Distance > SettlementPeriod;同时SettlementDate < NaturalDate,于是被规格化成“20100707”。
应该注意到跨年导致的Distance会远远大于SettlementPeriod,这个Distance都快接近一年了。随意算法:if(Distance >> SettlementPeriod)的“远远大于”的具体实施应该是:
if(Distance > SettlementPeriod * Tolerance=7) 最多容忍7天不做日切,或提前7天做日切。

String-yyyyMMdd SettlementDateNormalization(String MMdd-BankUnion) {
        String SettlementDate = System.currentTime("yyyy") + MMdd-BankUnion;
        String NaturalDate = System.currentTime("yyyyMMdd");
        long Distance = |SettlementDate - NaturalDate|;
        long SettlementPeriod = 1天;//双方协议约定的对账周期常量
        int Tolerance = 7;//对账日期切换相对协议滞后或提前的容忍度

        if(Distance > SettlementPeriod * Tolerance) {//当时间差远远大于对账周期,则说明出现跨年的问题了
            if(SettlementDate < NaturalDate)  SettlementDateNormalized = SettlementDate.yyyy + 1;
            else SettlementDateNormalized = SettlementDate.yyyy - 1;
        }
        return SettlementDateNormalized;
}

 

(5)java代码

对账日期规格化(补齐年份)算法代码 复制代码
  1. /**   
  2.      * @param   bankCheckDate   银行传入的清算日期,不带年的   
  3.      * @return  本地补充一个带年的清算日期   
  4.      * */   
  5.     protected String caculateBankCheckDate(String bankCheckDate) throws ParseException {   
  6.         String yyyyMMdd = null;   
  7.         String yyyy = Util.strDateTime("yyyy");   
  8.         String stldate = yyyy + bankCheckDate;   
  9.         SimpleDateFormat fmt = new SimpleDateFormat("yyyyMMdd");   
  10.         long slttime = fmt.parse(stldate).getTime();   
  11.         long currtime = System.currentTimeMillis();   
  12.         long dis = slttime - currtime;   
  13.         long day1 = 24 * 3600 * 1000;   
  14.         int tolerance = 7;   
  15.         if (Math.abs(dis) >= day1 * tolerance) {   
  16.             if (slttime < currtime) {   
  17.                 yyyy = String.valueOf(Integer.parseInt(yyyy) + 1);   
  18.                 stldate = yyyy + bankCheckDate;   
  19.             } else {   
  20.                 yyyy = String.valueOf(Integer.parseInt(yyyy) - 1);   
  21.                 stldate = yyyy + bankCheckDate;   
  22.             }   
  23.         }   
  24.         yyyyMMdd = stldate;   
  25.         return yyyyMMdd;   
  26.     }  

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值