Django与缓存

阅读: 30557     评论:4

我们都知道Django建立的是动态网站,正常情况下,页面请求都经历了这样一个处理过程:

接收请求 -> url路由 -> 视图处理 -> 数据库读写 -> 视图处理 -> 模版渲染 -> 返回请求

设想这么个场景,一个用户或者大量用户都对某个页面非常感兴趣,出现了大量实质相同的请求,如果每次请求都采取上面的流程,将出现大量的重复工作,尤其是大量无谓的数据库读写。

要解决这个问题,有很多办法,其中一个就是使用缓存。

缓存的思路是,既然已经处理过一次,得到了结果,就把当前结果缓存下来。下次再请求时,把缓存的处理结果直接返回。这样,可以极大地减少重复工作,降低数据库负载。

下面是缓存思路的伪代码:

给定一个URL, 试图在缓存中查询对应的页面

如果缓存中有该页面:
    返回这个缓存的页面
否则:
    生成页面
    将生成的页面保存到缓存中(用作以后)
    返回这个生成的页面

以Django一站式服务的尿性,像缓存这么重要的功能,怎么可能不具备?当然是必带的了!

Django提供不同粒度不同层级的缓存:你可以缓存指定的页面、难以生成的部分或者缓存整个站点。

Django也能很好的配合那些“下游”缓存, 比如Squid和基于浏览器的缓存。

一、设置缓存

Django支持基于数据库的、文件的和内存的缓存。

Memcached

Memcached是Django原生支持的缓存系统,速度快,效率高。

Memcached是一种基于内存的缓存服务,起初是为了解决LiveJournal.com的负载问题而开发的,后来由Danga开源。 它被类似Facebook和维基百科这种大型网站使用,用来减少数据库访问次数,显著地提高了网站的性能。

Memcached会启动一个守护进程,并分配单独的内存块,为缓存提供一个快速的添加,检索,删除的接口。所有的数据直接存储在内存中,所以它不能取代数据库或者文件系统的功能。如果你对缓存很熟悉,这些内容都很好理解。

一些说明:

  • Memcached不是Django自带的软件,而是一个独立的软件,需要你自己安装、配置和启动服务;
  • Memcached安装好了后,还要安装Python操作Memcached的依赖库,最常用的是pymemcachepylibmc
  • 上面两个条件都满足了后,还要在Django中进行配置。

配置方法:

  • 根据你安装的Python依赖库不同,将CACHES的BACKEND设置为django.core.cache.backends.memcached.PyMemcacheCache或者django.core.cache.backends.memcached.PyLibMCCache
  • 设置LOCATION为你的Memecached守护进程所在的主机IP和进程端口,格式为ip:port的字符串。或者unix:path的形式,在Unix操作系统中。

下面是一个参考例子,Memcached运行在localhost (127.0.0.1) port 11211,使用了pymemcache 库:

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
        "LOCATION": "127.0.0.1:11211",
    }
}

下面的Memcached运行在本地的Unix socket上,位于/tmp/memcached.sock

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
        "LOCATION": "unix:/tmp/memcached.sock",
    }
}

下面的Memcached运行在/tmp/memcached.sock,不带unix:/前缀,依赖pylibmc库:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
        'LOCATION': '/tmp/memcached.sock',
    }
}

Memcached支持分布式服务,可能同时在几台机器上运行,将它们的IP地址都加入到LOCATION中,如下所示:

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
        "LOCATION": [
            "172.19.26.240:11211",
            "172.19.26.242:11212",
            "172.19.26.244:11213",
        ],
    }
}

基于内存的缓存系统有个明显的缺点就是断电数据丢失,尤其是Memcached这种不支持序列化的缓存,所以请大家务必要注意数据的安全性。

默认情况下,Memcached使用如下的配置参数:

"OPTIONS": {
    "allow_unicode_keys": True,
    "default_noreply": False,
    "serde": pymemcache.serde.pickle_serde,
}

其实对于当下,redis如日中天的时代,还是选择redis作为缓存吧,还支持序列化。

Redis

实际上,在生产环境中,我们绝大多数情况都是使用redis作为缓存系统。Django生态圈里有个django-redis库,帮我们封装了Django和redis的连接操作。

Redis是基于内存的缓存数据库。使用它之前,你必须安装它,并配置好服务器,可以是本地,也可以是远程服务器。

启动Redis服务器后,我们还需要安装Python访问Redis的库,比如redis-pyhiredis-py

外部条件具备后,在Django中如下配置Redis缓存:

  • 将BACKEND设置为django.core.cache.backends.redis.RedisCache
  • LOCATION指向Redis服务器的ip地址和端口。

下面是个示例:

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
    }
}

当需要提供Redis用户名和密码时,如下配置:

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": "redis://username:password@127.0.0.1:6379",
    }
}

