关于助力红包随机分配思考总结
业务开发中涉及这样一个项目,用户要经过n次拆红包才能获得m元的红包,同时运营可能会设置前x次总共获得y元的红包(y<m),也可能第z次可能获得一个设定额度t的惊喜红包。在这个过程中涉及x,z三个重要的个变量。
设计的小细节:
设计过程红包的分配可以在拆逐次的时候生成,也可以预先分配好。方案一不好的地方是在拆的时候生成,可能存在并发的问题,每次还得加锁保证数据的一致性,另外还是记录用户到底第几次拆红包、是否惊喜红包等情况,这样子设计有些复杂。最终选择第二套分配在用户第一次拆红包的时候分配好红包写入redis队列,仅在第一次拆红包保证针对单个用户不出现并发就ok了,之后每一次已队列的数据为准。当然对于redis丢失数据的可能性,我们可以另外开一个检测脚本对齐进行二次分配,为了降低复杂度,也可以仅仅考虑惊喜红包进行分配。
逻辑实现难点:
当时第一个想法是要考虑是否设定惊喜轮z,是否设定了前x次,另外要考虑惊喜轮z是在x次之前还是x次之后,在这样总共会有以下的情况:
一般红包随机分配会有随机、二倍均值随机等算法,这一层随便在网上搜一下就可以解决。项目中选择了二倍均值法,二倍均值法的好次是每次红包不会太大或大小,较为均匀分配且有一定的随机性。
1、未设置x和z,最简单按照二倍均值解决。
2、设置了x,未设置z,也比较简单,我们一分为2采用二倍均值就ok了。
3、未设置x,设置了z,也比较简单,我们除了惊喜轮返回t元,其他的还是二倍均值就ok了。
4、设置了x和z,这种情况考虑z>x还是z<x,又或者z=x,总共会有3个情况要考虑。
想想如果按照这样的思维编码,最终代码会一团乱麻,逻辑难以实现,出现bug也较难调试。我的想法是复杂情况应该是可以简单化处理的,但是一时间也没有想出好的方案,只能先实现测试看是否出现想不到的点,自测过程发现深深的埋进去了,真的一团乱麻。但是最后坚持不懈1天还是想出来了原来自己傻了。其实整个算法压根不用考虑是否有x次的存在,惊喜红包z轮才是特殊的点。我们可以这样子如果没有设置前x次,其实所表达的意思是前0次或者0元红包,或者说是前n次(总共n次)或者m元红包。所以1、3的情况排除了,惊喜红包也一样可以这么考虑,未设置惊喜红包就是惊喜红包在第0次获得0元,所以第2种情况也是不用考虑。我们只要做好变量的初始化就ok了,x和z的初始值都是0,y和t的初始值也是0。从这里衍生出来的变量的声明有点重要,它们能辅助我们思考,简化逻辑。最后算法就简化为两次二倍均值。
var prePart, sufPart []int limitMoney := actRule.LimitMoney limitCount := actRule.LimitCount surpriseTime := actRule.SurpriseTime surpriseMoney := actRule.SurpriseMoney previousTimes := actRule.PreviousTimes previousMoney := actRule.PreviousMoney if previousTimes >= surpriseTime { if surpriseTime > 0 && surpriseTime!= previousTimes{ previousTimes -= 1 } prePart = redPackage(previousTimes, previousMoney-surpriseMoney) sufPart = redPackage(limitCount-actRule.PreviousTimes, limitMoney-previousMoney) } else { if surpriseTime > 0 { limitCount -= 1 } prePart = redPackage(previousTimes, previousMoney) sufPart = redPackage(limitCount-previousTimes, limitMoney-previousMoney-surpriseMoney) }
后记:当然对于redis丢失数据的可能性,我们可以另外开一个检测脚本对齐进行二次分配,为了降低复杂度,也可以仅仅考虑惊喜红包进行分配。关于这一点如果在业务允许的时候我们可以考虑在活动开始之前先生成10-100份左右的随机数列,存入redis然后随机读取一份也是一种方案,但这种方案我们需要考虑在一定时间段禁止运营修改或者修改活动规则后重新生成。