前言:今天有同事反映说客户在平台投标后,看到的是失败状态,但是钱在某银行的状态是被冻结了,我这边给出答复是只有投标成功才会冻结。
首先写下流程:P2P对接某银行托管,某银行的部分接口要求我们通过同步回查的方式对订单再次确认,投标是其中一个。我们这边P2P后台是5分钟查询一次金额订单表,把处理中和待处理的订单拿出来组装报文加密上送到X银行,然后根据返回数据确认是否ok?如果失败那么这笔订单关联的操作也是失败,成功则反之。
为什么需要回查呢?
- 用户在某银行操作的时候如果超过25分钟就是失败,但25分钟内都属于处理中,需要空5分钟确认期间的状态。
- 可能是某银行考虑到消息一致性问题,所以需要我们再请求一次确认订单,当然时间也是5分钟后。
问题重现:
恰巧的是今天才发现这个bug,而且是很严重的bug。因为我再次确认订单的时候,发现ctime和utime相隔1秒,但是很奇怪的用户操作哪有这么快1秒就完成了操作。再后来看到订单创建时间是12:15分,马上想起我们平台定时回查是5分钟一次,刚好满足定时任务,也就是用户刚好12:15创建了一笔订单是待处理,还没跳到某银行页面,就被我们后台定时任务扫描去了某银行,某银行返报文没有该订单,然后悲剧来了,用户高兴的投了一笔钱,回调的时候发现订单失败了,代码不往下处理了,正是因为这样这个原因导致为什么看到投标失败了但我的钱却冻结了。
这时候不禁感觉一阵冷汗,要是以后搞什么秒杀活动,或者人气很高的时候,100个人都在定时任务扫描那个点投资出去那就悲剧了,可以想象一下100个人看到的结果都是失败,然后我们要么修改数据库,要么解冻用户钱,但带来的损失肯定没办法弥补了!
于是进行思考和查阅代码后总结了如下2点:
- 错开定时任务扫描时间,将订单ctime推迟2-3分钟,尽量把时间错开整点数,因为后台的定时任务是间隔5分钟执行,错开后就不会把创建的订单扫描进去了。但存在问题有:1.数据很奇怪,明明是xx分创建的订单,但是却显示是延迟2-3分钟后的时间。2:体验感下降了,比如用户在某银行操作失败,由于推迟了create_time,用户需要多等2-3分钟才能操作。
- 通过redis重复确认,因为确实会存在订单不存在的现象,但为了防止上面说那个情况(用户数据还没请求到某银行就被后端的定时任务提前发送到银行这种情况),根据订单号唯一性,假如接口返回订单不存在不做修改数据库的操作,而是先存到redis,然后根据现在的时间和创建时间对比一次,看是否是首次回查,例如创建时间是20:45分,但是回查回来的是20:50分,则是首次回查,那么就把redis对应key的value设置成1,然后return。当定时任务第二次回查的时时候判断是否失败,是的话value incr成2,代表第二次回查,这样中间有5分钟的响应时间,一般来说从前端发起请求到银行那边,最多是几秒钟的,所以这种方式比刚开始直接修改数据为失败保险了很多。但第三次的时候就没必要设置value为3了,直接设置失败。
不考虑mysql存放原因:
- 之所以不存放数据库是因为考虑到后面如果有1万这样的个订单要1万次入库,这样数据压力也会变大,既要不停的insert还要对比select。
以上是暂时想出来的2个办法,不过这肯定不是最好的办法,由于个人技术和能力有限,希望能在以后的工作中通过不断实践和发现得到更多更好的办法,写下此记录用来回顾!