竞争条件在django
-
06-07-2019 - |
题
这是一个简单的例子的一个django景,与一个潜在的竞争条件:
# myapp/views.py
from django.contrib.auth.models import User
from my_libs import calculate_points
def add_points(request):
user = request.user
user.points += calculate_points(user)
user.save()
竞争条件应相当明显的:用户可以使这个请求的两倍,以及应用可能执行 user = request.user
同时,引起一项请求复盖。
假设的功能 calculate_points
比较复杂,使计算基于所有种奇怪的东西,无法被安置在一个单一的 update
并将难以放在一个存储的过程。
因此,这里是我的问题:什么样的锁定机制可用来django、处理情况与此类似的?
解决方案
Django 1.4+支持 select_for_update ,在早期版本中,您可以执行原始SQL查询,例如 select ... for update
,根据底层数据库将锁定任何更新的行,您可以使用该行执行任何操作,直到事务结束。 e.g。
from django.db import transaction
@transaction.commit_manually()
def add_points(request):
user = User.objects.select_for_update().get(id=request.user.id)
# you can go back at this point if something is not right
if user.points > 1000:
# too many points
return
user.points += calculate_points(user)
user.save()
transaction.commit()
其他提示
从Django 1.1开始,您可以使用ORM的F()表达式来解决这个特定问题。
from django.db.models import F
user = request.user
user.points = F('points') + calculate_points(user)
user.save()
有关详细信息,请参阅文档:
https://docs.djangoproject的.com / EN / 1.8 / REF /模型/表达式/#django.db.models.F
数据库锁定是这里的方法。有计划添加“select for update”;支持Django(此处),但目前最简单的方法是使用原始SQL进行更新在开始计算分数之前的用户对象。
当底层数据库(如Postgres)支持它时,Django 1.4的ORM现在支持悲观锁定。请参阅 Django 1.4a1发行说明。
你有很多方法来单线这样的事情。
一个标准的办法是 更新的第一个.你做的更新,这将抓住一个独特锁行;然后做你的工作;最后提交的变化。对于这项工作,你需要旁的奥姆的缓存。
另一个标准做法是要有一个独立的、单程应用程序服务器将网交易的复杂的计算。
你的网络应用程序可以创建一个队的得分要求,产生一个单独的进程,然后编写的评分要求这队列中。在产卵可以放在Django
urls.py
那么它发生于网络的应用程序的启动。或者,它可以放入独立的manage.py
管理脚本。或者这是可以做到"为所需要的"当第一个得分的请求是企图。你还可以创建一个独立的过味网服务器使用,其接受WS请求通过urllib2.如果你有一个单一的口号用于这种服务器,请求进行排队的TCP/IP。如果你过处理程序具有一个线程,然后,你已经实现化单一的线程。这个稍微更多的可扩缩的,因为分动机是WS请求,并且可以运行。
然而另一种做法是有一些其他资源,必须获得并保持做的计算。
一个单独目的数据库。一个单列在一个独特的表可以更新一届会议ID抓住的控制;更新与session ID
None
释放控制。必要的更新以包含一个WHERE SESSION_ID IS NONE
过滤,以确保更新失败的时候锁举行的其他人。这是有趣,因为它是固有的种族免费--这是一个单一的更新--不是一个选择更新序列。一个花园-各种信号可以使用外部数据库。排队(一般)工作更容易比一个低级别的信号。
这可能会使您的情况过于简单,但仅仅更换JavaScript链接呢?换句话说,当用户点击链接或按钮将请求包装在JavaScript函数中时,该函数立即禁用/“灰化”。链接并用“正在加载...”替换文本。或“提交请求......”信息或类似的东西。这对你有用吗?
现在,你必须使用:
Model.objects.select_for_update().get(foo=bar)