## 1. 项目背景与需求分析
### 1.1 背景
招生计划管理页面(`/data-maintenance/enrollment-plan/`)作为高考志愿填报系统的数据维护核心功能之一,承担着展示和管理招生计划数据的重要职责。随着数据量的不断增长,该页面在频繁访问时可能面临性能瓶颈,影响用户体验。
### 1.2 性能挑战
通过代码分析,发现以下性能问题:
1. 每次页面加载都需要执行多个数据库查询来获取筛选选项(学科、批次、专业等)
2. 查询结果未进行缓存,重复查询相同条件时会重复执行数据库操作
3. 数据量较大时,这些数据库查询会显著增加页面加载时间
### 1.3 需求目标
为招生计划管理页面实施Redis缓存加速,具体目标:
- 减少页面加载时的数据库查询次数
- 提升页面响应速度
- 保持数据的一致性和实时性
- 与现有缓存机制保持一致的设计风格
## 2. Redis缓存实施方案设计
### 2.1 缓存架构
采用与`summary/search/`页面相同的多级缓存架构:
- **筛选选项缓存**:缓存页面所需的所有筛选下拉选项数据
- **查询结果缓存**:缓存用户查询后的分页数据结果
### 2.2 缓存分类
| 缓存类型 | 缓存键模式 | 过期时间 | 缓存内容 | 缓存时机 |
|---------|----------|---------|---------|--------|
| 筛选选项缓存 | `enrollment_plan_filter_options` | 2小时 | 学科、批次、专业等筛选选项列表 | 首次访问页面时 |
| 查询结果缓存 | `enrollment_plan_list:{hash}` | 10分钟 | 特定查询条件下的分页数据结果 | 用户执行查询时 |
### 2.3 键设计策略
- 筛选选项缓存:使用固定键名`enrollment_plan_filter_options`
- 查询结果缓存:使用`enrollment_plan_list:{hash}`格式,其中`hash`由查询参数生成的MD5值
- 参数排序后再生成哈希,确保相同参数不同顺序生成相同的键
### 2.4 过期策略
- 筛选选项:2小时过期,适合相对稳定的数据
- 查询结果:10分钟过期,平衡实时性和性能
## 3. 实施步骤
### 3.1 环境准备
确保项目已正确配置Redis缓存:
- 项目已配置Django缓存后端,包含`query_cache`缓存别名
- Redis服务已安装并运行
### 3.2 Django缓存配置
确保在`settings.py`中正确配置了Redis缓存后端:
```python
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
},
'query_cache': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/2',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
```
### 3.3 缓存工具类
利用现有的`cache_utils.py`中的工具函数:
- `generate_cache_key`: 用于生成唯一的缓存键
- `caches['query_cache']`: 用于访问专门的查询缓存
### 3.4 代码实现
#### 3.4.1 筛选选项缓存实现
在`data_maintenance.py`文件中添加筛选选项缓存函数:
```python
def get_enrollment_plan_filter_options():
"""
获取并缓存招生计划筛选选项
"""
# 尝试从Redis缓存获取筛选选项
query_cache = caches['query_cache']
key = "enrollment_plan_filter_options"
options = query_cache.get(key)
if options is None:
options = {
'disciplines': list(models.EnrollmentPlan.objects.values_list('discipline', flat=True).distinct().order_by('discipline')),
'batches': list(models.EnrollmentPlan.objects.values_list('batch_name', flat=True).distinct().order_by('batch_name')),
'categories': list(models.EnrollmentPlan.objects.values_list('subject_category', flat=True).distinct().order_by('subject_category')),
'natures': list(models.EnrollmentPlan.objects.values_list('plan_nature', flat=True).distinct().order_by('plan_nature')),
'subjects': list(models.EnrollmentPlan.objects.values_list('alternative_subject', flat=True).distinct().order_by('alternative_subject')),
# 限制专业名称数量以减少缓存大小
'majors': list(models.EnrollmentPlan.objects.values_list('major_name', flat=True).distinct().order_by('major_name')[:1000]),
# 添加缓存时间戳
'cache_timestamp': timezone.now().isoformat()
}
# 保存到Redis缓存,设置2小时过期时间
query_cache.set(key, options, 7200) # 缓存2小时
return options
```
#### 3.4.2 查询结果缓存实现
修改`enrollment_plan_list`函数,添加查询结果缓存逻辑:
```python
def enrollment_plan_list(request):
# 获取搜索和筛选参数
key = request.GET.get('key', '').strip()
discipline_filter = request.GET.get('discipline', '')
batch_filter = request.GET.get('batch', '')
category_filter = request.GET.get('category', '')
nature_filter = request.GET.get('nature', '')
subject_filter = request.GET.get('subject', '')
selected_majors = request.GET.getlist('major')
page = request.GET.get('page', '1')
# 生成缓存键,包含查询参数和分页信息
cache_params = {
'key': key,
'discipline': discipline_filter,
'batch': batch_filter,
'category': category_filter,
'nature': nature_filter,
'subject': subject_filter,
'major': selected_majors,
'page': page
}
cache_key = generate_cache_key("enrollment_plan_list", cache_params)
# 尝试从查询缓存获取结果
query_cache = caches['query_cache']
cached_result = query_cache.get(cache_key)
if cached_result:
plans_page = cached_result['plans_page']
total_count = cached_result['total_count']
else:
# 构建查询...
# 执行数据库查询...
# 分页处理...
# 将查询结果转换为可序列化的形式
serializable_plans = []
for plan in plans_page:
serializable_plan = {
'id': plan.id,
'institution_name': plan.institution_name,
'major_name': plan.major_name,
'discipline': plan.discipline,
'batch_name': plan.batch_name,
'subject_category': plan.subject_category,
'plan_nature': plan.plan_nature,
'alternative_subject': plan.alternative_subject,
'plan_number': plan.plan_number,
'year': plan.year
}
serializable_plans.append(serializable_plan)
# 缓存查询结果,设置10分钟过期时间
query_cache.set(cache_key, {
'plans_page': serializable_plans,
'total_count': total_count
}, 600) # 缓存10分钟
plans_page = serializable_plans
# 获取筛选选项(从缓存)
options = get_enrollment_plan_filter_options()
# 准备上下文数据并渲染模板...
```
## 4. 性能提升预期
### 4.1 性能对比分析
| 操作类型 | 优化前 | 优化后 | 预期提升倍数 |
|---------|-------|-------|------------|
| 首次加载筛选选项 | 多次数据库查询 | 单次数据库查询 | 5-10倍 |
| 重复加载筛选选项 | 多次数据库查询 | 内存缓存读取 | 100-500倍 |
| 重复查询相同条件 | 完整数据库查询 | 内存缓存读取 | 20-100倍 |
### 4.2 实际效果评估
基于类似页面的优化经验,预计页面加载时间将从原来的:
- 首次加载:降低约60-80%
- 重复访问:降低约90-95%
## 5. 缓存维护与监控
### 5.1 缓存一致性维护
当招生计划数据发生变更时,需要清除相关缓存以保持数据一致性:
```python
# 在数据变更相关的视图或信号处理函数中
from django.core.cache import caches
def clear_enrollment_plan_cache():
"""清除招生计划相关缓存"""
query_cache = caches['query_cache']
# 清除筛选选项缓存
query_cache.delete("enrollment_plan_filter_options")
# 清除所有查询结果缓存(使用模式匹配)
query_cache.delete_pattern("enrollment_plan_list:*")
```
### 5.2 缓存监控
建议通过Redis提供的监控工具或自定义监控函数来监控缓存使用情况:
```python
from app.utils.cache_utils import get_cache_stats
def get_enrollment_cache_stats():
"""获取招生计划缓存统计信息"""
stats = get_cache_stats()
# 可以添加特定于招生计划缓存的统计逻辑
return stats
```
## 6. 测试与验证
### 6.1 测试脚本
已创建测试脚本`test_enrollment_cache.py`用于验证缓存功能的正确性,包括:
- 筛选选项缓存的读取性能测试
- 查询结果缓存的读写测试
- 缓存键验证
### 6.2 浏览器验证
建议在实际环境中进行以下验证:
1. 访问`/data-maintenance/enrollment-plan/`页面,记录加载时间
2. 刷新页面,验证二次加载速度显著提升
3. 尝试不同的筛选条件,验证缓存键的正确性
## 7. 部署与注意事项
### 7.1 部署前检查
- 确保Redis服务已安装并正常运行
- 验证Django缓存配置正确无误
- 检查项目依赖是否包含`django-redis`
### 7.2 注意事项
- 缓存过期时间设置:筛选选项缓存2小时,查询结果缓存10分钟
- 当招生计划数据批量更新时,需要手动清除缓存
- 对于专业名称数量较大的情况,已限制最多缓存1000个专业,超出部分需要动态加载
## 8. 总结
通过为`/data-maintenance/enrollment-plan/`页面实施Redis缓存加速,可以显著提升页面加载速度和用户体验,同时降低数据库服务器的负载。缓存方案遵循了项目现有的设计模式,确保了代码的一致性和可维护性。
主要优势:
1. 实现了多级缓存策略,针对不同类型数据采用不同的缓存策略
2. 缓存键设计合理,确保缓存命中率和数据一致性
3. 过期时间设置合理,平衡了实时性和性能需求
4. 与现有缓存机制保持一致,便于维护
通过此优化,招生计划管理页面的响应速度将得到显著提升,尤其是在频繁访问和重复查询相同条件的场景下效果更为明显。
1660

被折叠的 条评论
为什么被折叠?



