2010-06-30 51 views
14

我想实现一个装饰器,它提供每个请求缓存给任何方法,而不仅仅是视图。这是一个示例用例。Django中的每个请求缓存?

我有一个确定 中记录的一长串的纪录是 “最喜欢”的自定义标签。为了检查 项目是否是最爱,您必须查询 数据库。理想情况下,你会 执行一个查询来获得所有的 收藏夹,然后只检查每个记录的缓存列表 。

一种解决方案是让所有在视图中 收藏夹,然后通过 该套入模板,然后 到每个标签调用。

或者,标签本身可以执行查询本身,但只有 第一次被调用。然后 结果可以被缓存以用于随后的 调用。好处在于,您可以使用 此标签从任何模板,任何 视图,而不提醒视图。

在现有的高速缓存机制中,您可以将结果缓存为50ms, ,并且假定这将与当前请求相关联 。我想使 相关性可靠。

这是我现在有的标签的一个例子。

@register.filter() 
def is_favorite(record, request): 

    if "get_favorites" in request.POST: 
     favorites = request.POST["get_favorites"] 
    else: 

     favorites = get_favorites(request.user) 

     post = request.POST.copy() 
     post["get_favorites"] = favorites 
     request.POST = post 

    return record in favorites 

有没有办法从Django中获取当前请求对象,无须传递它呢?从一个标签,我可以通过请求,这将永远存在。但我想从其他函数使用这个装饰器。

是否存在每个请求缓存的现有实现?

回答

21

使用自定义中间件,您可以获得一个Django缓存实例,保证为每个请求清除Django缓存实例。

这是我在一个项目中使用:

from threading import currentThread 
from django.core.cache.backends.locmem import LocMemCache 

_request_cache = {} 
_installed_middleware = False 

def get_request_cache(): 
    assert _installed_middleware, 'RequestCacheMiddleware not loaded' 
    return _request_cache[currentThread()] 

# LocMemCache is a threadsafe local memory cache 
class RequestCache(LocMemCache): 
    def __init__(self): 
     name = '[email protected]%i' % hash(currentThread()) 
     params = dict() 
     super(RequestCache, self).__init__(name, params) 

class RequestCacheMiddleware(object): 
    def __init__(self): 
     global _installed_middleware 
     _installed_middleware = True 

    def process_request(self, request): 
     cache = _request_cache.get(currentThread()) or RequestCache() 
     _request_cache[currentThread()] = cache 

     cache.clear() 

要使用中间件settings.py中进行注册,如:

MIDDLEWARE_CLASSES = (
    ... 
    'myapp.request_cache.RequestCacheMiddleware' 
) 

然后您就可以使用缓存如下:

from myapp.request_cache import get_request_cache 

cache = get_request_cache() 

有关更多信息,请参阅django低级高速缓存api文档:

Django Low-Level Cache API

应该很容易修改memoize修饰符以使用请求缓存。看看Python的装饰图书馆的memoize的装饰的一个很好的例子:

Python Decorator Library

+3

请小心此解决方案!随着越来越多的线程打开为您的用户提供服务,_request_cache字典将不断填满,并且它永远不会被清理干净。根据您的网络服务器如何存储Python全局变量,这可能会导致内存泄漏。 – CoreDumpError 2015-01-09 20:19:16

+1

你应该在process_response方法中清除缓存????? – 2015-06-27 04:31:18

+1

是的 - 清除process_response和process_expception上的缓存 - 在django cuser中间件插件中有一个很好的例子。见:https://github.com/Alir3z4/django-cuser/blob/master/cuser/middleware.py – 2015-08-26 14:14:15

1

您可以随时手动进行缓存。

... 
    if "get_favorites" in request.POST: 
     favorites = request.POST["get_favorites"] 
    else: 
     from django.core.cache import cache 

     favorites = cache.get(request.user.username) 
     if not favorites: 
      favorites = get_favorites(request.user) 
      cache.set(request.user.username, favorites, seconds) 
    ... 
+0

问题是关于每个请求缓存。这个解决方案也会为用户的第二个请求使用缓存。但我认为大部分时间cache.get()和cache.set()或更好。 – guettli 2012-01-06 14:32:47

3

