Django cache中比较常用的有 cache_page 这么个 decorators, 下面就根据请求流程,结合源码来说说它是怎么工作的?

版本是django1.8,不同版本可能函数等会变化,逻辑应该类似。

cache_page的功能

从逻辑上来说 cache_page 的功能非常简单,无非就是针对被装饰views对应的request进行缓存。当请求来了就检查是否有缓存,如果没有命中就正常获取 response,然后缓存给定的时间,如果命中缓存就从缓存中获取 缓存的response 对象并返回给客户端。缓存的方式key,value。

逻辑很简单,那么Django怎么实现,带着一些问题来研究。

1. Django是怎样对应请求缓存的?

2. 缓存的key是怎样的格式?

3. 当views关联的数据变化了,而缓存又没有过期,怎么手动过期缓存呢?django并没有这个api,见 Django中过期@cache_page中缓存的views数据

代码逻辑

涉及到的几个代码文件,文件都是相对django源码目录

1. views/decorators/cache.py cache_page所在的文件

2. utils/decorators.py 装饰器和middleware的调用封装,通用的库,这里不用看。

3. middleware/cache.py CacheMiddleware和它的父类 主要的逻辑在这里了

例如 http://xxx.com/hello 在views中对应下面的方法

@cache_page(60*3, key_prefix='lzz')
def hello(request):
    ....
    return HttpResponse()

上面的代码表示缓存 response 3分钟。缓存是一个动词,也是一个名次,也是个服务,有点绕。

请求进来

当一个请求进来之后,cache_page 检查 cache 的逻辑在 django.middleware.cache.FetchFromCacheMiddleware的 process_request 方法

    def process_request(self, request):
        """
        Checks whether the page is already cached and returns the cached
        version if available.
        """
        if request.method not in ('GET', 'HEAD'):
            request._cache_update_cache = False
            return None  # Don't bother checking the cache.

        # try and get the cached GET response
        # 获取当前请求对应的 cache_key,默认使用GET方法
        cache_key = get_cache_key(request, self.key_prefix, 'GET', cache=self.cache)
        if cache_key is None:
            request._cache_update_cache = True
            return None  # No cache information available, need to rebuild.
        # 从根据cache_key从缓存中读取缓存值
        response = self.cache.get(cache_key, None)
        # if it wasn't found and we are looking for a HEAD, try looking just for that
        # 如果没有获取到缓存,再尝试获取HEAD方法的缓存
        if response is None and request.method == 'HEAD':
            cache_key = get_cache_key(request, self.key_prefix, 'HEAD', cache=self.cache)
            response = self.cache.get(cache_key, None)

        if response is None:
            request._cache_update_cache = True
            return None  # No cache information available, need to rebuild.

        # hit, return cached response
        request._cache_update_cache = False
        return response

代码中的注释已经很清楚了,总结几点

  • 只缓存 HTTP method 为 GET和HEAD的请求
  • cache_key 是当前请求获取其对应的关键,get_cache_key 是一个核心的方法。获取这个key之后,后续的操作跟平时使用缓存服务类似。
  • 通过 request._cache_update_cache 这个属性来标记,响应的时候是否更新缓存。像遇到POST请求,还有命中缓存的时候就不用更新缓存内容了。
  • self.key_prefix 这个值是从cache_page的参数中传递过来的,默认为”

下面说说这个 get_cache_key的逻辑,中间涉及到的调用.

这个方法的定义 get_cache_key(request, key_prefix=None, method='GET', cache=None) 在django.utils.cache中

def get_cache_key(request, key_prefix=None, method='GET', cache=None):
    """
    Returns a cache key based on the request URL and query. It can be used
    in the request phase because it pulls the list of headers to take into
    account from the global URL registry and uses those to build a cache key
    to check against.

    If there is no headerlist stored, the page needs to be rebuilt, so this
    function returns None.
    """
    if key_prefix is None:
        key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
    cache_key = _generate_cache_header_key(key_prefix, request)
    if cache is None:
        cache = caches[settings.CACHE_MIDDLEWARE_ALIAS]
    # get header list
    headerlist = cache.get(cache_key, None)
    if headerlist is not None:
        return _generate_cache_key(request, method, headerlist, key_prefix)
    else:
        return None

1、_generate_cache_header_key的代码 header key生成的逻辑

2、_generate_cache_key的代码 response生成的逻辑

3、_i18n_cache_key_suffix的代码 语言和时区的处理

  • 上面代码 cache 变量可以理解为 cache服务
  • 首先调用 _generate_cache_header_key 生成 cache_key,它依据什么生成的呢?url, key_prefix,语言, 时区(后面两个需要有settings中配置), 结果类似 views.decorators.cache.cache_header..bcd1deb2a62fa916f88d6faca741df45.zh-hans 这么个东西。这个key的有什么作用呢?从代码中看出来是为了获取 headerlist(17行),这个key在缓存服务中对应的是一些header信息,例如 HTTP_COOKIE,对应 using-vary-headers这部分API,如果没有设置 vary_on_headers, 结果就为空。
  • 接着调用 _generate_cache_key 返回上面middleware中的 cache_key,这个key的产生需要那些东西呢? method,headerlist,key_prefix,语言,时区 最后的结果像这样 views.decorators.cache.cache_page..GET.bcd1deb2a62fa916f88d6faca741df45.66f40c1b9365da184c0dd0ba7087a7f0.zh-hans,这个key对应的是真正的response

可以看到一个请求会在缓存服务中缓存两个key,一个是 header key 对应 headerlist(配合vary_on_headers),另外一个key对应response的内容。

