我写的伪代码如下,但出现了个bug,抽奖现在要限制每日抽奖结果出现的次数,但实际运行是在并发时不能限制住,如何解决?
<code>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 </code>
回复内容:
我写的伪代码如下,但出现了个bug,抽奖现在要限制每日抽奖结果出现的次数,但实际运行是在并发时不能限制住,如何解决?
<code>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 </code>
暂时能想到的是:
再增加一个表来专门记录某天某个result的发放次数,缺点是需要预先创建好期间每天每个result初始数据,并且循环里使用行锁在高并发里效率就非常低了,这是不能被接受的:
<code>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'] </code>
关于抽奖,需要考虑的点有很多,这里稍微整理了下主要需要考虑以下三点:
- 用户抽奖次数限制
- 奖品数量限制
- 奖品发放的分布
- 中奖的概率的可控性
用户抽象次数限制
一个用户必须限制抽奖的次数,而同一个用户的并发几率其实是很小的,所以这里可以用悲观锁来控制用户的抽奖次数。
奖品数量限制
因为并发修改一个奖品的数量可能性是很大的,特别是一些安慰奖,如果这里我们再用悲观锁的话,很容易造成锁超时。所以这里我选择用乐观锁来解决可能出现的并发脏读的情况。
奖品发放的分布
为了防止用脚本来刷抽奖,所以这里需要控制一下奖品发放的一个分布,中大奖需要一个时间间隔,当然这里通过代码来控制是很容易实现的(当然这里也需要考虑一下并发中到两个大奖的情况,也可以通过乐观锁来控制)
中奖的概率的可控性
当我们开始估计抽奖大概会有10W人参加,所以我在设计概率的时候是按照10w来设计的,但是突然发现活动开始一个小时候以后抽奖人数就达到了5W,这个时候就需要可以动态来调整中奖的概率了。这里最好的方式是,不要把中奖概论写死在数据库,而是通过中奖次数/参加人数
来计算出来,这样就可以方便的动态的改变中奖概率了。
关于优化
如果并发量实在是太大,导致数据库的QPS异常的高。那么可以在数据库前面加一层缓存来挡一下,把需要写进数据库的数据放入队列。当使用了这种架构架构,就需要考虑一些数据一致性的问题了,比如说
- 怎么保证数据库的数据和缓存的数据是一致的
- 如果队列挂掉了,怎么保证缓存的数据能够及时更新到数据库中。如果缓存挂掉了,怎么保证抽奖能够继续进行下去(当然这里可以进行业务妥协,如果缓存挂掉整个抽奖挂掉,如果来不及写进数据库的数据,就当做这些事情没有发生,这就会导致某些人抽奖次数超过限定次数,或者某些奖中奖次数超过了限定次数)
关于优化中我对一些异常情况的解决方法不是很了解,希望懂的朋友可以指教一下
附录(简单流程图)
先插入中奖结果 之后根据唯一主键取得中奖数量
select count(*) from table where id
数量超出返回失败 后台根据限定的中奖数取前n个即可
高并发下抽奖跟秒杀系统有些类似。
比如,中奖只是少数,大部分人并不会中奖。所以可以在第一步便限制只有少数用户的请求能够打到真正抽奖逻辑上。这样一来服务器压力就小了很多。
真正参与抽奖的用户,先随机下该用户能不能中奖,中什么奖, 如果是中了,再去查询有没有该奖(若没有奖则显示未中)。这样,又减少了一些对数据库的请求。
这样,高并发也只限于http服务器,对数据库的压力不是很大。
把中奖逻辑倒过来看,当他还没有抽奖的时候其实就相当于某个人已经中奖。
在向服务器请求的时候就已经把中奖结果在过程中分发下去,抽奖过程就一个简单的显示结果。这样就没有抽奖的压力了,非中奖用户根本没有走抽奖逻辑。压力自然就降下来了。
参考一下小米的抢手机
我觉得这个是设计思路的问题,
你应该先取当天已发奖品的数量,并且加上排它锁
<code>yyCount = select count(*) from lottery_chance where result=YY and used_time > todayDate for update </code>
这样当存在并发时,取奖品数量能保证有唯一进程在(奖品等于 YY 上)抽奖,其他进程处于等待加锁状态,直到上一次抽奖结束,然后获取锁,继续下面的操作
高并发还想着用mysql解决问题,恐怕就不太合适.
建议考虑下队列.或者将数据放在redis这类单线处理数据的功能中.
memcache不行啦,需要注意下.
reids是个好选择,可以考虑使用.或者考虑使用队列系统处理
高并发下,我觉得大概是这么几个步骤:
1、是否丢弃该抽奖请求(根据服务器的承受能力,会有一部分请求直接丢弃);
2、用户本次抽奖是否符合业务逻辑(是否有剩余抽奖次数等);
3、如果有多个奖品,那么去掉不符合规则的奖品(比如有的奖品只能中一次,而该用户已经中了一次);
4、在剩下的奖品中按照规则抽取(这里的规则可能简单也可能复杂);
5、如果抽中了某奖品,立刻尝试从奖品队列(这里可以是缓存队列,也可以是数据库记录,但要保证该操作的原子性)中拿取奖品,如果拿取成功(即奖品有剩余)则告诉用户已经中奖。
最OK的办法就是乐观锁
只要是抽奖,必然有并发问题。
涉及到利益的东西,必然有人通过工具刷请求

php把负数转为正整数的方法:1、使用abs()函数将负数转为正数,使用intval()函数对正数取整,转为正整数,语法“intval(abs($number))”;2、利用“~”位运算符将负数取反加一,语法“~$number + 1”。

实现方法:1、使用“sleep(延迟秒数)”语句,可延迟执行函数若干秒;2、使用“time_nanosleep(延迟秒数,延迟纳秒数)”语句,可延迟执行函数若干秒和纳秒;3、使用“time_sleep_until(time()+7)”语句。

php除以100保留两位小数的方法:1、利用“/”运算符进行除法运算,语法“数值 / 100”;2、使用“number_format(除法结果, 2)”或“sprintf("%.2f",除法结果)”语句进行四舍五入的处理值,并保留两位小数。

判断方法:1、使用“strtotime("年-月-日")”语句将给定的年月日转换为时间戳格式;2、用“date("z",时间戳)+1”语句计算指定时间戳是一年的第几天。date()返回的天数是从0开始计算的,因此真实天数需要在此基础上加1。

方法:1、用“str_replace(" ","其他字符",$str)”语句,可将nbsp符替换为其他字符;2、用“preg_replace("/(\s|\ \;||\xc2\xa0)/","其他字符",$str)”语句。

php判断有没有小数点的方法:1、使用“strpos(数字字符串,'.')”语法,如果返回小数点在字符串中第一次出现的位置,则有小数点;2、使用“strrpos(数字字符串,'.')”语句,如果返回小数点在字符串中最后一次出现的位置,则有。

php字符串有下标。在PHP中,下标不仅可以应用于数组和对象,还可应用于字符串,利用字符串的下标和中括号“[]”可以访问指定索引位置的字符,并对该字符进行读写,语法“字符串名[下标值]”;字符串的下标值(索引值)只能是整数类型,起始值为0。

在PHP中,可以利用implode()函数的第一个参数来设置没有分隔符,该函数的第一个参数用于规定数组元素之间放置的内容,默认是空字符串,也可将第一个参数设置为空,语法为“implode(数组)”或者“implode("",数组)”。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

DVWA
Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

SecLists
SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

Atom編輯器mac版下載
最受歡迎的的開源編輯器

ZendStudio 13.5.1 Mac
強大的PHP整合開發環境