如果在复制模式下设置了多个Redis服务器,可以使用如下的配置方式:

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": [
            "redis://127.0.0.1:6379",  # leader
            "redis://127.0.0.1:6378",  # read-replica 1
            "redis://127.0.0.1:6377",  # read-replica 2
        ],
    }
}

数据库缓存

我们使用缓存的很大原因就是要减少数据库的操作,如果将缓存又存到数据库,岂不是脱....

所以,尽量不要使用基于数据库的缓存,这里也不做具体介绍,给个简单的配置范例吧:

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.db.DatabaseCache",
        "LOCATION": "my_cache_table",
    }
}

但是在某些情况下,还是有一些用途的,比如你有一个高速、高效索引的数据库。

文件系统缓存

连数据库我们都觉得慢,那么基于文件系统的呢?更慢!不过在你手里没有Redis、Memcached和数据库的时候,也可以勉为其难的用一下。下面给出两个配置案例:

基于Unix系统:

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
        "LOCATION": "/var/tmp/django_cache",
    }
}

基于Windows操作系统,需要带盘符路径:

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
        "LOCATION": "c:/foo/bar",
    }
}

基于本地内存的缓存

如果你的本地主机内存够大够快,也可以直接使用它作为缓存,并且这也是Django默认使用的缓存后端。配置如下:

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

LOCATION 被用于标识各个内存的存储位置。如果只有一个 locmem 缓存,你可以忽略 LOCATION 。但是如果你有多个本地内存缓存,那么你至少要为其中一个起个名字,以便将它们区分开。

这种缓存使用最近最少使用(LRU)的淘汰策略。

注意,每个进程将有它们自己的私有缓存实例,这意味着不存在跨进程的缓存。也意味着本地内存缓存不是特别节省内存,因此它不适合生产环境,不过它在开发环境中表现很好。

开发用的虚假缓存

Django很贴心的为我们提供了一个开发用的虚假缓存。

虚假缓存的作用是,你不需要缓存做任何实际的工作,只是希望测试和接入缓存的接口。

配置如下:

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.dummy.DummyCache",
    }
}

自定义缓存后端

自己写一个缓存后端,或者接入redis等第三方缓存!配置很简单:

CACHES = {
    'default': {
        'BACKEND': 'path.to.backend',
    }
}

缓存的参数

上述每一个缓存后端都可以设置一些额外的参数来控制缓存行为,可以设置的参数如下:

  • TIMEOUT

缓存的过期时间,以秒为单位,默认是300秒。None表示永远不会过期。设置成0将造成缓存立即失效(缓存就没有意义了)。

  • OPTIONS

可选参数,根据缓存后端的不同而不同。

MAX_ENTRIES :删除旧值之前允许缓存的最大条目。默认是 300

CULL_FREQUENCY :当达到 MAX_ENTRIES 时被淘汰的部分条目。实际比率为 1 / CULL_FREQUENCY ,当达到 MAX_ENTRIES 时,设置为2就会淘汰一半的条目。这个参数应该是一个整数,默认为3。

CULL_FREQUENCY 的值为 0 意味着当达到 MAX_ENTRIES 缓存时,整个缓存都会被清空。在一些后端(尤其是 database ),这会使以更多的缓存未命中为代价来更快的进行淘汰。

  • KEY_PREFIX

Django服务器使用的所有缓存键的前缀字符串。

  • VERSION

由Django服务器生成的默认版本号。

  • KEY_FUNCTION

一个字符串,其中包含一个函数的点路径,该函数定义了如何将前缀,版本和密钥组合成最终缓存密钥。

下面例子中配置了一个基于文件系统的缓存后端,缓存过期时间被设置为60秒,最大条目为1000.

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
        "LOCATION": "/var/tmp/django_cache",
        "TIMEOUT": 60,
        "OPTIONS": {"MAX_ENTRIES": 1000},
    }
}

以下示例配置了一个基于memcached的后端,其对象大小限制为2MB:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
        'LOCATION': '127.0.0.1:11211',
        'OPTIONS': {
            'server_max_value_length': 1024 * 1024 * 2,
        }
    }
}

下面是一个基于 pymemcache 的后端配置实例,它启用了客户端池(通过保持客户端连接来提高性能),将 memcache/网络错误视为缓存失效,并在连接的 socket 上设置了 TCP_NODELAY 标志:

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
        "LOCATION": "127.0.0.1:11211",
        "OPTIONS": {
            "no_delay": True,
            "ignore_exc": True,
            "max_pool_size": 4,
            "use_pooling": True,
        },
    }
}

以下是基于pylibmc库的后端配置,该后端启用二进制协议、SASL认证和ketama行为模式:

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.memcached.PyLibMCCache",
        "LOCATION": "127.0.0.1:11211",
        "OPTIONS": {
            "binary": True,
            "username": "user",
            "password": "pass",
            "behaviors": {
                "ketama": True,
            },
        },
    }
}

