如何设计高并发下的抽奖?
我写的伪代码如下,但出现了个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的办法就是乐观锁
只要是抽奖,必然有并发问题。
涉及到利益的东西,必然有人通过工具刷请求

熱AI工具

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

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

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

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

要安全、徹底地卸載MySQL並清理所有殘留文件,需遵循以下步驟:1.停止MySQL服務;2.卸載MySQL軟件包;3.清理配置文件和數據目錄;4.驗證卸載是否徹底。

session_start()iscucialinphpformanagingusersessions.1)ItInitiateSanewsessionifnoneexists,2)resumesanexistingsessions,and3)setsasesessionCookieforContinuityActinuityAccontinuityAcconActInityAcconActInityAcconAccRequests,EnablingApplicationsApplicationsLikeUseAppericationLikeUseAthenticationalticationaltication and PersersonalizedContentent。

在MySQL中,添加字段使用ALTERTABLEtable_nameADDCOLUMNnew_columnVARCHAR(255)AFTERexisting_column,刪除字段使用ALTERTABLEtable_nameDROPCOLUMNcolumn_to_drop。添加字段時,需指定位置以優化查詢性能和數據結構;刪除字段前需確認操作不可逆;使用在線DDL、備份數據、測試環境和低負載時間段修改表結構是性能優化和最佳實踐。

MySQL批量插入数据的高效方法包括:1.使用INSERTINTO...VALUES语法,2.利用LOADDATAINFILE命令,3.使用事务处理,4.调整批量大小,5.禁用索引,6.使用INSERTIGNORE或INSERT...ONDUPLICATEKEYUPDATE,这些方法能显著提升数据库操作效率。

MySQL函數可用於數據處理和計算。 1.基本用法包括字符串處理、日期計算和數學運算。 2.高級用法涉及結合多個函數實現複雜操作。 3.性能優化需避免在WHERE子句中使用函數,並使用GROUPBY和臨時表。

在MySQL中配置字符集和排序規則的方法包括:1.設置服務器級別的字符集和排序規則:SETNAMES'utf8';SETCHARACTERSETutf8;SETCOLLATION_CONNECTION='utf8_general_ci';2.創建使用特定字符集和排序規則的數據庫:CREATEDATABASEexample_dbCHARACTERSETutf8COLLATEutf8_general_ci;3.創建表時指定字符集和排序規則:CREATETABLEexample_table(idINT

Composer是PHP的依賴管理工具,通過composer.json文件管理項目依賴。 1)解析composer.json獲取依賴信息;2)解析依賴關係形成依賴樹;3)從Packagist下載並安裝依賴到vendor目錄;4)生成composer.lock文件鎖定依賴版本,確保團隊一致性和項目可維護性。

使用EXPLAIN命令可以分析MySQL查詢的執行計劃。 1.EXPLAIN命令顯示查詢的執行計劃,幫助找出性能瓶頸。 2.執行計劃包括id、select_type、table、type、possible_keys、key、key_len、ref、rows和Extra等字段。 3.根據執行計劃,可以通過添加索引、避免全表掃描、優化JOIN操作和使用覆蓋索引來優化查詢。
