如何设计高并发下的抽奖?
我写的伪代码如下,但出现了个bug,抽奖现在要限制每日抽奖结果出现的次数,但实际运行是在并发时不能限制住,如何解决?
resultDayLimitTimes = {
resultA => 2 # 每天最多出现2次
resultB => 5 # 每天最多出现5次
resultC => 20 # 每天最多出现20次
resultD => Infinite # 每天出现次数没有限制
}
Begin transaction
select * from lottery_chance where id =XX and result = null limit 1 for update
#bug 就在下面这个循环里,如果resultA今天已经出现过一次了,
#然后有2个人(这2人的XX是不同的,所以前面的for update对
#于这种并发不能限制,前面的for update是用来防止同一次抽奖机会被并发时使用多次的)
#同时抽到YY=resultA,由于事务还未提
#交那么yyCount都是1,小于每日限制2,于是跳出循环,这2人
#都中了resultA,这时当天出现了3个resultA 超出2个限制,
#我应该怎么写才能解决这个问题?
while true {
YY = randomIn [resultA,resultB,resultC,resultD]
yyCount = select count(*) from lottery_chance where result=YY and used_time > todayDate
if yyCount < resultDayLimitTimes[YY] {
break
}
}
update lottery_chance set result=YY, used_time = now where id =XX
Commit
小榔头儿丶
10 years, 4 months ago
Answers
暂时能想到的是:
再增加一个表来专门记录某天某个result的发放次数,缺点是需要预先创建好期间每天每个result初始数据,并且循环里使用行锁在高并发里效率就非常低了,这是不能被接受的:<
resultDayLimitTimes = {
resultA => 2 # 每天最多出现2次
resultB => 5 # 每天最多出现5次
resultC => 20 # 每天最多出现20次
resultD => Infinite # 每天出现次数没有限制
}
Begin transaction
select * from lottery_chance where id =XX and result = null limit 1 for update
todayDate = now.Date
while true {
YY = randomExcludeBeforeIn [resultA,resultB,resultC,resultD]
dayResultTimes = select * from result_day_times where date=todayDate and result=YY limit 1 for update
if dayResultTimes['times'] < resultDayLimitTimes[YY] {
break;
}
}
update result_day_times set times=times+1 where date=todayDate and result=YY
update lottery_chance set result=YY, used_time = now where id =XX
Commit
秋天的晚枫叶
answered 10 years, 4 months ago
关于抽奖,需要考虑的点有很多,这里稍微整理了下主要需要考虑以下三点:
- 用户抽奖次数限制
- 奖品数量限制
- 奖品发放的分布
- 中奖的概率的可控性
用户抽象次数限制
一个用户必须限制抽奖的次数,而同一个用户的并发几率其实是很小的,所以这里可以用悲观锁来控制用户的抽奖次数。
奖品数量限制
因为并发修改一个奖品的数量可能性是很大的,特别是一些安慰奖,如果这里我们再用悲观锁的话,很容易造成锁超时。所以这里我选择用乐观锁来解决可能出现的并发脏读的情况。
奖品发放的分布
为了防止用脚本来刷抽奖,所以这里需要控制一下奖品发放的一个分布,中大奖需要一个时间间隔,当然这里通过代码来控制是很容易实现的(当然这里也需要考虑一下并发中到两个大奖的情况,也可以通过乐观锁来控制)
中奖的概率的可控性
当我们开始估计抽奖大概会有10W人参加,所以我在设计概率的时候是按照10w来设计的,但是突然发现活动开始一个小时候以后抽奖人数就达到了5W,这个时候就需要可以动态来调整中奖的概率了。这里最好的方式是,不要把中奖概论写死在数据库,而是通过
中奖次数/参加人数
来计算出来,这样就可以方便的动态的改变中奖概率了。
关于优化
如果并发量实在是太大,导致数据库的QPS异常的高。那么可以在数据库前面加一层缓存来挡一下,把需要写进数据库的数据放入队列。当使用了这种架构架构,就需要考虑一些数据一致性的问题了,比如说
- 怎么保证数据库的数据和缓存的数据是一致的
- 如果队列挂掉了,怎么保证缓存的数据能够及时更新到数据库中。如果缓存挂掉了,怎么保证抽奖能够继续进行下去(当然这里可以进行业务妥协,如果缓存挂掉整个抽奖挂掉,如果来不及写进数据库的数据,就当做这些事情没有发生,这就会导致某些人抽奖次数超过限定次数,或者某些奖中奖次数超过了限定次数)
关于优化中我对一些异常情况的解决方法不是很了解,希望懂的朋友可以指教一下
附录(简单流程图)
9v9v9
answered 10 years, 4 months ago