Django 缓存机制

Django 缓存简介

Django 提供了一个强大的缓存框架,允许你缓存动态内容,从而减少数据库查询和计算开销,显著提高网站性能。缓存框架支持多种后端存储,并提供了不同粒度的缓存控制。

1
请求到达
检查缓存
2
缓存命中
直接返回缓存内容
3
缓存未命中
执行视图逻辑
4
存储缓存
保存结果到缓存
缓存的优势
  • 性能提升 - 减少数据库查询和计算
  • 响应加速 - 快速返回预计算内容
  • 负载降低 - 减轻服务器和数据库压力
  • 成本节约 - 减少服务器资源消耗
  • 用户体验改善 - 页面加载更快
缓存级别
级别 描述 适用场景
全站缓存 缓存整个网站 静态内容多的网站
视图缓存 缓存特定视图 动态但不常变的内容
模板片段缓存 缓存模板片段 页面部分内容
低级缓存API 编程式缓存控制 细粒度缓存需求

缓存后端

Django 支持多种缓存后端,你可以根据应用需求选择最适合的后端。

支持的缓存后端
后端 描述 性能 适用场景
Memcached 内存缓存,高性能
极高
性能
生产环境首选
Redis 内存数据结构存储
极高
性能
生产环境,需要持久化
Database 数据库缓存
中等
性能
开发环境,小型应用
FileSystem 文件系统缓存
较低
性能
开发环境,共享主机
LocalMemory 本地内存缓存
性能
开发环境,单进程
Dummy 虚拟缓存(不缓存)
性能
测试,禁用缓存
缓存后端配置
settings.py - 缓存配置
# 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 = ''    # 缓存键前缀
URL 配置
urls.py
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'),
]
全站缓存流程
1. 请求到达

FetchFromCacheMiddleware 检查缓存

2. 缓存命中

直接返回缓存内容,跳过视图处理

3. 缓存未命中

正常处理请求,执行视图逻辑

4. 响应返回

UpdateCacheMiddleware 保存响应到缓存

95%
缓存命中率
理想情况
5%
缓存未命中率
可接受范围
注意事项:
  • 全站缓存不适合个性化内容
  • POST 请求不会被缓存
  • 用户相关的内容需要特殊处理
  • 确保中间件顺序正确

视图缓存

视图缓存允许你缓存特定视图的输出,比全站缓存更灵活。

基于函数的视图缓存
views.py
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')
基于类的视图缓存
views.py
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

低级缓存 API 提供了最灵活的缓存控制,允许你在代码的任何位置进行缓存操作。

基本缓存操作
使用低级缓存 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

常见问题和解决方案

常见问题
问题 原因 解决方案
缓存不一致 数据更新后缓存未失效 使用信号自动失效缓存
内存溢出 缓存数据过大或无限增长 设置合理的超时和内存限制
缓存穿透 大量请求不存在的键 缓存空值或使用布隆过滤器
缓存雪崩 大量缓存同时失效 设置随机超时时间
调试技巧
  • 使用 Django 调试工具栏
  • 添加缓存操作日志
  • 监控缓存命中率
  • 使用缓存统计命令
  • 分析缓存键模式
# 缓存统计命令
python manage.py shell

from django.core.cache import cache
# 获取缓存统计
print(cache._cache.get_stats())
# 查看所有键(仅适用于某些后端)
# print(cache._cache.get_keys())
生产环境注意事项:
  • 使用持久化缓存后端
  • 设置监控和告警
  • 定期备份缓存配置
  • 测试缓存故障转移