编辑:
我想出的最终解决方案已编译成 PyPI 包:https ://pypi.org/project/django-request-cache/
编辑 2016-06-15:
我发现了一个简单得多的解决方案来解决这个问题,并且因为从一开始就没有意识到这应该是多么容易而有点面无表情。
from django.core.cache.backends.base import BaseCache
from django.core.cache.backends.locmem import LocMemCache
from django.utils.synch import RWLock
class RequestCache(LocMemCache):
"""
RequestCache is a customized LocMemCache which stores its data cache as an instance attribute, rather than
a global. It's designed to live only as long as the request object that RequestCacheMiddleware attaches it to.
"""
def __init__(self):
# We explicitly do not call super() here, because while we want BaseCache.__init__() to run, we *don't*
# want LocMemCache.__init__() to run, because that would store our caches in its globals.
BaseCache.__init__(self, {})
self._cache = {}
self._expire_info = {}
self._lock = RWLock()
class RequestCacheMiddleware(object):
"""
Creates a fresh cache instance as request.cache. The cache instance lives only as long as request does.
"""
def process_request(self, request):
request.cache = RequestCache()
有了这个,您可以将request.cache
其用作缓存实例,该实例的寿命与实际寿命一样长request
,并且在请求完成时将被垃圾收集器完全清除。
如果您需要从通常不可用的上下文中访问该request
对象,您可以使用可以在线找到的所谓“全局请求中间件”的各种实现之一。
** 初步答案:**
这里没有其他解决方案可以解决的一个主要问题是,当您在单个进程的生命周期中创建和销毁其中的几个时,LocMemCache 会泄漏内存。django.core.cache.backends.locmem
定义了几个全局字典,这些字典包含对每个 LocalMemCache 实例的缓存数据的引用,并且这些字典永远不会被清空。
下面的代码解决了这个问题。它最初是@href_ 的答案和@squarelogic.hayden 评论中链接的代码所使用的更简洁逻辑的组合,然后我进一步完善了它。
from uuid import uuid4
from threading import current_thread
from django.core.cache.backends.base import BaseCache
from django.core.cache.backends.locmem import LocMemCache
from django.utils.synch import RWLock
# Global in-memory store of cache data. Keyed by name, to provides multiple
# named local memory caches.
_caches = {}
_expire_info = {}
_locks = {}
class RequestCache(LocMemCache):
"""
RequestCache is a customized LocMemCache with a destructor, ensuring that creating
and destroying RequestCache objects over and over doesn't leak memory.
"""
def __init__(self):
# We explicitly do not call super() here, because while we want
# BaseCache.__init__() to run, we *don't* want LocMemCache.__init__() to run.
BaseCache.__init__(self, {})
# Use a name that is guaranteed to be unique for each RequestCache instance.
# This ensures that it will always be safe to call del _caches[self.name] in
# the destructor, even when multiple threads are doing so at the same time.
self.name = uuid4()
self._cache = _caches.setdefault(self.name, {})
self._expire_info = _expire_info.setdefault(self.name, {})
self._lock = _locks.setdefault(self.name, RWLock())
def __del__(self):
del _caches[self.name]
del _expire_info[self.name]
del _locks[self.name]
class RequestCacheMiddleware(object):
"""
Creates a cache instance that persists only for the duration of the current request.
"""
_request_caches = {}
def process_request(self, request):
# The RequestCache object is keyed on the current thread because each request is
# processed on a single thread, allowing us to retrieve the correct RequestCache
# object in the other functions.
self._request_caches[current_thread()] = RequestCache()
def process_response(self, request, response):
self.delete_cache()
return response
def process_exception(self, request, exception):
self.delete_cache()
@classmethod
def get_cache(cls):
"""
Retrieve the current request's cache.
Returns None if RequestCacheMiddleware is not currently installed via
MIDDLEWARE_CLASSES, or if there is no active request.
"""
return cls._request_caches.get(current_thread())
@classmethod
def clear_cache(cls):
"""
Clear the current request's cache.
"""
cache = cls.get_cache()
if cache:
cache.clear()
@classmethod
def delete_cache(cls):
"""
Delete the current request's cache object to avoid leaking memory.
"""
cache = cls._request_caches.pop(current_thread(), None)
del cache
编辑 2016-06-15:我发现了一个简单得多的解决方案来解决这个问题,并且因为从一开始就没有意识到这应该是多么容易而有点面无表情。
from django.core.cache.backends.base import BaseCache
from django.core.cache.backends.locmem import LocMemCache
from django.utils.synch import RWLock
class RequestCache(LocMemCache):
"""
RequestCache is a customized LocMemCache which stores its data cache as an instance attribute, rather than
a global. It's designed to live only as long as the request object that RequestCacheMiddleware attaches it to.
"""
def __init__(self):
# We explicitly do not call super() here, because while we want BaseCache.__init__() to run, we *don't*
# want LocMemCache.__init__() to run, because that would store our caches in its globals.
BaseCache.__init__(self, {})
self._cache = {}
self._expire_info = {}
self._lock = RWLock()
class RequestCacheMiddleware(object):
"""
Creates a fresh cache instance as request.cache. The cache instance lives only as long as request does.
"""
def process_request(self, request):
request.cache = RequestCache()
有了这个,您可以将request.cache
其用作缓存实例,该实例的寿命与实际寿命一样长request
,并且在请求完成时将被垃圾收集器完全清除。
如果您需要从通常不可用的上下文中访问该request
对象,您可以使用可以在线找到的所谓“全局请求中间件”的各种实现之一。