1. 整体架构设计
Django采用了分离式缓存设计,将缓存操作分为两个阶段:
1 2 3 4 5 6 7 8
| MIDDLEWARE = [ 'django.middleware.cache.UpdateCacheMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.cache.FetchFromCacheMiddleware' ]
|
为什么这样设计?
- 请求阶段:中间件从上到下执行,
FetchFromCacheMiddleware最后执行,可以直接返回缓存
- 响应阶段:中间件从下到上执行,
UpdateCacheMiddleware最后执行,获得最终响应进行缓存
2. FetchFromCacheMiddleware - 缓存获取中间件
2.1 核心功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| def process_request(self, request): """从缓存中检查并返回页面""" if request.method not in ('GET', 'HEAD'): request._cache_update_cache = False return None cache_key = get_cache_key(request, self.key_prefix, 'GET', cache=self.cache) if cache_key is None: request._cache_update_cache = True return None response = self.cache.get(cache_key) if response is None and request.method == 'HEAD': cache_key = get_cache_key(request, self.key_prefix, 'HEAD', cache=self.cache) response = self.cache.get(cache_key) if response is None: request._cache_update_cache = True return None request._cache_update_cache = False return response
|
2.2 缓存键生成机制
1 2 3 4 5 6 7 8 9 10 11
| def generate_cache_key_example(request): factors = [ request.get_full_path(), request.method, request.META.get('HTTP_ACCEPT_LANGUAGE'), request.META.get('HTTP_USER_AGENT'), request.COOKIES, ] return hashlib.md5('|'.join(factors).encode()).hexdigest()
|
3. UpdateCacheMiddleware - 缓存更新中间件
3.1 核心功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| def process_response(self, request, response): """设置缓存(如果需要)""" if not self._should_update_cache(request, response): return response if response.streaming or response.status_code not in (200, 304): return response if not request.COOKIES and response.cookies and has_vary_header(response, 'Cookie'): return response timeout = get_max_age(response) if timeout is None: timeout = self.cache_timeout elif timeout == 0: return response patch_response_headers(response, timeout) if timeout and response.status_code == 200: cache_key = learn_cache_key(request, response, timeout, self.key_prefix, cache=self.cache) if hasattr(response, 'render') and callable(response.render): response.add_post_render_callback( lambda r: self.cache.set(cache_key, r, timeout) ) else: self.cache.set(cache_key, response, timeout) return response
|
3.2 安全机制详解
1 2 3 4 5 6 7 8 9 10 11
| def security_check_example(request, response): """ 场景:用户首次访问(无Cookie),服务器设置了用户特定的Cookie 问题:如果缓存这个响应,其他用户可能看到第一个用户的Cookie 解决:检测到这种情况时,不进行缓存 """ if not request.COOKIES and response.cookies and has_vary_header(response, 'Cookie'): return False return True
|
4. CacheMiddleware - 组合中间件
4.1 设计特点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| class CacheMiddleware(UpdateCacheMiddleware, FetchFromCacheMiddleware): """ 简单站点的基础缓存中间件 同时继承获取和更新功能 """ def __init__(self, get_response=None, cache_timeout=None, **kwargs): self.get_response = get_response try: key_prefix = kwargs['key_prefix'] if key_prefix is None: key_prefix = '' except KeyError: key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX self.key_prefix = key_prefix try: cache_alias = kwargs['cache_alias'] if cache_alias is None: cache_alias = DEFAULT_CACHE_ALIAS except KeyError: cache_alias = settings.CACHE_MIDDLEWARE_ALIAS self.cache_alias = cache_alias if cache_timeout is None: cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS self.cache_timeout = cache_timeout self.cache = caches[self.cache_alias]
|
4.2 装饰器支持
1 2 3 4 5 6 7 8 9 10 11 12
| from django.views.decorators.cache import cache_page
@cache_page(60 * 15) def my_view(request): return render(request, 'template.html', context)
def my_view(request): return render(request, 'template.html', context)
my_view = CacheMiddleware(cache_timeout=900)(my_view)
|
5. 缓存策略详解
5.1 缓存条件
1 2 3 4 5 6 7 8 9
| def should_cache_response(request, response): """判断响应是否应该被缓存""" conditions = [ request.method in ('GET', 'HEAD'), response.status_code in (200, 304), not response.streaming, not (not request.COOKIES and response.cookies and has_vary_header(response, 'Cookie')), ] return all(conditions)
|
5.2 缓存键策略
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| def cache_key_factors(): """影响缓存键的因素""" return { 'always_included': [ 'request.get_full_path()', 'request.method', ], 'vary_dependent': [ 'Accept-Language', 'Accept-Encoding', 'Cookie', 'User-Agent', ] }
|
5.3 超时时间优先级
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| def get_cache_timeout(response, default_timeout): """获取缓存超时时间的优先级""" max_age = get_max_age(response) if max_age is not None: if max_age == 0: return None return max_age if default_timeout: return default_timeout return settings.CACHE_MIDDLEWARE_SECONDS
|
6. 实际使用场景
6.1 全站缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| MIDDLEWARE = [ 'django.middleware.cache.UpdateCacheMiddleware', 'django.middleware.cache.FetchFromCacheMiddleware' ]
CACHE_MIDDLEWARE_ALIAS = 'default' CACHE_MIDDLEWARE_SECONDS = 600 CACHE_MIDDLEWARE_KEY_PREFIX = 'mysite'
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.redis.RedisCache', 'LOCATION': 'redis://127.0.0.1:6379/1', } }
|
6.2 视图级缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| from django.views.decorators.cache import cache_page from django.utils.decorators import method_decorator
@cache_page(60 * 15, key_prefix='api') def api_view(request): data = expensive_operation() return JsonResponse(data)
@method_decorator(cache_page(60 * 15), name='get') class ProductListView(ListView): model = Product template_name = 'products.html'
|
6.3 条件缓存
1 2 3 4 5 6 7 8 9 10 11 12 13
| def conditional_cache_view(request): response = render(request, 'template.html', context) if request.user.is_authenticated: response['Cache-Control'] = 'private, max-age=300' else: response['Cache-Control'] = 'public, max-age=3600' response['Vary'] = 'Cookie' return response
|
7. 性能优化和注意事项
7.1 Vary头的影响
1 2 3 4 5 6 7 8 9 10 11 12
| def bad_vary_example(request): response = render(request, 'template.html') response['Vary'] = 'User-Agent, Accept-Language, Accept-Encoding, Cookie' return response
def good_vary_example(request): response = render(request, 'template.html') response['Vary'] = 'Accept-Language' return response
|
7.2 缓存失效策略
1 2 3 4 5 6 7 8 9 10 11 12
| from django.core.cache import cache from django.core.cache.utils import make_template_fragment_key
def clear_cache_example(): cache_key = f"views.decorators.cache.cache_page.{hashlib.md5(b'/products/').hexdigest()}" cache.delete(cache_key) fragment_key = make_template_fragment_key('product_list', [category_id]) cache.delete(fragment_key)
|
8. 调试和监控
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class CacheMetricsMiddleware: def __init__(self, get_response): self.get_response = get_response self.cache_hits = 0 self.cache_misses = 0 def __call__(self, request): response = self.get_response(request) if hasattr(request, '_cache_update_cache'): if request._cache_update_cache: self.cache_misses += 1 else: self.cache_hits += 1 return response
|
总结
Django的缓存中间件系统通过分离获取和更新逻辑,提供了灵活而强大的页面级缓存功能。关键特点:
- 分阶段处理:请求阶段获取,响应阶段更新
- 安全第一:防止用户数据泄露
- 灵活配置:支持多种超时策略
- Vary支持:基于请求头的细粒度缓存
- 性能优化:减少数据库查询和模板渲染
正确使用这些中间件可以显著提升Django应用的性能。