php - 如何设计高并发下的抽奖?
天蓬老师
天蓬老师 2017-04-10 15:52:34
[PHP讨论组]

我写的伪代码如下,但出现了个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

天蓬老师
天蓬老师

欢迎选择我的课程,让我们一起见证您的进步~~

全部回复(7)
迷茫

暂时能想到的是:
再增加一个表来专门记录某天某个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

PHPz

先插入中奖结果 之后根据唯一主键取得中奖数量

select count(*) from table where id < [新插入id]

数量超出返回失败 后台根据限定的中奖数取前n个即可

阿神

把中奖逻辑倒过来看,当他还没有抽奖的时候其实就相当于某个人已经中奖。

在向服务器请求的时候就已经把中奖结果在过程中分发下去,抽奖过程就一个简单的显示结果。这样就没有抽奖的压力了,非中奖用户根本没有走抽奖逻辑。压力自然就降下来了。

参考一下小米的抢手机

PHP中文网

我觉得这个是设计思路的问题,
你应该先取当天已发奖品的数量,并且加上排它锁

yyCount =  select count(*) from lottery_chance where result=YY and used_time > todayDate for update

这样当存在并发时,取奖品数量能保证有唯一进程在(奖品等于 YY 上)抽奖,其他进程处于等待加锁状态,直到上一次抽奖结束,然后获取锁,继续下面的操作

大家讲道理

高并发还想着用mysql解决问题,恐怕就不太合适.
建议考虑下队列.或者将数据放在redis这类单线处理数据的功能中.
memcache不行啦,需要注意下.
reids是个好选择,可以考虑使用.或者考虑使用队列系统处理

高洛峰

高并发下,我觉得大概是这么几个步骤:
1、是否丢弃该抽奖请求(根据服务器的承受能力,会有一部分请求直接丢弃);
2、用户本次抽奖是否符合业务逻辑(是否有剩余抽奖次数等);
3、如果有多个奖品,那么去掉不符合规则的奖品(比如有的奖品只能中一次,而该用户已经中了一次);
4、在剩下的奖品中按照规则抽取(这里的规则可能简单也可能复杂);
5、如果抽中了某奖品,立刻尝试从奖品队列(这里可以是缓存队列,也可以是数据库记录,但要保证该操作的原子性)中拿取奖品,如果拿取成功(即奖品有剩余)则告诉用户已经中奖。

天蓬老师

最OK的办法就是乐观锁
只要是抽奖,必然有并发问题。
涉及到利益的东西,必然有人通过工具刷请求

热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号