Django 缓存中间件

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):
"""从缓存中检查并返回页面"""

# 1. 只缓存GET和HEAD请求
if request.method not in ('GET', 'HEAD'):
request._cache_update_cache = False
return None # 不检查缓存

# 2. 尝试获取GET请求的缓存
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)

# 3. 如果没找到且是HEAD请求,尝试查找HEAD专用缓存
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 # 没有缓存,需要重新构建

# 4. 缓存命中,返回缓存的响应
request._cache_update_cache = False
return response

2.2 缓存键生成机制

1
2
3
4
5
6
7
8
9
10
11
# get_cache_key函数会考虑以下因素:
def generate_cache_key_example(request):
factors = [
request.get_full_path(), # URL路径和查询参数
request.method, # HTTP方法
request.META.get('HTTP_ACCEPT_LANGUAGE'), # 语言偏好
request.META.get('HTTP_USER_AGENT'), # 用户代理(如果在Vary中)
request.COOKIES, # Cookie(如果在Vary中)
]
# 根据响应的Vary头决定哪些请求头影响缓存键
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):
"""设置缓存(如果需要)"""

# 1. 检查是否需要更新缓存
if not self._should_update_cache(request, response):
return response

# 2. 不缓存流式响应和非200/304状态码
if response.streaming or response.status_code not in (200, 304):
return response

# 3. 安全检查:不缓存可能包含用户特定信息的响应
if not request.COOKIES and response.cookies and has_vary_header(response, 'Cookie'):
return response

# 4. 获取缓存超时时间
timeout = get_max_age(response) # 从Cache-Control获取
if timeout is None:
timeout = self.cache_timeout # 使用默认值
elif timeout == 0:
return response # max-age=0,不缓存

# 5. 设置响应头
patch_response_headers(response, timeout)

# 6. 缓存响应
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'):
# 请求无Cookie + 响应设置了Cookie + 响应Vary包含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

# 处理key_prefix参数
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

# 处理cache_alias参数
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

# 处理cache_timeout参数
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
# CacheMiddleware可以作为装饰器使用
from django.views.decorators.cache import cache_page

@cache_page(60 * 15) # 缓存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()', # URL + 查询参数
'request.method', # HTTP方法
],
'vary_dependent': [
'Accept-Language', # 如果Vary包含
'Accept-Encoding', # 如果Vary包含
'Cookie', # 如果Vary包含
'User-Agent', # 如果Vary包含
]
}

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):
"""获取缓存超时时间的优先级"""

# 1. 优先:响应的Cache-Control max-age
max_age = get_max_age(response)
if max_age is not None:
if max_age == 0:
return None # 不缓存
return max_age

# 2. 其次:中间件配置的超时时间
if default_timeout:
return default_timeout

# 3. 最后:全局配置
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
# settings.py
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' # 5分钟
else:
response['Cache-Control'] = 'public, max-age=3600' # 1小时

# 设置Vary头,基于用户状态缓存
response['Vary'] = 'Cookie'

return response

7. 性能优化和注意事项

7.1 Vary头的影响

1
2
3
4
5
6
7
8
9
10
11
12
# 过多的Vary头会导致缓存效率低下
def bad_vary_example(request):
response = render(request, 'template.html')
response['Vary'] = 'User-Agent, Accept-Language, Accept-Encoding, Cookie'
# 这会导致每个用户代理+语言+编码+Cookie组合都有单独缓存
return response

# 更好的做法:只Vary真正影响内容的头
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的缓存中间件系统通过分离获取和更新逻辑,提供了灵活而强大的页面级缓存功能。关键特点:

  1. 分阶段处理:请求阶段获取,响应阶段更新
  2. 安全第一:防止用户数据泄露
  3. 灵活配置:支持多种超时策略
  4. Vary支持:基于请求头的细粒度缓存
  5. 性能优化:减少数据库查询和模板渲染

正确使用这些中间件可以显著提升Django应用的性能。


Django 缓存中间件
https://luffy997.github.io/2025/07/19/Django缓存中间件/
作者
Luffy997
发布于
2025年7月19日
许可协议