在PHP中使用innoDB表和mysqli包装器进行查询。
我们当前遇到一个问题,就是每秒请求1500次相同脚本的流量激增。
一种情况是,访问脚本的前X个用户获得了奖励。
奖品是“奖品”表上的一条记录,该表中有#个已声明的编号和#个已分配的编号。
一旦使用的金额> =分配的金额,我们将停止奖励。
正在发生的事情是,在脚本的其他实例可以更新该行之前,对该脚本的许多请求正在同时读取该行,从而向他们显示仍然还有#个奖品需要领取。这导致我们奖励的金额超过了所定金额。
关于如何规避此事的任何想法?
答案 0 :(得分:1)
对,您描述的是经典比赛条件。
一种解决方案是在更新之前使用SELECT...FOR UPDATE
在奖品表中的行上建立锁。 InnoDB将建立对锁的请求顺序,使每个请求等待其轮换,直到它可以获取锁为止。
但是,这不是一个好的解决方案,因为它将导致每个用户的浏览器旋转并旋转,等待响应。在服务器上,您将迅速获得每秒1500个锁定请求的排队。即使每个会话仅花费10毫秒来完成SELECT FOR UPDATE
和随后的UPDATE
(这已经非常雄心勃勃),每秒钟要做的工作仍然是15.0秒。到第二秒,您将有30.0秒的工作要做。
与此同时,用户正在查看浏览器的挂起状态,直到转身过来。这几乎是一项设计的突破。
基本上,您需要一些解决方案来建立请求的顺序,即:
您可以让每个并发请求使用AUTO_INCREMENT键对表进行INSERT,这将保证其顺序。然后,一旦有X行,随后的请求就不会再插入任何行了。
另一种方法是使用消息队列。每个请求只是将自己的请求推入队列。然后,一个消费者从队列中拉出前X个请求,并向他们奖励。队列中的其余请求将被转储,并且没有获得奖励。
答案 1 :(得分:1)
虽然您不提供代码,表结构或更深入的数据库信息,但是在这些情况下的首要建议是使用LOCK
如果表是innoDB,则可以从行级锁定中受益,尽管如果表只有一行,则无关紧要。
使用伪代码,您需要在每次命中时
LOCK TABLE prizes
SELECT claimed, alloted FROM prizes
if claimed < alloted award prize
UPDATE prizes set claimed = claimed +1
else
do_nothing
UNLOCK TABLE prizes
<<after unlocking>>
if the user got an award, do whatever you need to do to award the prize which is not "inventory-sensitive" and can be done asynchronously
此操作的运行时间将以毫秒为单位,因此,如果您的数据库服务器涂满油脂,则所有命中都排队就不会成为问题,尽管您可能会遇到应用程序的进程限制或连接限制服务器,因此需要进行一些压力测试。
这可能是棘手的,复杂的,挑剔的...
更简单的方法是:
设置一个claim_attempt
表,其中包含一个自动递增的主键和一个引用有关用户的字段。
在每个匹配项上,插入一条记录(无论可用库存如何)并检索插入的行的ID。 然后,将检索到的ID与分配的奖励号进行比较。如果id <= alloted,则运行任何需要运行的过程才能将奖品提供给用户。如果id>已分配,则打印“下次重试”消息
答案 2 :(得分:0)
解决此问题的一种方法是拥有一个包含各个奖项记录的表格,并尝试通过以下查询来“认领”其中一个:
UPDATE prizes SET claimed_by=? WHERE prize_type=? AND claimed_by IS NULL LIMIT 1
如果您对UNIQUE
和prize_type
施加claimed_by
约束,那么这意味着没有人可以要求给定类型的奖品超过一个。这是在数据库级别强制执行的,不能被计时问题所规避。
调用该更新时,将修改零行或一行。检查结果的更新行数,以查看索赔是否成功。