Skip to content

Django - 缓存

缓存(Caching)意味着存储一个耗时操作的结果,这样下次需要相同结果时就不必再次执行该操作。你可以将其类比为记住一道复杂数学题的答案,而不是每次有人问时都重新计算。

以下是缓存工作的一个简单概念:

给定一个请求(例如,请求一个特定的网页 URL):
1. 检查缓存中是否存在针对此请求的有效、预先计算好的结果。
2. 如果存在:
- 直接返回缓存的结果。
3. 如果不存在:
- 执行耗时操作(例如,通过查询数据库、渲染模板来生成网页)。
- 将新生成的结果保存到缓存中,供将来请求使用。
- 返回生成的结果。

Django 提供了一个强大的缓存框架,允许你缓存应用程序的各个部分以提高性能:

  • 特定视图(view)的输出。
  • 模板的部分内容(模板片段)。
  • 整个网站。
  • 使用底层 API 缓存任意耗时计算的结果。

要在 Django 中开始使用缓存,首先需要配置缓存数据存储的位置(即缓存后端,cache backend)。Django 支持多种后端:

  • 数据库缓存(Database Caching): 将缓存数据存储在 Django 项目的数据库中。
  • 文件系统缓存(Filesystem Caching): 将缓存数据存储为服务器上的独立文件。
  • 内存缓存(LocMem): 将缓存数据存储在 Web 服务器进程的内存中。速度非常快,但在不同进程之间不共享。
  • Memcached: 使用快速的、外部的内存缓存服务器(Memcached)。需要安装 Memcached 服务器和相应的 Python 库(例如 pylibmc 或 pymemcache)。
  • Redis: 使用快速的、外部内存数据结构存储系统(Redis)。需要安装 Redis 服务器和相应的 Python 库(例如 redis 或 django-redis)。

你在项目的 settings.py 文件中使用 CACHES 设置来配置缓存后端。

将以下内容添加到你的 settings.py 文件中:

CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table', # Choose a name for your cache table
# 选择一个你的缓存表名
}
}

配置完成后,你需要运行以下命令在数据库中创建缓存表:

python manage.py createcachetable

将以下内容添加到你的 settings.py 文件中:

CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': '/var/tmp/django_cache', # Ensure this directory exists and is writable by the web server
# 确保此目录存在并且 Web 服务器有写入权限
}
}

这通常是生产环境中最具性能的选项。首先,安装一个 Memcached 客户端库(例如 pymemcache):pip install pymemcache。然后,配置 settings.py:

# Using pymemcache client
# 使用 pymemcache 客户端
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': '127.0.0.1:11211', # Assumes Memcached is running on localhost, port 11211
# 假设 Memcached 运行在 localhost 的 11211 端口
}
}
# Or using a Unix socket
# 或者使用 Unix 套接字
# CACHES = {
# 'default': {
# 'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
# 'LOCATION': 'unix:/tmp/memcached.sock',
# }
# }

Redis 是另一个高性能缓存的优秀选择。首先,安装一个 Redis 客户端库和一个适用于 Django 的 Redis 缓存后端(例如 django-redis):pip install django-redis redis。然后,配置 settings.py:

CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1", # Assumes Redis is running on localhost, port 6379, using DB 1
# 假设 Redis 运行在 localhost 的 6379 端口,使用 DB 1
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}

有关更多配置选项,请参阅 django-redis 文档。

使用缓存最简单的方法是缓存整个网站。这通过将 Django 的缓存中间件添加到 settings.py 中的 MIDDLEWARE 设置来完成。顺序至关重要:

MIDDLEWARE = [
# ... other middleware ...
# ... 其他中间件 ...
'django.middleware.cache.UpdateCacheMiddleware', # Must be first or near the start
# 必须在最前面或靠近开始的地方
'django.middleware.common.CommonMiddleware',
# ... other middleware ...
# ... 其他中间件 ...
'django.middleware.cache.FetchFromCacheMiddleware', # Must be last or near the end
# 必须在最后面或靠近结束的地方
# ... other middleware ...
# ... 其他中间件 ...
]

解释:UpdateCacheMiddleware 需要在生成响应 之后 运行才能缓存它。FetchFromCacheMiddleware 需要在其他中间件 之前 运行,以便查看是否可以直接从缓存中提供请求。

然后,在 settings.py 中配置这些设置:

# The cache alias defined in CACHES to use for storage.
# 在 CACHES 中定义的用于存储的缓存别名。
CACHE_MIDDLEWARE_ALIAS = 'default'
# The default number of seconds each page should be cached.
# 每个页面默认应缓存的秒数。
CACHE_MIDDLEWARE_SECONDS = 600 # Cache for 10 minutes
# 缓存 10 分钟
# Optional: A prefix for cache keys generated by the middleware.
# 可选:中间件生成的缓存键的前缀。
CACHE_MIDDLEWARE_KEY_PREFIX = ''

全站缓存对于静态内容较多的网站有效,但对于高度动态的网站则不太有用。

