10

Django我们使用& mongoDBPyMongo驱动程序)开发了一个 REST API 。问题是,在对 API 端点的某些请求中,PyMongo游标返回的部分响应包含的文档比它应该包含的要少(但它是一个完全有效的 JSON 文档)。

让我用一个我们的观点的例子来解释它:

def get_data(key):
    return collection.find({'key': key}, limit=24)

def my_view(request):
    key = request.POST.get('key')
    query = get_data(key)
    res = [app for app in query]
    return JsonResponse({'list': res})

我们确信有超过 8000 个文档与查询匹配,但在某些调用中,我们得到的结果少于 24 个(甚至为零)。我们调查的第一个问题是我们的代码中有多个MongoClient定义。通过解决这个问题,问题的发生次数减少了,但我们仍然在很多电话中遇到了这个问题。

在所有这些调查之后,我们设计了一个测试,我们同时向服务器发出 16 个异步请求。通过这种方法,我们可以重现问题。在这 16 个请求中,有 6-8 个请求有部分结果。运行此测试后,我们将uWsgi进程数减少到 6 个并重新启动服务器。所有结果都很好,但在服务器上再次施加重负载后,问题又开始了。此时,我们重新启动了 uwsgi 服务,一切正常。通过最后一个实验,我们现在有了一个线索,当 uwsgi 服务开始运行时,一切正常,但经过一段时间和高负载后,服务器又开始返回部分或空的结果。我们最近的调查是使用python manage.pywith运行 APIDEBUG=False,在这种情况下一段时间后我们又遇到了问题。

我们无法弄清楚问题是什么以及如何解决它。我们能想到的一个原因是 Django 在完成之前关闭了 pymongo 的连接。因为返回的结果是一个有效的 JSON。

我们的堆栈是:

  • nginx(未启用缓存)
  • uWsgi
  • MemCached(在调试过程中禁用)
  • Django(python 3 上的 v1.8)
  • PyMongo (v3.0.3)

非常感谢您的帮助。

更新:

蒙哥版本:

db version v3.0.7
git version: 6ce7cbe8c6b899552dadd907604559806aa2e9bd
  • 我们正在运行单个mongod实例。没有分片/复制。
  • 我们正在使用此代码段创建连接:

    con = MongoClient('localhost', 27017)

更新 2

Pymongo 问题跟踪器中的主题线程

4

2 回答 2

1

Pymongo 游标不是线程安全的元素。因此,像我在多线程环境中所做的那样使用它们会导致我所描述的问题。另一方面,Python 的list操作大多是线程安全的,像这样更改代码段将解决问题:

def get_data(key):
    return list(collection.find({'key': key}, limit=24))

def my_view(request):
    key = request.POST.get('key')
    query = get_data(key)
    res = [app for app in query]
    return JsonResponse({'list': res})
于 2015-11-02T06:59:09.690 回答
0

我非常推测的猜测是您在代码中的某处重用了游标。确保您在视图堆栈本身内初始化您的集合,而不是在它之外。

例如,如所写,如果您正在执行以下操作:

import ...
import con

collection = con.documents
# blah blah code
def my_view(request):
    key = request.POST.get('key')
    query = collection.find({'key': key}, limit=24)
    res = [app for app in query]
    return JsonResponse({'list': res})

你可以结束我们重用游标。最好做类似的事情

import ...
import con

# blah blah code
def my_view(request):
    collection = con.documents
    key = request.POST.get('key')
    query = collection.find({'key': key}, limit=24)
    res = [app for app in query]
    return JsonResponse({'list': res})

应提问者的澄清要求进行编辑:

您需要在视图堆栈中定义集合而不是在文件加载时定义集合的原因是集合变量有一个游标,这基本上是数据库和您的应用程序相互通信的方式。除了一堆其他东西之外,光标还可以跟踪您在一长串数据中的位置,但这是重要的部分。

当您在视图方法之外创建集合游标时,如果存在,它将在每个请求上重新使用游标。所以,如果你发出一个请求,然后又非常非常快地发出另一个请求(就像你应用高负载时发生的那样),游标可能只完成了与数据库对话的一半,所以你的一些数据会转到第一个请求,还有一些请求第二个。请求中没有数据的原因是游标完成获取数据但尚未关闭,因此下一个请求尝试从游标中获取数据,并且查询中没有任何数据可供获取。

通过将集合定义(以及关联的游标定义)移动到视图堆栈中,您将在处理新请求时始终获得新游标。您的光标和不同请求之间不会有任何交叉对话,因为每个请求周期都有自己的。

于 2015-10-30T15:46:46.953 回答