最近一直在忙,只能抽空周末把代码撸了出来。周一才来写这篇文章。代码有点缭乱,没时间整理,如果有误还请留言斧正。现在进入正题。
一、思路
1.奖品:
奖品分为奖品id(编号)、count(数量)、pointVal(价值)、remainCount (剩余数量)分为四个参数组成。
2.概率规则:
单个产品剩余数量/总产品剩余数量=单个产品概率*1000,这也就意味着每个奖品的概率都是随着数量变化的,这时候计算概率对数据在时间维度要求必须一致(数据快照)。
每个产品概率相加=1000,如果没有等于1*概率相乘的数说明数据快照有问题(我这里是乘1000所以等于1000)。
3.设计思路:
这个demo,我用的是取之于民用之于民的想法写的,保证每一个人都会中奖。是不是很开心???。首先我们得有甲乙双发,甲方就是抽奖系统的提供方,乙方就是我们这些抽奖的小百姓。甲方会先提供奖品出来,我这里是一,二,三,四奖项,有一个默认五等奖奖项(demo里面有一,二,三,四,五个奖项)。我会先计算甲方提供每个奖品的数量、价值然后计算所有奖品的总价值,然后用 总价值/每次抽奖的分数=总抽奖次数。总抽奖次数 - 每个奖品的 = 默认五等奖次数。这样我们就算出了所有奖品的数量(包括默认五等奖也就是安慰奖的次数)。没当抽奖总次数==0的时候就会自动轮询补充库存开始新的一轮抽奖。
中奖公式 :
总抽奖消耗积分上限值/每次抽奖消耗固定积分 = 总抽奖次数
总抽奖次数 - 已经抽奖数量 = 剩余抽奖次数
奖品类型 * 奖品数量 = 奖品总数量
奖品总数量 - 已中奖数量 = 剩余奖品数量
剩余抽奖次数/剩余奖品数量 = 剩余奖品中奖概率
4.降级问题:
这个一开始我也考虑过使用降级处理,以防服务器并发过大GG。不过我考虑到跟我的设计思路不符合,会影响到概率的公平性,我就把那部分去掉了(如果你们项目需要可以自己进行降级,限流,不过这会损失一部分概率的公平性,直接导致的结果就真正的奖品往往都是在最后面出现)。
5.注意事项:
特别强调一点,每次轮询的时候判断数量一定要用 == 不用用 <= 周末吃过这个亏,结果看轮询数据快照的时候偶尔来个 -1,特蛋疼。花了很多时间排除问题。总结一点就是涉及的数量红线变更的必须用精确判断,不能范围判断。
6.技术难点:
一、概率的计算
二、库存变更
三、轮询策略
基本就上面三个点,我这里是灵活使用redis 做缓存共享(也可以使用db的悲观锁、乐观锁、版本号),使用了其中管道技术做时间维度上的奖品数据快照解决概率计算的正确性;事物技术解决库存变更;两者结合解决了轮询策略问题。做到了监控每个轮询前每一个奖品数量,精确到每个轮询每个用户所中的奖品和顺序。
二、代码干货
奖品类:
public class Award {
/**编号*/
public String id;
/**数量(该类奖品数量)*/
public int count;
/**价值(该类奖品价值积分)*/
public int pointVal;
/**剩余数量(该类奖品剩余数量)*/
public int remainCount;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public int getPointVal() {
return pointVal;
}
public void setPointVal(int pointVal) {
this.pointVal = pointVal;
}
public int getRemainCount() {
return remainCount;
}
public void setRemainCount(int remainCount) {
this.remainCount = remainCount;
}
/**