我们有一个Django应用程序,它在DB中创建一个存根对象,带有一些基本的默认和初始信息,然后将消息丢弃到Rabbit MQ上,以执行填充此存根对象的数据所需的IO繁重工作。我们有一个单独的兔子MQ使用者,它作为命令行脚本运行(由daemontools包装 - 所以它不会发生故障),它处理来自Rabbit MQ的消息。
所以这就是发生的事情 - 有时,当消息回来时(此消息具有存根对象ID)并且我们尝试使用StubObject.objects.get(pk = message ['ID'])读取此对象,我们得到匹配查询不存在异常(该消息通常需要大约30秒才能返回)。但是当我们检查数据库时,我们得到的查询不存在异常时,对象的数据肯定存在。这种情况在我们处理消息的大约5次中发生一次。我们不知道为什么会发生这种情况并试图解决这个问题。特别奇怪的是,它有时只会失败。有什么想法吗?
我们尝试打印connection.queries,但没有打印输出。
正在尝试保存的兔子MQ消费者在标准的django python文件之外运行,因此我们需要导入一堆东西以使其能够访问ORM(不确定问题是否可能与我们如何执行此操作有关) )。代码如下:
#!/usr/bin/env python
import sys
import os
sys.path.append('/myproject')
os.environ['DJANGO_SETTINGS_MODULE'] = 'myproject.settings'
from django.db import connection
from django.core import serializers
from django.core.management import setup_environ
from register_obj.models import Obj
from myproject import settings
import datetime
import json
import pika
setup_environ(settings)
conn = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = conn.channel()
print '[*] Waiting for responses. To exit press CTRL+C'
def callback(ch, method, properties, body):
print "[x] Received %r" % (body,)
objDict = json.loads(body)
print objDict
try:
obj = Obj.objects.get(pk=objDict["id"])
print connection.queries
except Exception, e:
print datetime.datetime.now()
print "Error occurred, could not find obj by ID - "
print objDict["id"]
print e
print '################Query##############'
print connection.queries
obj.updateDict(objDict)
# Save the obj data
obj.save()
channel.basic_consume(callback,
queue='myQueue',
no_ack=True)
channel.start_consuming()
答案 0 :(得分:1)
通常,这是由于您的Django应用程序提交事务与队列使用者之间的竞争条件。
最有可能的是,您正在运行Django应用程序设置以自动将每个请求包装在事务中。即,它开始一个事务,执行所有数据库操作,然后在处理请求并将其发送回浏览器后提交(或者如果有任何引发异常则回滚)。在那里你创建你的存根对象,然后用该存根的id将任务放在队列中。当队列为空时会出现问题,因此您的消费者会立即获得任务。与此同时,Django方面有一点延迟,而它正在完成其余的请求(即使只是将数据发送到浏览器并关闭连接可能需要一段时间)并且交易仍然是开放的。在事务关闭之前,数据库中该对象的行将不可用于其他事务,例如您的使用者。
解决方案是切换到将任务放入队列并在将其放入队列之前提交的视图的手动事务处理。
它看起来像这样:
from django.django.db import transaction
@transaction.commit_manually
def some_view(request):
try:
# do some work...
stub = Obj.objects.create(...)
except:
transaction.rollback()
raise
else:
transaction.commit()
add_task_to_queue(obj_id=stub.id)
# finish serving request
...
当然,一旦你手动处理交易,你需要非常小心,你总是提交或回滚你打开的任何交易。