侧边栏壁纸
  • 累计撰写 218 篇文章
  • 累计创建 59 个标签
  • 累计收到 5 条评论

DRF请求缓存策略

barwe
2024-01-08 / 0 评论 / 0 点赞 / 532 阅读 / 5,310 字
温馨提示:
本文最后更新于 2024-01-08,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

DRF (Django) 提供了工具直接对 视图方法 进行缓存。

from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page

class ModuleViewset(ModelViewSet):
    @method_decorator(cache_page(60 * 60 * 2)) # 单位:秒
    def list(self, request: Request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

需要启用缓存插件:

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.locmem.LocMemCache",
    }
}

method_decorator

method_decorator函数装饰器 转换成 方法装饰器

用来装饰 视图函数的请求处理方法 时,既可以放在请求处理方法上,也可以放在视图类上。

放在请求处理方法上:

class HomeView(View):
    @method_decorator(check_login)
    def get(self, request):
        return render(request, "home.html")

放在视图类上(此时需要传递额外的 name 关键字参数):

@method_decorator(check_login, name="get")
class HomeView(View):
    def get(self, request):
        return render(request, "home.html")

cache_page

cache_page 用于缓存视图函数(或者视图类的请求处理方法)。

该函数以视图 URL 以及请求头中的部分数据作为缓存键。

多站点应用中还可以指定缓存键前缀用以区分不同站点的缓存。

from django.middleware.cache import CacheMiddleware
from django.utils.decorators import decorator_from_middleware_with_args

def cache_page(timeout, *, cache=None, key_prefix=None):
    """
    Decorator for views that tries getting the page from the cache and
    populates the cache if the page isn't in the cache yet.

    The cache is keyed by the URL and some data from the headers.
    Additionally there is the key prefix that is used to distinguish different
    cache areas in a multi-site setup. You could use the
    get_current_site().domain, for example, as that is unique across a Django
    project.

    Additionally, all headers from the response's Vary header will be taken
    into account on caching -- just like the middleware does.
    """
    return decorator_from_middleware_with_args(CacheMiddleware)(
        page_timeout=timeout,
        cache_alias=cache,
        key_prefix=key_prefix,
    )

这里用到了两个导入:

  • CacheMiddleware: 缓存中间件
  • decorator_from_middleware_with_args: 传递参数,构造中间件的装饰器

缓存中间件 CacheMiddleware

CacheMiddleware 的定义如下:

class CacheMiddleware(UpdateCacheMiddleware, FetchFromCacheMiddleware):
    def __init__(self, get_response, cache_timeout=None, page_timeout=None, key_prefix="", cache_alias="default"): None
    /

中间件如何通过装饰器修改视图函数

decorator_from_middleware_with_args 的定义如下:

def make_middleware_decorator(middleware_class): # middleware_class <= CacheMiddleware
    def _make_decorator(*m_args, **m_kwargs): # m_kwargs <= {page_timeout, cache_alias, key_prefix}
        ...
    return _make_decorator

故而 decorator_from_middleware_with_args(CacheMiddleware) 返回的是一个构造装饰器的函数 _make_decorator

def make_middleware_decorator(middleware_class):
    def _make_decorator(*m_args, **m_kwargs): # m_kwargs <= {page_timeout, cache_alias, key_prefix}
        def _decorator(view_func):
            ...
        return _decorator
    return _make_decorator

调用 _make_decorator(page_timeout=timeout,cache_alias=cache,key_prefix=key_prefix) 将返回一个装饰器函数 _decorator

def make_middleware_decorator(middleware_class):
    def _make_decorator(*m_args, **m_kwargs):
        def _decorator(view_func):
            middleware = middleware_class(view_func, *m_args, **m_kwargs)

            @wraps(view_func)
            def _wrapped_view(request, *args, **kwargs):
                ...

            return _wrapped_view

        return _decorator

    return _make_decorator

_decorator 内部干了啥呢?首先实例化缓存中间件:

# middleware = CacheMiddleware(view_func, *m_args, **m_kwargs)
middleware = middleware_class(view_func, page_timeout=timeout, cache_alias=cache, key_prefix=key_prefix)

中间件的洋葱模型处理方式

然后就是具体处理视图响应数据的逻辑(_wrapped_view):

# _wrapped_view 负责对视图处理函数 view_func 进行再包装,同时通过 @wraps 将其伪装成原始视图函数
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs): # 这个形参与视图函数是一样的
    # process_request: 缓存中间件对请求的拦截
    if hasattr(middleware, "process_request"):
        result = middleware.process_request(request)
        if result is not None:
            return result
    # process_view: 缓存中间件对视图的拦截(请求需要进入视图函数才能返回响应)
    if hasattr(middleware, "process_view"):
        result = middleware.process_view(request, view_func, args, kwargs)
        if result is not None:
            return result
    # view_func: 请求进入视图函数返回响应
    try:
        response = view_func(request, *args, **kwargs)
    except Exception as e:
        if hasattr(middleware, "process_exception"):
            result = middleware.process_exception(request, e)
            if result is not None:
                return result
        raise
    # process_response: 缓存中间件对响应的拦截
    if hasattr(response, "render") and callable(response.render):
        if hasattr(middleware, "process_template_response"):
            response = middleware.process_template_response(
                request, response
            )
        # Defer running of process_response until after the template
        # has been rendered:
        if hasattr(middleware, "process_response"):

            def callback(response):
                return middleware.process_response(request, response)

            response.add_post_render_callback(callback)
    else:
        if hasattr(middleware, "process_response"):
            return middleware.process_response(request, response)
    return response

cache_page 装饰视图函数

调用 cache_page 将返回一个 _decorator 函数,后者以原始视图函数为参数,返回一个施加了中间件作用的新的视图函数。

结合一个 cache_page 的使用例子:

# 下面这个带括号,会被直接调用,它返回一个以视图函数为参数,又返回一个新的视图函数的函数(_decorator)
# 原始函数就是这个 _decorator 的输入参数,处理请求的真实视图函数就是 _decorator 返回的函数
@cache_page(60) # 单位:秒
def my_view(request): ...

cache_page 装饰视图方法

DRF 中的视图函数多以视图类的方法的形式存在,cache_page 用于视图类的方法时需要先转换为 方法装饰器

class ModuleViewset(ModelViewSet):
    @method_decorator(cache_page(60 * 60 * 2))
    def list(self, request: Request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

vary_on_cookie

当请求具有不同的 Cookie 值时,Django 会为每个不同的 Cookie 值缓存不同的响应。

from django.views.decorators.vary import vary_on_cookie
from django.http import HttpResponse

@vary_on_cookie
def my_view(request):
    # 基于Cookie的一些处理逻辑
    # ...
    response = HttpResponse("Hello, World!")
    return response

结合 DRF 使用:

class ModuleViewset(ModelViewSet):
    @method_decorator(cache_page(60 * 60 * 2))
    @method_decorator(vary_on_cookie)
    def list(self, request: Request, *args, **kwargs):
        return super().list(request, *args, **kwargs)
0

评论区