Django 提供了一个强大的缓存框架,允许你缓存动态内容,从而减少数据库查询和计算开销,显著提高网站性能。缓存框架支持多种后端存储,并提供了不同粒度的缓存控制。
| 级别 | 描述 | 适用场景 |
|---|---|---|
| 全站缓存 | 缓存整个网站 | 静态内容多的网站 |
| 视图缓存 | 缓存特定视图 | 动态但不常变的内容 |
| 模板片段缓存 | 缓存模板片段 | 页面部分内容 |
| 低级缓存API | 编程式缓存控制 | 细粒度缓存需求 |
Django 支持多种缓存后端,你可以根据应用需求选择最适合的后端。
| 后端 | 描述 | 性能 | 适用场景 |
|---|---|---|---|
Memcached |
内存缓存,高性能 |
极高
性能
|
生产环境首选 |
Redis |
内存数据结构存储 |
极高
性能
|
生产环境,需要持久化 |
Database |
数据库缓存 |
中等
性能
|
开发环境,小型应用 |
FileSystem |
文件系统缓存 |
较低
性能
|
开发环境,共享主机 |
LocalMemory |
本地内存缓存 |
高
性能
|
开发环境,单进程 |
Dummy |
虚拟缓存(不缓存) |
无
性能
|
测试,禁用缓存 |
# Redis 缓存配置
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
'PASSWORD': 'your_password', # 如果有密码
'SOCKET_CONNECT_TIMEOUT': 5, # 连接超时
'SOCKET_TIMEOUT': 5, # 读写超时
},
'KEY_PREFIX': 'myapp', # 缓存键前缀
'TIMEOUT': 300, # 默认超时时间(秒)
}
}
# Memcached 配置
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211',
# 或者多个服务器
# 'LOCATION': [
# '127.0.0.1:11211',
# '127.0.0.1:11212',
# ],
}
}
# 数据库缓存
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table', # 数据库表名
}
}
# 文件系统缓存
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': '/var/tmp/django_cache', # 缓存目录
}
}
# 本地内存缓存
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake', # 唯一标识
}
}
# 虚拟缓存(用于测试)
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
}
}
TIMEOUT - 缓存默认过期时间KEY_PREFIX - 所有缓存键的前缀VERSION - 缓存版本号OPTIONS - 后端特定选项Django 提供了多种级别的缓存控制,从全站缓存到细粒度的模板片段缓存。
# settings.py
MIDDLEWARE = [
# 更新缓存中间件应该在前面
'django.middleware.cache.UpdateCacheMiddleware',
# 其他中间件...
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
# 获取缓存中间件应该在后面
'django.middleware.cache.FetchFromCacheMiddleware',
]
# 缓存设置
CACHE_MIDDLEWARE_ALIAS = 'default' # 使用的缓存别名
CACHE_MIDDLEWARE_SECONDS = 600 # 默认缓存时间(秒)
CACHE_MIDDLEWARE_KEY_PREFIX = '' # 缓存键前缀
from django.views.decorators.cache import cache_page
from django.urls import path
from . import views
urlpatterns = [
# 缓存整个站点(通过中间件)
path('', views.home, name='home'),
# 缓存特定视图
path('about/', cache_page(60 * 15)(views.about), name='about'),
# 或者使用装饰器语法
path('contact/', views.contact, name='contact'),
]
# 基于类的视图缓存
from django.views.generic import TemplateView
from django.views.decorators.cache import cache_page
urlpatterns += [
path('faq/', cache_page(60 * 60)(TemplateView.as_view(
template_name='faq.html'
)), name='faq'),
]
FetchFromCacheMiddleware 检查缓存
直接返回缓存内容,跳过视图处理
正常处理请求,执行视图逻辑
UpdateCacheMiddleware 保存响应到缓存
视图缓存允许你缓存特定视图的输出,比全站缓存更灵活。
from django.views.decorators.cache import cache_page
from django.shortcuts import render
from .models import Product
# 基本视图缓存
@cache_page(60 * 15) # 缓存15分钟
def product_list(request):
products = Product.objects.all()
return render(request, 'products/list.html', {'products': products})
# 基于参数的缓存
@cache_page(60 * 5)
def product_detail(request, product_id):
product = Product.objects.get(id=product_id)
return render(request, 'products/detail.html', {'product': product})
# 条件缓存
def should_cache(request):
"""自定义缓存条件"""
return not request.user.is_authenticated
@cache_page(60 * 10, condition=should_cache)
def public_page(request):
return render(request, 'public.html')
# 使用缓存别名
@cache_page(60 * 60, cache='special_cache')
def special_view(request):
return render(request, 'special.html')
from django.views.generic import ListView, DetailView
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from .models import Article
# 方法装饰器方式
class ArticleListView(ListView):
model = Article
template_name = 'articles/list.html'
@method_decorator(cache_page(60 * 15))
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
# 在 URL 配置中缓存
class ArticleDetailView(DetailView):
model = Article
template_name = 'articles/detail.html'
# urls.py
# path('article/<int:pk>/',
# cache_page(60 * 30)(ArticleDetailView.as_view()),
# name='article_detail')
# 使用 mixin
from django.views.decorators.cache import cache_control
class CacheControlMixin:
"""缓存控制混合类"""
@method_decorator(cache_control(max_age=3600))
@method_decorator(cache_control(private=True))
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
class CachedArticleView(CacheControlMixin, ListView):
model = Article
template_name = 'articles/cached_list.html'
cache_control - 设置 HTTP 缓存头
never_cache - 禁用缓存
vary_on_cookie - 基于 Cookie 变化
vary_on_headers - 基于头部变化
模板片段缓存允许你缓存模板的特定部分,提供最细粒度的缓存控制。
{% load cache %}
<!-- 基本缓存 -->
{% cache 300 sidebar %}
<div class="sidebar">
<h3>热门文章</h3>
{% for article in popular_articles %}
<div class="article">
<h4>{{ article.title }}</h4>
<p>{{ article.summary }}</p>
</div>
{% endfor %}
</div>
{% endcache %}
<!-- 基于变量的缓存 -->
{% cache 300 article_header article.id %}
<header class="article-header">
<h1>{{ article.title }}</h1>
<div class="meta">
作者: {{ article.author.username }}
发布时间: {{ article.publish_date }}
</div>
</header>
{% endcache %}
<!-- 使用缓存后端 -->
{% cache 300 footer "special_cache" %}
<footer>
<p>© 2023 我的网站. 所有权利保留.</p>
</footer>
{% endcache %}
{% load cache %}
<!-- 基于多个变量缓存 -->
{% cache 3600 user_profile user.id user.profile_updated %}
<div class="profile">
<img src="{{ user.avatar.url }}" alt="{{ user.username }}">
<h2>{{ user.get_full_name }}</h2>
<p>{{ user.bio }}</p>
<div class="stats">
<span>文章: {{ user.article_count }}</span>
<span>粉丝: {{ user.follower_count }}</span>
</div>
</div>
{% endcache %}
<!-- 条件缓存 -->
{% if not user.is_authenticated %}
{% cache 600 anonymous_welcome %}
<div class="welcome-message">
<h2>欢迎访问我们的网站!</h2>
<p>请<a href="{% url 'login' %}">登录</a>或<a href="{% url 'register' %}">注册</a>。</p>
</div>
{% endcache %}
{% endif %}
<!-- 嵌套缓存 -->
{% cache 1200 page_content %}
<div class="content">
{% cache 300 featured_content %}
<section class="featured">
<h2>特色内容</h2>
{% for item in featured_items %}
<div class="item">{{ item.title }}</div>
{% endfor %}
</section>
{% endcache %}
{% cache 600 recent_posts %}
<section class="recent">
<h2>最新文章</h2>
{% for post in recent_posts %}
<article>{{ post.title }}</article>
{% endfor %}
</section>
{% endcache %}
</div>
{% endcache %}
低级缓存 API 提供了最灵活的缓存控制,允许你在代码的任何位置进行缓存操作。
from django.core.cache import cache
from django.core.cache import caches # 多个缓存后端
# 基本设置和获取
def get_expensive_data():
# 尝试从缓存获取
data = cache.get('expensive_data')
if data is None:
# 缓存未命中,计算数据
data = calculate_expensive_data()
# 存储到缓存,超时时间1小时
cache.set('expensive_data', data, 60 * 60)
return data
# 带默认值的获取
data = cache.get('some_key', 'default_value')
# 设置缓存(多种方式)
cache.set('key', 'value', 30) # 30秒超时
cache.set('key', 'value', timeout=None) # 永不过期
cache.set('key', 'value') # 使用默认超时
# 添加(仅当键不存在时)
cache.add('new_key', 'value', 60) # 如果键存在,返回False
# 获取或设置
data = cache.get_or_set('complex_data', calculate_complex_data, 300)
# 获取多个键
values = cache.get_many(['key1', 'key2', 'key3'])
# 设置多个键
cache.set_many({'key1': 'value1', 'key2': 'value2'}, 300)
# 删除缓存
cache.delete('key_to_delete')
cache.delete_many(['key1', 'key2'])
# 清空所有缓存
cache.clear()
# 检查键是否存在
if cache.has_key('some_key'):
print("键存在")
# 递增/递减
cache.set('counter', 10)
cache.incr('counter') # 11
cache.incr('counter', 5) # 16
cache.decr('counter', 3) # 13
import time
from django.core.cache import cache
from functools import wraps
# 缓存装饰器
def cached_function(timeout=300):
"""函数结果缓存装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# 基于参数生成缓存键
cache_key = f"{func.__name__}:{args}:{kwargs}"
# 尝试从缓存获取
result = cache.get(cache_key)
if result is not None:
return result
# 执行函数
result = func(*args, **kwargs)
# 存储结果
cache.set(cache_key, result, timeout)
return result
return wrapper
return decorator
# 使用缓存装饰器
@cached_function(timeout=3600)
def get_user_statistics(user_id):
"""获取用户统计信息(计算昂贵)"""
time.sleep(2) # 模拟耗时计算
return {
'post_count': Post.objects.filter(author_id=user_id).count(),
'comment_count': Comment.objects.filter(user_id=user_id).count(),
'like_count': Like.objects.filter(user_id=user_id).count(),
}
# 缓存失效模式
class DataService:
@cached_function(timeout=3600)
def get_data(self, data_id):
return self._fetch_from_database(data_id)
def update_data(self, data_id, new_data):
# 更新数据库
self._update_database(data_id, new_data)
# 使缓存失效
cache_key = f"get_data:({data_id},):{{}}"
cache.delete(cache_key)
def _fetch_from_database(self, data_id):
# 模拟数据库查询
time.sleep(1)
return f"Data for {data_id}"
def _update_database(self, data_id, data):
# 模拟数据库更新
pass
# 使用多个缓存后端
def get_from_fast_cache():
fast_cache = caches['fast'] # 内存缓存
slow_cache = caches['slow'] # 数据库缓存
data = fast_cache.get('data')
if data is None:
data = slow_cache.get('data')
if data is not None:
# 填充快速缓存
fast_cache.set('data', data, 60)
return data
合理的缓存策略可以显著提高应用性能,避免常见的缓存问题。
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django.core.cache import cache
from .models import Product, Category
# 基于信号自动失效缓存
@receiver([post_save, post_delete], sender=Product)
def invalidate_product_cache(sender, **kwargs):
"""产品数据变更时失效相关缓存"""
cache_keys = [
'featured_products',
'product_list',
'product_stats',
]
for key in cache_keys:
cache.delete(key)
# 失效分类相关的缓存
if hasattr(kwargs.get('instance'), 'category'):
category_id = kwargs['instance'].category_id
cache.delete(f'category_products_{category_id}')
# 基于版本的缓存
def get_product_list(force_refresh=False):
"""带版本控制的缓存"""
cache_key = 'product_list'
version_key = 'product_list_version'
if force_refresh:
# 强制刷新,增加版本号
current_version = cache.get(version_key, 0)
cache.set(version_key, current_version + 1)
cache.delete(cache_key)
current_version = cache.get(version_key, 0)
versioned_key = f"{cache_key}_v{current_version}"
data = cache.get(versioned_key)
if data is None:
data = list(Product.objects.all())
cache.set(versioned_key, data, 60 * 60)
return data
# 写时失效模式
class CachedProductService:
@cached_function(timeout=3600)
def get_products_by_category(self, category_id):
return list(Product.objects.filter(category_id=category_id))
def create_product(self, product_data):
# 创建产品
product = Product.objects.create(**product_data)
# 失效相关缓存
self._invalidate_category_cache(product.category_id)
self._invalidate_general_cache()
return product
def _invalidate_category_cache(self, category_id):
cache_key = f"get_products_by_category:({category_id},):@{{}}"
cache.delete(cache_key)
def _invalidate_general_cache(self):
cache.delete('featured_products')
cache.delete('product_list')
import time
from django.core.cache import cache
from django.core.cache.backends.base import DEFAULT_TIMEOUT
def get_with_penetration_protection(key, func, timeout=DEFAULT_TIMEOUT):
"""
带缓存穿透保护的获取函数
"""
# 尝试获取缓存
value = cache.get(key)
if value is not None:
return value
# 检查是否有其他进程正在计算
lock_key = f"{key}_lock"
if cache.add(lock_key, 'locked', 60): # 获取锁
try:
# 重新检查缓存(防止多个请求同时到达)
value = cache.get(key)
if value is not None:
return value
# 计算值
value = func()
# 存储到缓存
cache.set(key, value, timeout)
return value
finally:
# 释放锁
cache.delete(lock_key)
else:
# 等待其他进程完成
for i in range(10): # 最多等待1秒
time.sleep(0.1)
value = cache.get(key)
if value is not None:
return value
# 如果等待超时,直接计算(降级)
return func()
# 空值缓存(防止缓存穿透)
def get_product_details(product_id):
"""获取产品详情,缓存空值防止穿透"""
cache_key = f"product_details_{product_id}"
value = cache.get(cache_key)
if value is not None:
if value == '__NULL__': # 空值标记
return None
return value
try:
product = Product.objects.get(id=product_id)
cache.set(cache_key, product, 3600)
return product
except Product.DoesNotExist:
# 缓存空值,较短时间
cache.set(cache_key, '__NULL__', 300)
return None
# 热点数据预加载
def preload_hot_data():
"""预加载热点数据"""
hot_products = Product.objects.filter(is_hot=True)[:10]
cache.set('hot_products', list(hot_products), 3600)
# 设置下次预加载时间
cache.set('last_preload_time', time.time(), 3600)
# 定时预加载
def scheduled_preload():
"""定时预加载任务"""
last_preload = cache.get('last_preload_time', 0)
if time.time() - last_preload > 1800: # 30分钟
preload_hot_data()
# 监控缓存统计
from django.core.cache import cache
stats = cache._cache.get_stats()
hit_ratio = stats['gets'] / stats['hits']
# 事务中的缓存操作
from django.db import transaction
@transaction.atomic
def update_product(product_id, data):
product = Product.objects.get(id=product_id)
product.update(**data)
cache.delete(f'product_{product_id}')
# 安全的缓存键生成
import hashlib
def safe_cache_key(data):
key_data = str(sorted(data.items()))
return hashlib.md5(key_data.encode()).hexdigest()
# 缓存调试中间件
class CacheDebugMiddleware:
def __call__(self, request):
start = time.time()
response = self.get_response(request)
duration = time.time() - start
response['X-Cache-Debug'] = f'{duration:.2f}s'
return response
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 缓存不一致 | 数据更新后缓存未失效 | 使用信号自动失效缓存 |
| 内存溢出 | 缓存数据过大或无限增长 | 设置合理的超时和内存限制 |
| 缓存穿透 | 大量请求不存在的键 | 缓存空值或使用布隆过滤器 |
| 缓存雪崩 | 大量缓存同时失效 | 设置随机超时时间 |
# 缓存统计命令
python manage.py shell
from django.core.cache import cache
# 获取缓存统计
print(cache._cache.get_stats())
# 查看所有键(仅适用于某些后端)
# print(cache._cache.get_keys())