二、缓存全站

前面是配置和启动缓存功能,下面介绍如何使用缓存。

最简单的使用方法是缓存整个网站。

这需要额外将django.middleware.cache.UpdateCacheMiddlewaredjango.middleware.cache.FetchFromCacheMiddleware添加到MIDDLEWARE设置中,如下所示:

MIDDLEWARE = [
    'django.middleware.cache.UpdateCacheMiddleware',
    'django.middleware.common.CommonMiddleware',
    '...'
    'django.middleware.cache.FetchFromCacheMiddleware',
]

注意: update中间件必须放在列表的开始位置,而fectch中间件,必须放在最后。 这是Django使用中间件的规则,它们是有顺序关系的。

然后,添加下面这些需要的参数到settings文件里:

CACHE_MIDDLEWARE_ALIAS : 用于存储数据的缓存后端的别名
CACHE_MIDDLEWARE_SECONDS : 全站级别的设置每个page需要被缓存多少秒.
CACHE_MIDDLEWARE_KEY_PREFIX : Django支持多站点,所以需要通过这个配置来区分不同站点使用的缓存

三、缓存指定视图

另一个使用缓存框架的方法是对视图的输出进行缓存。在django.views.decorators.cache定义了一个自动缓存视图响应结果的装饰器cache_page,使用非常简单:

from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def my_view(request):
    ...

cache_page接受一个参数:timeout,秒为单位。在上例中,my_view()视图的结果将被缓存15分钟(为了提高可读性写成了60 * 15)

如果多个URL指向同一视图,每个URL将会分别缓存。 继续my_view的例子,如果URLconf如下所示:

urlpatterns = [
    path('foo/<int:code>/', my_view),   
]

那么发送到/foo/23//foo/1/的请求会被分别缓存。但是一旦一个明确的URL(例如/foo/23/) 已经被请求过了, 之后再度发出的指向该URL的请求将使用缓存的内容。

cache_page装饰器还可以使用一些额外的参数,比如cache,这个参数指示具体使用的缓存后端。

@cache_page(60 * 15, cache="special_cache")
def my_view(request):
    ...

还可以采用可选的关键字参数key_prefix在每个视图中指定具体的缓存前缀,用于区分多站点或者分布式站点,如下所示:

@cache_page(60 * 15, key_prefix="site1")
def my_view(request):
    ...

上面硬编码了视图和缓存系统,因为 cache_page 改变了 my_view 函数。这种方法将你的视图和缓存系统耦合起来,并不理想。例如,你可能想在其他没有缓存的站点上重用这个视图函数,或者你可能想分发这个视图给那些想使用视图但不想缓存它们的人员。解决这些问题的办法是在 URLconf 中指定视图缓存,而不是视图函数上指定。

当你在 URLconf 中使用 cache_page 时,可以这样包装视图函数。下面是原URLconf:

urlpatterns = [
    path('foo/<int:code>/', my_view),
]

my_view 包含在 cache_page 中:

from django.views.decorators.cache import cache_page

urlpatterns = [
    path('foo/<int:code>/', cache_page(60 * 15)(my_view)),
]

四、缓存模板片段

我们还可以使用cache模板标签来缓存模板的一个片段。要使用这个标签,首先要在模版的顶部位置添加{% load cache %}

模板标签{% cache %}将在设定的时间内,缓存标签块中包含的内容。它最少需要两个参数:缓存时间(以秒为单位)以及给缓存片段起的名称。像这样:

{% load cache %}


{% cache 500 sidebar %}
    .. sidebar ..
{% endcache %}

还可以依据片段内的动态内容缓存多个版本。如上个例子中,可以给站点的每个用户生成不同版本的sidebar缓存。 只需要给{% cache %}标签再传递一个参数来标识区分这个缓存片段,如下所示:

{% load cache %}
{% cache 500 sidebar request.user.username %}
    .. sidebar for logged in user ..
{% endcache %}

缓存超时参数可以是个模板变量,只要模板变量可以解析为整数值即可。例如,如果模板变量my_timeout设置为值600,则以下两个示例是等效的:

{% cache 600 sidebar %} ... {% endcache %}
{% cache my_timeout sidebar %} ... {% endcache %}

 Django 日志 Authentication 

评论总数: 4


点击登录后方可评论

django-redis作为缓存后端还是非常可以的



最后说的django+redis的缓存文章在哪里?求链接!谢谢!



缓存在使用过程中,需要考虑的一点就是清缓存,不然会导致页面不是最新的,这方面的网上的资料还是比较少。很多时候可能用底层 api来解决,比如把大数据直接保存在缓存中,再读出来呈现。



缓存很实用。