第 10 章 · 缓存与 Celery 异步入门
本章目标:使用 Redis 作为 Django 缓存后端;掌握视图缓存、模板片段缓存与低层 cache API;入门 Celery 分布式任务队列;用 @shared_task 实现异步发邮件与订单统计;在本地 Docker 完成 shop-demo 概念验证,理解生产架构分工。
学时建议:5 小时(含 2 小时 Docker + Celery 跟练)
前置:本模块 ch05 ORM 与查询;ch07 API;ch09 信号(异步副作用入口)。Redis 概念见 ops-deploy 相关章节。
10.1 为什么需要缓存与异步
| 问题 | 同步方案痛点 | 本 chapter 方案 |
|---|---|---|
| 商品列表重复查库 | 每次请求打满 MySQL | Redis 页面/查询缓存 |
| 注册后发欢迎邮件 | 用户等 SMTP 3~5 秒 | Celery 后台发信 |
| 日订单汇总报表 | 管理页超时 | 定时任务 @shared_task |
┌─────────────┐
HTTP 请求 ───────►│ Django │
│ views │
└──────┬──────┘
│ cache.get/set
▼
┌─────────────┐
│ Redis │◄──── Celery Worker
└─────────────┘ 读/写缓存、发邮件
▲
┌──────┴──────┐
│ MySQL │
└─────────────┘
shop-demo 为教学项目;Redis 地址示例 redis://127.0.0.1:6379/1,邮件使用控制台 backend,不连接真实业务中间件。
10.2 本地 Redis 与 docker-compose
shop-demo/docker-compose.dev.yml:
services:
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
redis_data:
docker compose -f docker-compose.dev.yml up -d redis
redis-cli ping # PONG
10.3 Django 缓存配置
pip install django-redis
settings.py:
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
"KEY_PREFIX": "shopdemo",
"TIMEOUT": 300, # 默认 5 分钟
}
}
验证:
# python manage.py shell
from django.core.cache import cache
cache.set("ping", "pong", 60)
cache.get("ping") # 'pong'
| 方法 | 说明 |
|---|---|
cache.set(key, value, timeout) | 写入 |
cache.get(key, default=None) | 读取 |
cache.delete(key) | 删除 |
cache.get_or_set(key, callable, timeout) | 读或计算后写 |
10.4 缓存商品列表查询
# catalog/services.py
from django.core.cache import cache
from .models import Product
PRODUCT_LIST_KEY = "product_list:published:v1"
def get_published_products():
data = cache.get(PRODUCT_LIST_KEY)
if data is not None:
return data
qs = list(
Product.objects.filter(is_published=True)
.select_related("category")
.values("id", "name", "price", "slug")[:50]
)
cache.set(PRODUCT_LIST_KEY, qs, timeout=120)
return qs
失效策略(与 ch09 信号结合):
from django.core.cache import cache
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from catalog.models import Product
from catalog.services import PRODUCT_LIST_KEY
@receiver([post_save, post_delete], sender=Product)
def invalidate_product_list_cache(sender, **kwargs):
cache.delete(PRODUCT_LIST_KEY)
原则:先更新库,再删缓存(cache-aside)。
10.5 页面缓存
全页缓存适合变化少、个性化低的页面(如帮助页、公开商品分类树)。
from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator
@cache_page(60 * 5) # 5 分钟
def category_tree(request):
...
# CBV
@method_decorator(cache_page(60 * 5), name="dispatch")
class PublicProductListView(ListView):
model = Product
注意:
- 含用户个性化内容的页面不要整页缓存
- API(DRF)一般用查询缓存而非
cache_page - 使用
@vary_on_cookie可按 Cookie 区分缓存键
10.6 模板片段缓存
{% load cache %}
{% cache 300 product_sidebar category.id %}
<aside>
{% for p in hot_products %}
<li>{{ p.name }}</li>
{% endfor %}
</aside>
{% endcache %}
300 为秒数;缓存键自动包含片段内容与 category.id。