响应返回

当请求没有命中缓存的时候,views就会处理,然后返回响应,这个时候就会 更新缓存

重要逻辑在 django.middleware.cache.UpdateCacheMiddleware 的 process_respons 方法中。

  1. 检查是否要更新缓存,就是检查 request._cache_update_cache 这个属性
  2. 对于返回 streaming 和 非200 的响应不会缓存
  3. 对于有cookie并且header已经设置了Vary:Cookie的不缓存 (例如在views中设置了)
  4. 如果header Cache-Control 设置为0,也不进行缓存
  5. 设置response的header,缓存header key,缓存response内容

大概就这些,django的缓存一方面利用浏览器cache,一方面使用自己的缓存框架,一起完成整个cache过程。

Django 缓存模块 page_cache 源码阅读的更多相关文章

  1. 【第五篇】Volley代码修改之图片二级缓存以及相关源码阅读(重写ImageLoader.ImageCache)

    前面http://www.cnblogs.com/androidsuperman/p/8a157b18ede85caa61ca5bc04bba43d0.html 有讲到使用LRU来处理缓存的,但是只是 ...

  2. 基于Python的datetime模块和time模块源码阅读分析

    目录 1 前言  2 datetime.pyi源码分步解析 2.1 头部定义源码分析 2.2 tzinfo类源码分析 2.3 date类源码分析 2.4 time类源码分析 2.5 timedelta ...

  3. 【原】AFNetworking源码阅读(一)

    [原]AFNetworking源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 AFNetworking版本:3.0.4 由于我平常并没有经常使用AFNetw ...

  4. 【原】SDWebImage源码阅读(五)

    [原]SDWebImage源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 前面的代码并没有特意去讲SDWebImage的缓存机制,主要是想单独开一章节专门讲 ...

  5. CI框架源码阅读笔记3 全局函数Common.php

    从本篇开始,将深入CI框架的内部,一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说,全局函数具有最高的加载优先权,因此大多数的框架中BootStrap ...

  6. 【 js 基础 】【 源码学习 】backbone 源码阅读(一)

    最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(https://github.com/JiayiLi/source-code-stud ...

  7. 【 js 基础 】【 源码学习 】backbone 源码阅读(三)浅谈 REST 和 CRUD

    最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(https://github.com/JiayiLi/source-code-stud ...

  8. SpringMVC源码阅读系列汇总

    1.前言 1.1 导入 SpringMVC是基于Servlet和Spring框架设计的Web框架,做JavaWeb的同学应该都知道 本文基于Spring4.3.7源码分析,(不要被图片欺骗了,手动滑稽 ...

  9. 【 js 基础 】【 源码学习 】backbone 源码阅读(三)

    最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(https://github.com/JiayiLi/source-code-stud ...

随机推荐

  1. win7安装JDK6

    注:虽然9已经出来了,但是今天刚好业务需要要装JDK6,所以以JDK 6作为演示,同样适用于JDK 7.8的安装. 安装 基本上一直点下一步就可以. 此处可修改安装路径. 我将JDK的安装路径设置成了 ...

  2. DDD实战进阶第一波(五):开发一般业务的大健康行业直销系统(实现产品上下文领域层)

    从这篇文章开始,我们根据前面的DDD理论与DDD框架的约束,正式进入直销系统案例的开发. 本篇文章主要讲产品上下文中的领域层的主要实现,先简单讲下业务方面的需求:产品SPU与产品SKU,产品SPU主要 ...

  3. 关于Go 的 Interface

    最近在用Go语言写程序, 其中遇到一个场景:写了一个接口,3个实现接口的struct. 另外一个struct包含此接口,根据构造函数赋予不同的结构实现. 一开始struct里写的是接口的地址,但是在创 ...

  4. Git的安装和使用(托管至GitHub的方法)

    一.下载Git 1.下载 下载地址: https://git-scm.com/download/win 根据你的操作系统选择32位或者64位 2.安装过程一路next 3.检验安装是否成功 在桌面点击 ...

  5. TOMCAT下的JNDI的配置

    一.第一种配置局部JNDI 1.在tomcat的conf目录下的server.xml的<host>标签内,添加: <Context path="/TestMvcMode&q ...

  6. error and solve

    1.缺少对应的jar包 出错信息: Multiple markers at this line - The type org.springframework.beans.factory.Aware c ...

  7. 计蒜客NOIP模拟赛(3)D2T1 小区划分

    一条街道的两侧各连续坐落着 N 座单元楼.现在要为这些单元楼划分居民校区. 规则如下: 每个小区只能由同一侧连续的若干座单元楼组成.且两侧都恰有 K 个小区(每个小区至少有一栋楼). 两侧的小区划分规 ...

  8. hdu5587 BestCoder Round #64 (div.2)

    问题描述 Vicky是个热爱数学的魔法师,拥有复制创造的能力. 一开始他拥有一个数列{1}.每过一天,他将他当天的数列复制一遍,放在数列尾,并在两个数列间用0隔开.Vicky想做些改变,于是他将当天新 ...

  9. [Noi2013]矩阵游戏

    来自FallDream的博客,未经允许,请勿转载,谢谢. 婷婷是个喜欢矩阵的小朋友,有一天她想用电脑生成一个巨大的n行m列的矩阵(你不用担心她如何存储).她生成的这个矩阵满足一个神奇的性质:若用F[i ...

  10. bzip2

    压缩和解压缩文件bzip2 options] [file-list] bunzip2 [options] [file-list] bzcat [options] [file-list] bzip2re ...