微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

python-避免竞争条件,Django Heroku PostgreSQL

我正在运行一个比赛网站,您尝试在该网站上点击X号以赢得奖金.它是用Django编写的,并在Postgresql的Heroku上运行.
每次点击都保存为Play模型的实例,该实例通过查看数据库中之前有多少次播放来计算其编号,并加1.此数字保存在Play模型中.这对整个站点至关重要,因为您打什么号码决定您是否获得奖金.

最近,我们有一个案例,其中有2个人同时获得中奖号码.查看数据库,我发现实际上大约有3%的戏剧分享他们的数字.哎呀.
我在Play模型的“数字”和“游戏”字段中添加了“ unique_together”,因此数据库将帮助我避免将来出现重复的数字,但担心未来的比赛条件可能会使系统跳过某些数字,这会如果所讨论的数字在哪里赢了,那就不好了.

我已经研究过锁定表,但是担心这可能会破坏站点的并发性(我们目前有多达500个同时播放器,并且将来还会更多).

我也应该采取什么策略才能100%确保自己从未重复或跳过数字?

我的游戏课:

class Play(models.Model):
    token = models.CharField(unique=True, max_length=200)
    user = models.ForeignKey(User)
    game = models.ForeignKey(Game)
    datetime = models.DateTimeField(auto_Now_add=True)
    winner = models.BooleanField(default=False)
    flagged = models.BooleanField(default=False)
    number = models.IntegerField(blank=True, null=True)
    ip = models.CharField(max_length=200, blank=True, null=True)

    def assign_number(self, x=1000):
        #try to assign number up to 1000 times, in case of race condition
        while x > 0:
            before = Play.objects.filter(id__lt=self.id, game=self.game)
            try:
                self.number = before.count()+1
                self.save()
                x=0
            except:
                x-=1

    class Meta:
        unique_together = (('user', 'game', 'datetime'), ('game','number'))

解决方法:

一个简单的解决方案是将柜台用户和获胜者用户置于游戏模型中.然后,您可以使用select_for_update锁定记录:

game = Game.objects.select_for_update().get(pk=gamepk)
if game.number + 1 == X
    # he is a winner
    game.winner = request.user
    game.number = game.number + 1
    game.save()

else:
    # u might need to stop the game if a winner already decided

作为同一笔交易的一部分,您还可以记录Player的对象,以便您也知道单击了谁并跟踪其他信息,但不要在其中放置数字和获胜者.要使用select_for_update,您需要使用postgresql_psycopg2后端.

更新:
由于django认情况下将autocommit设置为on,因此您必须将以上代码包装在原子事务中.从Django docs

Select for update
If you were relying on “automatic transactions” to provide locking between select_for_update() and a subsequent >write operation — an extremely fragile design, but nonetheless possible —
you must wrap the relevant code in atomic().

您可以使用@ transaction.atomic装饰视图:

from django.db import transaction

@transaction.atomic
def viewfunc(request):
    # This code executes inside a transaction.
    do_stuff()

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