为了实现更细粒度的控制,你可以使用 cache_page 装饰器缓存单个视图的输出。将其直接应用于你的视图函数:

from django.views.decorators.cache import cache_page
from django.http import HttpResponse
import datetime
# Cache this view for 15 minutes (900 seconds)
# 缓存此视图 15 分钟 (900 秒)
@cache_page(60 * 15)
def expensive_calculation_view(request):
# Simulate an expensive operation
# 模拟一个耗时操作
now = datetime.datetime.now()
html = f"<html><body>It is now {now}.</body></html>"
return HttpResponse(html)
# Example view with parameters
# 带参数的示例视图
@cache_page(60 * 15)
def view_articles_by_month(request, year, month):
text = f"Displaying articles for month: {month}/{year}"
# In a real app, you'd query the database here
# 在实际应用中,你会在 GITHUB 此处查询数据库
return HttpResponse(text)

cache_page 装饰器将其参数设置为缓存超时时间,单位为秒。每个唯一的 URL 都将独立缓存。例如,对 /articles/02/2023/ 的请求将独立于对 /articles/03/2024/ 的请求进行缓存。

你还可以在 URL 配置(urls.py)中应用 cache_page,这在不修改视图代码的情况下应用缓存时很有用:

from django.urls import path, re_path
from django.views.decorators.cache import cache_page
from . import views # Assuming views are in the same app's views.py
# 假设视图位于同一应用的 views.py 中
urlpatterns = [
# Cache the view for 15 minutes directly in the URLconf
# 直接在 URLconf 中缓存视图 15 分钟
re_path(
r'^articles/(?P<month>\d{2})/(?P<year>\d{4})/$',
cache_page(60 * 15)(views.view_articles_by_month),
name='articles_by_month'
),
path('expensive/', views.expensive_calculation_view, name='expensive_view'),
]

如果在 urls.py 中应用了缓存,那么对于该特定视图,你就不需要在 views.py 中使用 @cache_page 装饰器了。

有时,页面中只有一部分计算量较大或变化不频繁。你可以使用 {% cache %} 模板标签在模板内缓存这些特定部分。

考虑以下模板片段:

{% extends "base.html" %}
{% load cache %}
{% block title %}My Page Title{% endblock %}
{% block title %}我的页面标题{% endblock %}
{% block content %}
<h1>Welcome, {{ user.username }}!</h1>
<h1>欢迎,{{ user.username }}!</h1>
{# This part is expensive to generate #}
{# 这部分生成成本较高 #}
{% cache 3600 sidebar request.user.username %} {# Cache for 1 hour, vary by username #}
{# 缓存 1 小时,根据用户名变化 #}
<div class="sidebar">
<h2>Recommendations for You</h2>
<h2>为您推荐</h2>
<ul>
{% for item in recommended_items %}
<li>{{ item.name }}</li>
{% empty %}
<li>No recommendations available.</li>
<li>暂无推荐。</li>
{% endfor %}
</ul>
</div>
{% endcache %}
<div class="main-content">
<p>This content changes frequently.</p>
<p>此内容经常变化。</p>
<p>Current time: {% now "H:i:s" %}</p>
<p>当前时间:{% now "H:i:s" %}</p>
</div>
{% endblock %}

解释:

  • 首先,加载缓存库:{% load cache %}。
  • {% cache %} 标签至少接受两个参数:以秒为单位的缓存超时时间(例如 3600)和缓存片段的唯一名称(例如 sidebar)。
  • 你可以选择添加更多参数(如上面的 request.user.username),以便根据这些值创建不同的缓存版本。这称为缓存的“变化”(varying)。
  • {% cache %} 和 {% endcache %} 之间的所有内容都将被缓存。

要缓存任意 Python 对象或在视图/模板上下文之外的函数结果,可以使用底层缓存 API:

from django.core.cache import cache
import time
def get_complex_data(user_id):
cache_key = f'complex_data_{user_id}'
result = cache.get(cache_key)
if result is None:
# Data not in cache, calculate it
# 数据不在缓存中,计算它
print(f'Calculating complex data for user {user_id}...')
# 模拟耗时操作
time.sleep(2) # Simulate expensive operation
result = {'data': f'Some complex data for {user_id}', 'timestamp': time.time()}
# Store in cache for 5 minutes (300 seconds)
# 存储到缓存中 5 分钟 (300 秒)
cache.set(cache_key, result, 300)
print('Data stored in cache.')
print('数据已存入缓存。')
else:
print(f'Retrieved complex data for user {user_id} from cache.')
print(f'从缓存中检索到用户 {user_id} 的复杂数据。')
return result
# Example usage
# 示例用法
data1 = get_complex_data(123)
data2 = get_complex_data(123) # This call should hit the cache
# 此调用应该命中缓存

主要方法包括 cache.set(key, value, timeout)、cache.get(key, default=None)、cache.delete(key)、cache.clear() 等等。请参阅 Django 文档以获取完整的 API 说明。

延伸阅读: