
动态网站的一个基本权衡是它们是动态的。每当用户请求页面时,Web 服务器进行各种计算,从数据库查询到模板渲染到业务逻辑,以创建您网站访问者看到的页面。从处理开销的角度来看,这比标准的从文件系统中读取文件的服务器安排要昂贵得多。
对于大多数 Web 应用程序,这种开销并不是什么大问题。大多数 Web 应用程序不像 washingtonpost.com
或 slashdot.org
那样大型,它们是小到中等规模的站点,流量一般。但对于中等到高流量的站点来说,尽量减少开销是非常重要的。
这就是缓存的用武之地。
缓存是指保存昂贵计算的结果,以便下次无需再次执行计算。以下是一些伪代码,说明了如何为动态生成的网页实现缓存:
given a URL, try finding that page in the cache
if the page is in the cache:
return the cached page
else:
generate the page
save the generated page in the cache (for next time)
return the generated page
Django 自带强大的缓存系统,可以让你保存动态页面,这样就不必为每次请求计算。为了方便,Django 提供了不同级别的缓存粒度。你可以缓存特定视图的输出,你可以只缓存难以生成的部分,或者你可以缓存整个网站。
Django 也与 "下游" 缓存很好地配合, 比如 Squid 和基于浏览器的缓存。这些是您不直接控制但可以通过 HTTP 标头提供提示的缓存类型,用于指示哪些部分的网站应该被缓存,以及如何缓存。
一、设置缓存
缓存系统需要少量的设置。也就是说,你必须告诉它你的缓存数据应该放在哪里 ------ 是在数据库中,还是在文件系统上,或者直接放在内存中。这是一个重要的决定,会影响你的缓存的性能;是的,有些缓存类型比其他类型快。
你的缓存首选项在设置文件中的 CACHES 设置中。以下是所有可用值对 CACHES 的解释。
1、Memcached
Memcached 是一个完全基于内存的缓存服务器,最初是为了处理 LiveJournal.com 上的高负载而开发的,并随后由 Danga Interactive 开源。它被类似 Facebook 和 Wikipedia 这样的网站使用,以减少数据库访问并显著提高网站性能。
Memcached 以一个守护进程的形式运行,并且被分配了指定数量的 RAM。它所做的就是提供一个快速接口用于在缓存中添加,检索和删除数据。所有数据都直接存储在内存中,因此不会产生数据库或文件系统使用的开销。
在安装 Memcached 本身之后,您需要安装一个 Memcached 绑定。有几个 Python Memcached 绑定可用,Django 支持的两个是 pylibmc 和 pymemcache。
在 Django 中使用 Memcached :
- 设置 BACKEND 为 django.core.cache.backends.memcached.PyMemcacheCache 或 django.core.cache.backends.memcached.PyLibMCCache (取决于你选择的 memcached 绑定)。
- 将 LOCATION 设置为 ip:port 值,其中 ip 是 Memcached 守护进程的 IP 地址,port 是 Memcached 运行的端口,或者设置为 unix:path 值,其中 path 是 Memcached Unix socket 文件的路径。
在这个例子中,Memcached 运行在 localhost(127.0.0.1)端口 11211,使用 pymemcache
绑定:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
"LOCATION": "127.0.0.1:11211",
}
}
在这个例子中,Memcached 可以通过本地 Unix 套接字文件 /tmp/memcached.sock
使用 pymemcache
绑定:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
"LOCATION": "unix:/tmp/memcached.sock",
}
}
Memcached 有一个很好的特性,就是它可以在多台服务器上共享一个缓存。这意味着你可以在多台机器上运行 Memcached 守护进程,程序将把这组机器作为一个 单一 的缓存,而不需要在每台机器上重复缓存值。要利用这个特性,请在 LOCATION 中包含所有服务器地址,可以是分号或逗号分隔的字符串,也可以是一个列表。
在这个例子中,缓存是通过运行在 IP 地址 172.19.26.240 和 172.19.26.242 上的 Memcached 实例共享的,这两个实例都在 11211 端口上:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
"LOCATION": [
"172.19.26.240:11211",
"172.19.26.242:11211",
],
}
}
在下面的例子中,缓存是由运行在 IP 地址 172.19.26.240(端口11211)、172.19.26.242(端口11212)和 172.19.26.244(端口11213)上的 Memcached 实例共享的:
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",
],
}
}
默认情况下,PyMemcacheCache 后端设置以下选项(您可以在 OPTIONS 中覆盖它们):
"OPTIONS": {
"allow_unicode_keys": True,
"default_noreply": False,
"serde": pymemcache.serde.pickle_serde,
}
关于 Memcached 的最后一点是,基于内存的缓存有一个缺点:因为缓存的数据存储在内存中,如果你的服务器崩溃,数据将丢失。显然,内存并不是用来永久存储数据的,所以不要依赖基于内存的缓存作为你唯一的数据存储。毫无疑问,Django 缓存后端中的 每个 都不应该用于永久存储 ------ 它们的目的都是为了缓存的解决方案,而不是存储 ------ 但我们在这里指出这一点是因为基于内存的缓存是格外临时的。
2、Redis
Redis 是一个内存数据库,可用于缓存。要开始使用它,您需要在本地或远程机器上运行一个 Redis 服务器。
在设置好 Redis 服务器之后,您需要安装 Redis 的 Python 绑定。Django 原生支持的绑定是 redis-py。同时,建议安装 hiredis-py 包。
要在 Django 中使用 Redis 作为缓存后端:
- 将 BACKEND 设置为 django.core.cache.backends.redis.RedisCache。
- 将 LOCATION 设置为指向您的 Redis 实例的 URL,使用适当的方案。
例如,如果 Redis 在本地主机(127.0.0.1)的端口 6379 上运行:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": "redis://127.0.0.1:6379",
}
}
通常,Redis 服务器受到身份验证的保护。为了提供用户名和密码,您可以将它们与 URL 一起添加到 LOCATION
中:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": "redis://username:[email protected]: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
],
}
}
from django.core.cache import cache
# 设置键值对(默认使用配置中的 TIMEOUT)
cache.set("user:1:name", "Alice")
# 设置键值对并指定超时时间(单位:秒)
cache.set("user:1:email", "[email protected]", timeout=3600)
# 获取单个值
name = cache.get("user:1:name") # 返回 "Alice"
email = cache.get("user:1:email") # 返回 "[email protected]"
# 获取不存在的键(返回 None)
age = cache.get("user:1:age") # 返回 None
# 设置默认值(如果键不存在)
age = cache.get("user:1:age", default=25) # 返回 25
3、数据库缓存
Django 可以在数据库中存储缓存数据。如果你有一个快速、索引正常的数据库服务器,这种缓存效果最好。
用数据库表作为你的缓存后端:
- 将 BACKEND 设置为 django.core.cache.backends.db.DatabaseCache
- 将 LOCATION 设置为数据库表的 tablename。这个表名可以是没有使用过的任何符合要求的名称。
在这个例子中,缓存表的名称是 my_cache_table
:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.db.DatabaseCache",
"LOCATION": "my_cache_table",
}
}
与其他缓存后端不同,数据库缓存不支持在数据库级别自动清除过期条目。相反,每次调用 add()
, set()
, 或 touch()
时都会清除过期的缓存条目。
创建缓存表
在使用数据库缓存之前,您必须使用以下命令创建缓存表:
python manage.py createcachetable
这将在数据库中创建一个表,该表的格式与 Django 数据库缓存系统期望的一致。该表的表名取自 LOCATION 。
如果你正在使用多个数据库缓存, createcachetable 会为每个缓存创建一个表。
如果你正在使用多个数据库, createcachetable 观察你的数据库路由器的 allow_migrate() 方法(见下文)。
像 migrate 一样, createcachetable 不会影响已经存在的表,它只创建缺失的表。
要打印即将运行的 SQL,而不是运行它,请使用 createcachetable --dry-run 选项。
多数据库
如果在多数据库中使用缓存,你也需要设置数据库缓存表的路由指令。因为路由的原因,数据库缓存表在 django_cache
应用程序中显示为 CacheEntry
的模型名。这个模型不会出现在模型缓存中,但模型详情可用于路由目的。
比如,下面的路由可以将所有缓存读取操作指向 cache_replica
,并且所有的写操作指向 cache_primary
。缓存表将会只同步到 cache_primary
。
class CacheRouter:
"""A router to control all database cache operations"""
def db_for_read(self, model, **hints):
"All cache read operations go to the replica"
if model._meta.app_label == "django_cache":
return "cache_replica"
return None
def db_for_write(self, model, **hints):
"All cache write operations go to primary"
if model._meta.app_label == "django_cache":
return "cache_primary"
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
"Only install the cache model on primary"
if app_label == "django_cache":
return db == "cache_primary"
return None
如果你没有指定路由指向数据库缓存模型,缓存后端将使用 默认
的数据库。
如果没使用数据库缓存后端,则无需担心为数据库缓存模型提供路由指令。
4、文件系统缓存
基于文件的后端序列化并保存每个缓存值作为单独的文件。要使用此后端,可将 BACKEND 设置为 "django.core.cache.backends.filebased.FileBasedCache" 并将 LOCATION 设置为一个合适的路径。比如,在 /var/tmp/django_cache 存储缓存数据,使用以下配置:
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",
}
}
目录路径应该是绝对路径------因此,它应该以文件系统根目录开始。无需担心是否需要以斜杠结尾。
确保该设置所指向的目录存在,并且具备读写权限,或者可以由您的 Web 服务器运行的系统用户创建。继续上面的示例,如果您的服务器以用户 apache
运行,请确保目录 /var/tmp/django_cache
存在,并且用户 apache
具备读写权限,或者可以由用户 apache
创建。
5、本地内存缓存
如果你的配置文件中没有指定其他缓存,那么这是默认的缓存。如果你想获得内存缓存的速度优势,但又不具备运行 Memcached 的能力,可以考虑使用本地内存缓存后端。这个缓存是每进程所有(见下文)和线程安全的。要使用它,可以将 BACKEND 设置为 "django.core.cache.backends.locmem.LocMemCache"。例如:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
"LOCATION": "unique-snowflake",
}
}
LOCATION 被用于标识各个内存存储。如果只有一个 locmem 缓存,你可以忽略 LOCATION 。但是如果你有多个本地内存缓存,那么你至少要为其中一个起个名字,以便将它们区分开。
这种缓存使用最近最少使用(LRU)的淘汰策略。
请注意,每个进程都会有自己的私有缓存实例,这意味着不可能进行跨进程缓存。这也意味着本地内存缓存的内存效率不是特别高,所以对于生产环境来说,它可能不是一个好的选择。对于开发来说是不错的选择。