Django - 解决生产中 DRF ViewSets 的线程局部变量和中间件问题
Django - Troubleshooting thread locals & middleware issue with DRF ViewSets in Production
有人对解决这个具有挑战性的问题有建议吗?不确定如何进一步解决此问题。
我正在使用以下方法在中间件的线程局部变量中设置一个值:
from threading import local
ORG_ATTR_NAME = getattr(settings, "LOCAL_ORG_ATTR_NAME", "_current_org")
_thread_locals = local()
def _do_set_current_variable(variable_fun, attr_name):
setattr(_thread_locals, attr_name, variable_fun.__get__(variable_fun, local))
def thread_local_middleware(get_response):
def middleware(request):
organization = Organization.objects.get_current(request)
_do_set_current_variable(lambda self: organization, ORG_ATTR_NAME)
response = get_response(request)
return response
return middleware
def get_current_variable(attr_name: str):
"""Given an attr_name, returns the object or callable if it exists in the local thread"""
current_variable = getattr(_thread_locals, attr_name, None)
if callable(current_variable):
return current_variable()
return current_variable
我有一个管理器 class,所有包含 organization
FK 字段的模型都使用:
class OrganizationAwareManager(models.Manager):
def get_queryset(self):
logger.debug(f"_thread_locals attributes in manager: {_thread_locals.__dict__}")
organization = get_current_variable(ORG_ATTR_NAME)
queryset = super().get_queryset().filter(organization=organization)
logger.debug(f"OrganizationAwareManager queryset: {queryset.query}")
return queryset
这一切在开发中都非常有效,并且在生产中的几乎所有方面都非常有效 - 除了在使用 OrganizationAwareManager
查询模型的 DRF ViewSets 中。其他 ViewSet 按预期工作,并且在视图上下文中引用具有 OrganizationAwareManager
的模型的普通 django 视图也可以正常工作。从字面上看,生产中 OrganizationAwareManager
的 DRF 视图集是唯一的问题。
如您所见,我在管理器中添加了日志记录以检查 _thread_locals
上设置了哪些属性。
在开发中我得到类似的东西:_thread_locals attributes in manager: {'_current_org': <bound method thread_local_middleware.<locals>.middleware.<locals>.<lambda> of <function thread_local_middleware.<locals>.middleware.<locals>.<lambda> at 0x7f1820264a60>>}
在产品中,似乎没有任何设置:_thread_locals attributes in manager: {}
关于 DRF 如何处理请求,我可能遗漏了什么吗?它应该是 运行 通过中间件堆栈并设置我的属性,无论我们是在开发还是生产,对吧?我似乎找不到可能解释这一点的两种环境之间的任何差异。两者非常相似,使用 docker-compose 和几乎相同的容器。我在 prod 环境中尝试了 gunicorn 和 gunicorn+uvicorn,症状没有区别。
我已经尝试解决这个问题 3 天了,但 运行 没有想法,所以解决这个问题的建议 非常 不胜感激。
真奇怪。一段时间后,我有了解决问题的想法。
仍然不完全确定根本原因(很想知道为什么它在开发中工作得很好,但在进行以下更改之前在生产中却没有),但似乎在 ViewSets 中定义了查询集class 本身,当线程开始时对查询求值?
我在我的日志中注意到,当我启动服务器时,我从 OrganizationAwareManager
得到了一大堆日志条目,说 _thread_locals
没有关联的属性。这些日志条目的数量似乎与我项目中使用 OrganizationAwareManager
的 ViewSet 的数量大致相同。他们都在系统启动时进行了初始评估,orgnization=None
,因此将丢弃对 organization
的任何进一步过滤。
像下面这样的 ViewSets 最终没有被 organization
正确过滤:
class AssetTypeViewSet(viewsets.ModelViewSet):
queryset = AssetType.objects.all()
serializer_class = AssetTypeSerializer
当我修改以在 get_queryset()
中定义查询集以便在执行 ViewSet 时对其进行评估,现在一切正常:
class AssetTypeViewSet(viewsets.ModelViewSet):
queryset = AssetType.objects.none()
serializer_class = AssetTypeSerializer
def get_queryset(self):
return AssetType.objects.all()
奇怪。
有人对解决这个具有挑战性的问题有建议吗?不确定如何进一步解决此问题。
我正在使用以下方法在中间件的线程局部变量中设置一个值:
from threading import local
ORG_ATTR_NAME = getattr(settings, "LOCAL_ORG_ATTR_NAME", "_current_org")
_thread_locals = local()
def _do_set_current_variable(variable_fun, attr_name):
setattr(_thread_locals, attr_name, variable_fun.__get__(variable_fun, local))
def thread_local_middleware(get_response):
def middleware(request):
organization = Organization.objects.get_current(request)
_do_set_current_variable(lambda self: organization, ORG_ATTR_NAME)
response = get_response(request)
return response
return middleware
def get_current_variable(attr_name: str):
"""Given an attr_name, returns the object or callable if it exists in the local thread"""
current_variable = getattr(_thread_locals, attr_name, None)
if callable(current_variable):
return current_variable()
return current_variable
我有一个管理器 class,所有包含 organization
FK 字段的模型都使用:
class OrganizationAwareManager(models.Manager):
def get_queryset(self):
logger.debug(f"_thread_locals attributes in manager: {_thread_locals.__dict__}")
organization = get_current_variable(ORG_ATTR_NAME)
queryset = super().get_queryset().filter(organization=organization)
logger.debug(f"OrganizationAwareManager queryset: {queryset.query}")
return queryset
这一切在开发中都非常有效,并且在生产中的几乎所有方面都非常有效 - 除了在使用 OrganizationAwareManager
查询模型的 DRF ViewSets 中。其他 ViewSet 按预期工作,并且在视图上下文中引用具有 OrganizationAwareManager
的模型的普通 django 视图也可以正常工作。从字面上看,生产中 OrganizationAwareManager
的 DRF 视图集是唯一的问题。
如您所见,我在管理器中添加了日志记录以检查 _thread_locals
上设置了哪些属性。
在开发中我得到类似的东西:_thread_locals attributes in manager: {'_current_org': <bound method thread_local_middleware.<locals>.middleware.<locals>.<lambda> of <function thread_local_middleware.<locals>.middleware.<locals>.<lambda> at 0x7f1820264a60>>}
在产品中,似乎没有任何设置:_thread_locals attributes in manager: {}
关于 DRF 如何处理请求,我可能遗漏了什么吗?它应该是 运行 通过中间件堆栈并设置我的属性,无论我们是在开发还是生产,对吧?我似乎找不到可能解释这一点的两种环境之间的任何差异。两者非常相似,使用 docker-compose 和几乎相同的容器。我在 prod 环境中尝试了 gunicorn 和 gunicorn+uvicorn,症状没有区别。
我已经尝试解决这个问题 3 天了,但 运行 没有想法,所以解决这个问题的建议 非常 不胜感激。
真奇怪。一段时间后,我有了解决问题的想法。
仍然不完全确定根本原因(很想知道为什么它在开发中工作得很好,但在进行以下更改之前在生产中却没有),但似乎在 ViewSets 中定义了查询集class 本身,当线程开始时对查询求值?
我在我的日志中注意到,当我启动服务器时,我从 OrganizationAwareManager
得到了一大堆日志条目,说 _thread_locals
没有关联的属性。这些日志条目的数量似乎与我项目中使用 OrganizationAwareManager
的 ViewSet 的数量大致相同。他们都在系统启动时进行了初始评估,orgnization=None
,因此将丢弃对 organization
的任何进一步过滤。
像下面这样的 ViewSets 最终没有被 organization
正确过滤:
class AssetTypeViewSet(viewsets.ModelViewSet):
queryset = AssetType.objects.all()
serializer_class = AssetTypeSerializer
当我修改以在 get_queryset()
中定义查询集以便在执行 ViewSet 时对其进行评估,现在一切正常:
class AssetTypeViewSet(viewsets.ModelViewSet):
queryset = AssetType.objects.none()
serializer_class = AssetTypeSerializer
def get_queryset(self):
return AssetType.objects.all()
奇怪。