因为公司业务调整,最近接手了公司的广告系统的业务,其中遇到这样的一个Bug,它的表象是这样:广告主账户里面没钱了,但是广告依然给他投放了出来。
一、基本业务逻辑
首先广告的计费方式有几种,CPA,CPT,CPC,CPM等,而CPM这种计费方式是按每1000次曝光收费x元,也就说如果单价为10元,那么单次曝光就是0.01元,每次曝光我们首先都会从广告系统的缓存余额里面扣对应的钱,扣完钱,会给账户系统发送一条事物消息,然后执行本地事物,将扣款订单落库,事物提交,然后账户系统那边也会在缓存里面记录扣款金额,如果账户那边扣款失败,也会发送一条扣款失败的消息,广告系统收到此消息后,会将余额补回进自己的缓存里面。
这里有个不太理解的点:
为什么广告这边都已经曝光出去了,但是调用账户系统扣款失败,就要把用户的余额给补回去,好比商户都把商品卖出去了,但是用户支付的时候扣钱扣失败了,就又给用户把余额补回去了。
二、Bug点
bug就在扣款失败,补回用户余额这里,因在再给账户系统发送CPM一类广告消息的时候,会将精度提升3,也就是单个曝光金额会 * 1000 发送给账户系统,而账户系统在扣款失败的时候,原封不动的将 * 1000过后的单价发送过来,并补回广告系统的用户余额里面。这里我举个例子:
假设用户余额1000,单个曝光2元(贵了哈~),单次曝光之后,用户余额应该是998元,但是账户系统扣款失败,会将用户余额补回,由于 * 1000,提升3精度,补回的余额就变成了 998 + 2 * 1000 = 2998
拼多多也不带怎么玩的啊。。。
这就造成了广告主账户里面没钱了,但是广告依然给他投放了出来。因为广告系统用户缓存余额远大于账户系统的余额,当缓存余额小于0时会触发一个广告下架的操作,这样就一直得不到触发,并且由于账户没钱了,广告系统一直收到扣款失败的消息,疯狂继续补余额。。。
三、修复措施
1、精度处理:联系账户系统的同学,将消息里面增加一个精度scale字段,如scale = 3,对应将amout / 1000
2、对扣款失败消息为账户余额不足一类的消息,不做补回余额操作
然后发现代码有个这样的注释,那我就好人做到底了,顺带做了
//伪代码
Long setnx = redis.setnx(业务Key+唯一消息id,"1"); redis.expire(业务Key+唯一消息id,x); if(setnx == 0) return;