我想出了缓存的东西一劈直入请求对象(而不是使用标准的缓存,这将被捆绑到memcached中,文件,数据库等)

# get the request object's dictionary (rather one of its methods' dictionary) 
mycache = request.get_host.__dict__ 

# check whether we already have our value cached and return it 
if mycache.get('c_category', False): 
    return mycache['c_category'] 
else: 
    # get some object from the database (a category object in this case) 
    c = Category.objects.get(id = cid) 

    # cache the database object into a new key in the request object 
    mycache['c_category'] = c 

    return c 

所以,基本上我只是存储下的请求字典的一把新钥匙c_category'的缓存值(在这种情况下,类别对象)。或者更确切地说,因为我们不能只是在请求对象上创建一个键,所以我将这个键添加到请求对象的一个​​方法 - get_host()中。

乔治亚州。

3

多年后,超级黑客在单个Django请求中缓存SELECT语句。您需要在请求范围的早期执行patch()方法,就像在一个中间件中一样。

patch()方法用一个名为execute_sql_cache的替代方法替换了Django内部的execute_sql方法。该方法查看要运行的sql,如果它是select语句,则首先检查线程本地缓存。只有在缓存中没有找到它,它才会继续执行SQL。在任何其他类型的sql语句中,它吹走了缓存。有一些逻辑不缓存大的结果集,这意味着超过100条记录。这是为了保留Django的懒惰查询集评估。

+0

乍一看这似乎很酷。但是看着它,当你没有得到一个SELECT语句时,你会使缓存失效。似乎是合理的,直到你有多个进程。它不会使所有缓存无效。一个小的改变是将其存储在请求对象上,以便在请求之间重置。 – dalore 2017-03-17 15:40:38

1

这一个使用python字典作为缓存(而不是django的缓存),并且简单而轻量。

  • 只要线程被破坏,它的缓存就会自动执行。
  • 不需要任何中间件,并且内容不会在每次访问时被腌渍和取消,速度更快。
  • 经过测试并与gevent的monkeypatching。

同样可以用threadlocal存储实现。 我不知道这种方法的任何缺点,请随意将它们添加到评论中。

from threading import currentThread 
import weakref 

_request_cache = weakref.WeakKeyDictionary() 

def get_request_cache(): 
    return _request_cache.setdefault(currentThread(), {}) 
+1

原始问题是关于每个请求缓存而不是每个线程的缓存。 在基于线程池的服务器中,您的实现永远不会过期并导致内存耗尽。 – 2014-06-07 16:21:19

1

Answer由@href_给出很好。

以防万一你想要的东西更短的也有可能做的伎俩:

from django.utils.lru_cache import lru_cache 

def cached_call(func, *args, **kwargs): 
    """Very basic temporary cache, will cache results 
    for average of 1.5 sec and no more then 3 sec""" 
    return _cached_call(int(time.time()/3), func, *args, **kwargs) 


@lru_cache(maxsize=100) 
def _cached_call(time, func, *args, **kwargs): 
    return func(*args, **kwargs) 

然后得到的最爱调用它是这样的:

favourites = cached_call(get_favourites, request.user) 

该方法利用的lru cache并结合其通过时间戳记,我们确保缓存不会持续更长的时间,然后持续几秒钟。如果您需要在短时间内多次拨打高成本功能,则可解决问题。

它不是一个完美的方式来使缓存无效,因为偶尔它会错过最近的数据:int(..2.99../3)其次是int(..3.00..)/3)。尽管存在这个缺点,它仍然可以在大多数命中中非常有效。

此外,您还可以在请求/响应周期外使用它,例如芹菜任务或管理命令作业。

2

这里没有其他解决方案解决的一个主要问题是LocMemCache在单个进程的生命周期中创建并销毁它们中的几个时会泄漏内存。 django.core.cache.backends.locmem定义了几个全局字典,它们持有对每个LocalMemCache实例缓存数据的引用,并且这些字典从不清空。

以下代码解决了此问题。它起始于@ href_的回答和@ sqrtlogic.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年6月15日: 我发现了一个显著简单的解决了这个问题,并kindof facepalmed不认识多么容易这本来应该是从一开始。

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对象,则可以使用可在线找到的所谓“全局请求中间件”的各种实现之一。