IT策士 10余年一线大厂经验专注 IT 思维、架构、职场进阶。我也会在其它其它平台发布最新文章助你少走弯路。上一篇我们用 Redis 做消息代理配合 Celery 实现了异步任务队列。今天Redis 的另一个核心用途——缓存终于要登场了。你是否有这样的感受电商首页、商品列表页、商品详情页每次访问都要查数据库高峰期数据库压力山大页面加载越来越慢。尤其是商品详情页图片多、规格多、还有浏览量实时更新SQL 查询次数居高不下。这时候就需要缓存来拯救性能——把频繁访问又不常变的数据放进 Redis下次访问直接走内存快到飞起。今天我就带大家用 Django 内置的缓存框架 Redis给商品列表、详情页加上多级缓存同时处理好缓存的更新和失效策略让项目具备生产级的性能基础。一、缓存策略分析1.1 哪些数据该缓存电商项目中典型的可缓存数据原则读多写少才缓存强一致性数据不缓存。1.2 缓存更新策略常见的三种策略本系列采用Cache Aside模式这也是 Django 缓存框架的默认行为。二、配置 Django 缓存2.1 安装依赖上一篇我们已经装了 Redis 和 CeleryredisPython 客户端已有。如果没有执行2.2 配置 settings.py在django_ecommerce/settings.py中添加缓存配置# Django 缓存配置Redis CACHES{default:{BACKEND:django.core.cache.backends.redis.RedisCache,LOCATION:redis://127.0.0.1:6379/2,# 使用 2 号数据库与 Celery 隔离OPTIONS:{CLIENT_CLASS:django_redis.client.DefaultClient,},KEY_PREFIX:ecommerce,# 键前缀防止与其他项目冲突TIMEOUT:300,# 默认过期时间 300 秒 5 分钟}}注意Redis 有 16 个逻辑数据库编号 0-15。我们规划db 0Celery Brokerdb 1Celery Result Backenddb 2Django 缓存各司其职互不干扰。三、缓存商品分类树商品分类变化极低是最适合缓存的。我们在第 11 篇的分类视图中加入缓存逻辑。编辑apps/products/views.py中的category_tree视图from django.core.cacheimportcache from django.views.decorators.cacheimportcache_page def category_tree(request):# 先从缓存取cache_keycategory_treecategoriescache.get(cache_key)ifcategories is None:# 缓存未命中查数据库categorieslist(Category.objects.filter(parent__isnullTrue,is_activeTrue).prefetch_related(children__children).order_by(sort))# 写入缓存设置超时时间为 1 小时cache.set(cache_key, categories,timeout3600)returnrender(request,products/category_tree.html,{categories:categories})说明cache.get()从 Redis 读取如果 key 不存在返回None。命中缓存时完全不走数据库响应时间从 20ms 降到 2ms。timeout3600意味着 1 小时后缓存过期下次访问会重新查库。更新策略当管理员在 Admin 中修改分类时需要清除分类缓存。我们可以通过 Django 信号来实现。四、使用信号自动清除分类缓存在apps/products/models.py末尾添加信号处理from django.db.models.signalsimportpost_save, post_delete from django.dispatchimportreceiver from django.core.cacheimportcache receiver(post_save,senderCategory)receiver(post_delete,senderCategory)def clear_category_cache(sender, **kwargs):当分类数据变更时自动清除缓存 cache.delete(category_tree)这样只要在 Admin 中新增、修改或删除分类缓存立即清除下次访问分类页时自动重建。五、缓存商品列表带参数商品列表比分类复杂因为涉及筛选参数分类、搜索关键词、排序、页码。缓存键需要包含这些参数。编辑apps/products/views.py中的sku_list视图def sku_list(request): queryrequest.GET.get(q,).strip()category_idrequest.GET.get(category_id)sort_optionrequest.GET.get(sort,-create_time)page_numberrequest.GET.get(page,1)# 构建缓存键包含所有查询参数cache_keyfsku_list:q{query}:cat{category_id}:sort{sort_option}:page{page_number}# 尝试从缓存获取cached_datacache.get(cache_key)ifcached_data is not None:# 缓存命中直接返回returnrender(request,products/sku_list.html, cached_data)# 缓存未命中执行原查询逻辑skusSKU.objects.filter(is_activeTrue).select_related(spu__category).prefetch_related(images)# ... 搜索过滤、分类过滤、排序逻辑不变 ...ifquery: skusskus.filter(Q(name__icontainsquery)|Q(spu__name__icontainsquery)|Q(spu__brand__icontainsquery)|Q(spu__desc__icontainsquery))# ... 其余过滤排序分页代码同上 ...context{page_obj:page_obj,current_category:current_category,top_categories:top_categories,query:query,sort_option:sort_option,}# 写入缓存2 分钟过期适合列表页cache.set(cache_key, context,timeout120)returnrender(request,products/sku_list.html, context)缓存键示例sku_list:qiPhone:cat2:sortprice_asc:page1这样不同筛选条件互不干扰各自缓存各自的页面数据。六、缓存商品详情页商品详情页访问频率最高但数据也相对稳定价格调整、库存变化不频繁。我们给详情页加上短时缓存。编辑apps/products/views.py中的spu_detail视图def spu_detail(request, spu_id): cache_keyfspu_detail:{spu_id}# 尝试从缓存获取cached_datacache.get(cache_key)ifcached_data is not None:# 浏览量在缓存命中时仍要递增不能用缓存数据# 直接查库做原子更新但不影响页面渲染active_skusSKU.objects.filter(spu_idspu_id,is_activeTrue)active_skus.update(viewsF(views)1)returnrender(request,products/spu_detail.html, cached_data)spuget_object_or_404(SPU.objects.prefetch_related(skus__images),pkspu_id)# 浏览量 1active_skusspu.skus.filter(is_activeTrue)active_skus.update(viewsF(views)1)# 重新获取更新后的 SKU 列表skusspu.skus.filter(is_activeTrue).prefetch_related(images)specs_data{}forskuinskus:forkey, valueinsku.specs.items():ifkey notinspecs_data: specs_data[key]set()specs_data[key].add(value)specs_list{k: list(v)fork,vinspecs_data.items()}default_skuskus.first()context{spu:spu,skus:skus,specs:specs_list,default_sku:default_sku,}# 缓存 5 分钟300 秒cache.set(cache_key, context,timeout300)returnrender(request,products/spu_detail.html, context)注意浏览量递增是写操作不能被缓存“吞掉”。即使缓存命中我们仍然执行update(viewsF(views) 1)保证浏览量实时更新。更新策略当 SKU 的库存、价格发生变化时通过信号清除对应 SPU 的详情缓存。在apps/products/models.py中添加receiver(post_save,senderSKU)def clear_spu_cache_on_sku_change(sender, instance, **kwargs):SKU 变更时清除对应 SPU 的详情缓存 cache.delete(fspu_detail:{instance.spu_id})七、缓存模板片段选择性缓存有些页面大部分内容可以缓存但个别区域需要动态更新如导航栏的用户名、购物车数量。Django 提供了模板片段缓存。在templates/base.html中我们可以缓存整个商品分类导航因为它变化极低但保留用户信息动态显示{% load cache %}...!-- 缓存分类导航1小时过期 --{% cache3600category_nav %}{%forcatintop_categories %}aclassnav-linkhref{% url products:sku_list %}?category_id{{ cat.id }}{{cat.name}}/a{% endfor %}{% endcache %}但注意模板缓存需要在settings.py的TEMPLATES中启用缓存相关的 context processor。或者直接在视图层做缓存更可控。我们以视图层缓存为主模板片段缓存作为补充手段。八、使用 Redis 计数器优化浏览量在第 15 篇我们直接在详情页用F(views) 1更新数据库。如果并发很高这会给数据库带来大量写压力。更优方案是用 Redis 做计数器定时批量回写数据库。这里给出一个进阶思路本系列不作强制实现感兴趣可自行扩展每次访问详情页执行redis.incr(fviews:{sku_id})。编写一个 Celery 定时任务每 5 分钟将 Redis 中的浏览量汇总写入数据库。读取浏览量时先从 Redis 取实时值加上数据库中的基值。这个方案适合超高并发场景我们当前阶段保持F表达式更新即可。九、监控缓存命中率我们可以通过 Django 的缓存 API 来粗略统计命中率。在开发阶段可以在视图中添加临时日志importlogging loggerlogging.getLogger(cache)# 在 sku_list 中ifcached_data is not None: logger.info(f缓存命中: {cache_key})else: logger.info(f缓存未命中: {cache_key})生产环境可以使用django-redis提供的统计命令或 Redis 的INFO stats查看keyspace_hits和keyspace_missesredis-cli INFO stats|grepkeyspace控制台输出keyspace_hits:1234 keyspace_misses:56命中率 1234 / (1234 56) ≈ 95.6%相当理想。十、测试缓存效果10.1 启动服务确保 Redis 运行中然后启动 Djangopython manage.py runserver10.2 测试分类缓存首次访问/products/categories/控制台有 SQL 查询日志如果有开启。刷新页面控制台不再出现 SQL 查询页面秒开。进入 Admin 修改一个分类名称保存。再次访问分类页显示最新数据缓存已自动清除。终端对比# 首次[28/May/2026 09:15:00]GET /products/categories/ HTTP/1.12003456# SQL: SELECT ... FROM tb_category ...# 缓存命中[28/May/2026 09:15:05]GET /products/categories/ HTTP/1.12003456# 无 SQL 查询10.3 测试详情页缓存访问 iPhone 15 详情页/products/spu/1/首次加载正常。刷新页面响应速度明显加快。查看浏览量每次刷新都有递增缓存不影响浏览量更新。在 Admin 中修改该 SPU 下任意 SKU 的价格详情页再次访问时数据已更新信号清除了缓存。十一、缓存最佳实践总结十二、总结与下集预告今天我们用 Redis 为项目装上了缓存加速引擎配置了 Django 的 Redis 缓存后端实现了分类树、商品列表、商品详情页的多级缓存通过信号自动清除缓存保证数据一致性了解了模板片段缓存和 Redis 计数器的进阶用法。现在商品相关页面的访问速度有了质的飞跃。但缓存只是性能优化的一环代码中的异常处理和日志记录同样关键。第 25 篇我将带大家系统搭建Django 日志与异常处理体系包括分级日志、异常邮件告警、请求追踪等让项目的运维能力更上一层楼。想了解更多也可以去其它平台搜索「IT策士」一起升级 IT 思维 本文为《Django 从 0 到 1 打造完整电商平台》系列第 24 篇。
Django 从 0 到 1 打造完整电商平台:商品缓存优化(Redis)
发布时间:2026/5/27 6:40:06
IT策士 10余年一线大厂经验专注 IT 思维、架构、职场进阶。我也会在其它其它平台发布最新文章助你少走弯路。上一篇我们用 Redis 做消息代理配合 Celery 实现了异步任务队列。今天Redis 的另一个核心用途——缓存终于要登场了。你是否有这样的感受电商首页、商品列表页、商品详情页每次访问都要查数据库高峰期数据库压力山大页面加载越来越慢。尤其是商品详情页图片多、规格多、还有浏览量实时更新SQL 查询次数居高不下。这时候就需要缓存来拯救性能——把频繁访问又不常变的数据放进 Redis下次访问直接走内存快到飞起。今天我就带大家用 Django 内置的缓存框架 Redis给商品列表、详情页加上多级缓存同时处理好缓存的更新和失效策略让项目具备生产级的性能基础。一、缓存策略分析1.1 哪些数据该缓存电商项目中典型的可缓存数据原则读多写少才缓存强一致性数据不缓存。1.2 缓存更新策略常见的三种策略本系列采用Cache Aside模式这也是 Django 缓存框架的默认行为。二、配置 Django 缓存2.1 安装依赖上一篇我们已经装了 Redis 和 CeleryredisPython 客户端已有。如果没有执行2.2 配置 settings.py在django_ecommerce/settings.py中添加缓存配置# Django 缓存配置Redis CACHES{default:{BACKEND:django.core.cache.backends.redis.RedisCache,LOCATION:redis://127.0.0.1:6379/2,# 使用 2 号数据库与 Celery 隔离OPTIONS:{CLIENT_CLASS:django_redis.client.DefaultClient,},KEY_PREFIX:ecommerce,# 键前缀防止与其他项目冲突TIMEOUT:300,# 默认过期时间 300 秒 5 分钟}}注意Redis 有 16 个逻辑数据库编号 0-15。我们规划db 0Celery Brokerdb 1Celery Result Backenddb 2Django 缓存各司其职互不干扰。三、缓存商品分类树商品分类变化极低是最适合缓存的。我们在第 11 篇的分类视图中加入缓存逻辑。编辑apps/products/views.py中的category_tree视图from django.core.cacheimportcache from django.views.decorators.cacheimportcache_page def category_tree(request):# 先从缓存取cache_keycategory_treecategoriescache.get(cache_key)ifcategories is None:# 缓存未命中查数据库categorieslist(Category.objects.filter(parent__isnullTrue,is_activeTrue).prefetch_related(children__children).order_by(sort))# 写入缓存设置超时时间为 1 小时cache.set(cache_key, categories,timeout3600)returnrender(request,products/category_tree.html,{categories:categories})说明cache.get()从 Redis 读取如果 key 不存在返回None。命中缓存时完全不走数据库响应时间从 20ms 降到 2ms。timeout3600意味着 1 小时后缓存过期下次访问会重新查库。更新策略当管理员在 Admin 中修改分类时需要清除分类缓存。我们可以通过 Django 信号来实现。四、使用信号自动清除分类缓存在apps/products/models.py末尾添加信号处理from django.db.models.signalsimportpost_save, post_delete from django.dispatchimportreceiver from django.core.cacheimportcache receiver(post_save,senderCategory)receiver(post_delete,senderCategory)def clear_category_cache(sender, **kwargs):当分类数据变更时自动清除缓存 cache.delete(category_tree)这样只要在 Admin 中新增、修改或删除分类缓存立即清除下次访问分类页时自动重建。五、缓存商品列表带参数商品列表比分类复杂因为涉及筛选参数分类、搜索关键词、排序、页码。缓存键需要包含这些参数。编辑apps/products/views.py中的sku_list视图def sku_list(request): queryrequest.GET.get(q,).strip()category_idrequest.GET.get(category_id)sort_optionrequest.GET.get(sort,-create_time)page_numberrequest.GET.get(page,1)# 构建缓存键包含所有查询参数cache_keyfsku_list:q{query}:cat{category_id}:sort{sort_option}:page{page_number}# 尝试从缓存获取cached_datacache.get(cache_key)ifcached_data is not None:# 缓存命中直接返回returnrender(request,products/sku_list.html, cached_data)# 缓存未命中执行原查询逻辑skusSKU.objects.filter(is_activeTrue).select_related(spu__category).prefetch_related(images)# ... 搜索过滤、分类过滤、排序逻辑不变 ...ifquery: skusskus.filter(Q(name__icontainsquery)|Q(spu__name__icontainsquery)|Q(spu__brand__icontainsquery)|Q(spu__desc__icontainsquery))# ... 其余过滤排序分页代码同上 ...context{page_obj:page_obj,current_category:current_category,top_categories:top_categories,query:query,sort_option:sort_option,}# 写入缓存2 分钟过期适合列表页cache.set(cache_key, context,timeout120)returnrender(request,products/sku_list.html, context)缓存键示例sku_list:qiPhone:cat2:sortprice_asc:page1这样不同筛选条件互不干扰各自缓存各自的页面数据。六、缓存商品详情页商品详情页访问频率最高但数据也相对稳定价格调整、库存变化不频繁。我们给详情页加上短时缓存。编辑apps/products/views.py中的spu_detail视图def spu_detail(request, spu_id): cache_keyfspu_detail:{spu_id}# 尝试从缓存获取cached_datacache.get(cache_key)ifcached_data is not None:# 浏览量在缓存命中时仍要递增不能用缓存数据# 直接查库做原子更新但不影响页面渲染active_skusSKU.objects.filter(spu_idspu_id,is_activeTrue)active_skus.update(viewsF(views)1)returnrender(request,products/spu_detail.html, cached_data)spuget_object_or_404(SPU.objects.prefetch_related(skus__images),pkspu_id)# 浏览量 1active_skusspu.skus.filter(is_activeTrue)active_skus.update(viewsF(views)1)# 重新获取更新后的 SKU 列表skusspu.skus.filter(is_activeTrue).prefetch_related(images)specs_data{}forskuinskus:forkey, valueinsku.specs.items():ifkey notinspecs_data: specs_data[key]set()specs_data[key].add(value)specs_list{k: list(v)fork,vinspecs_data.items()}default_skuskus.first()context{spu:spu,skus:skus,specs:specs_list,default_sku:default_sku,}# 缓存 5 分钟300 秒cache.set(cache_key, context,timeout300)returnrender(request,products/spu_detail.html, context)注意浏览量递增是写操作不能被缓存“吞掉”。即使缓存命中我们仍然执行update(viewsF(views) 1)保证浏览量实时更新。更新策略当 SKU 的库存、价格发生变化时通过信号清除对应 SPU 的详情缓存。在apps/products/models.py中添加receiver(post_save,senderSKU)def clear_spu_cache_on_sku_change(sender, instance, **kwargs):SKU 变更时清除对应 SPU 的详情缓存 cache.delete(fspu_detail:{instance.spu_id})七、缓存模板片段选择性缓存有些页面大部分内容可以缓存但个别区域需要动态更新如导航栏的用户名、购物车数量。Django 提供了模板片段缓存。在templates/base.html中我们可以缓存整个商品分类导航因为它变化极低但保留用户信息动态显示{% load cache %}...!-- 缓存分类导航1小时过期 --{% cache3600category_nav %}{%forcatintop_categories %}aclassnav-linkhref{% url products:sku_list %}?category_id{{ cat.id }}{{cat.name}}/a{% endfor %}{% endcache %}但注意模板缓存需要在settings.py的TEMPLATES中启用缓存相关的 context processor。或者直接在视图层做缓存更可控。我们以视图层缓存为主模板片段缓存作为补充手段。八、使用 Redis 计数器优化浏览量在第 15 篇我们直接在详情页用F(views) 1更新数据库。如果并发很高这会给数据库带来大量写压力。更优方案是用 Redis 做计数器定时批量回写数据库。这里给出一个进阶思路本系列不作强制实现感兴趣可自行扩展每次访问详情页执行redis.incr(fviews:{sku_id})。编写一个 Celery 定时任务每 5 分钟将 Redis 中的浏览量汇总写入数据库。读取浏览量时先从 Redis 取实时值加上数据库中的基值。这个方案适合超高并发场景我们当前阶段保持F表达式更新即可。九、监控缓存命中率我们可以通过 Django 的缓存 API 来粗略统计命中率。在开发阶段可以在视图中添加临时日志importlogging loggerlogging.getLogger(cache)# 在 sku_list 中ifcached_data is not None: logger.info(f缓存命中: {cache_key})else: logger.info(f缓存未命中: {cache_key})生产环境可以使用django-redis提供的统计命令或 Redis 的INFO stats查看keyspace_hits和keyspace_missesredis-cli INFO stats|grepkeyspace控制台输出keyspace_hits:1234 keyspace_misses:56命中率 1234 / (1234 56) ≈ 95.6%相当理想。十、测试缓存效果10.1 启动服务确保 Redis 运行中然后启动 Djangopython manage.py runserver10.2 测试分类缓存首次访问/products/categories/控制台有 SQL 查询日志如果有开启。刷新页面控制台不再出现 SQL 查询页面秒开。进入 Admin 修改一个分类名称保存。再次访问分类页显示最新数据缓存已自动清除。终端对比# 首次[28/May/2026 09:15:00]GET /products/categories/ HTTP/1.12003456# SQL: SELECT ... FROM tb_category ...# 缓存命中[28/May/2026 09:15:05]GET /products/categories/ HTTP/1.12003456# 无 SQL 查询10.3 测试详情页缓存访问 iPhone 15 详情页/products/spu/1/首次加载正常。刷新页面响应速度明显加快。查看浏览量每次刷新都有递增缓存不影响浏览量更新。在 Admin 中修改该 SPU 下任意 SKU 的价格详情页再次访问时数据已更新信号清除了缓存。十一、缓存最佳实践总结十二、总结与下集预告今天我们用 Redis 为项目装上了缓存加速引擎配置了 Django 的 Redis 缓存后端实现了分类树、商品列表、商品详情页的多级缓存通过信号自动清除缓存保证数据一致性了解了模板片段缓存和 Redis 计数器的进阶用法。现在商品相关页面的访问速度有了质的飞跃。但缓存只是性能优化的一环代码中的异常处理和日志记录同样关键。第 25 篇我将带大家系统搭建Django 日志与异常处理体系包括分级日志、异常邮件告警、请求追踪等让项目的运维能力更上一层楼。想了解更多也可以去其它平台搜索「IT策士」一起升级 IT 思维 本文为《Django 从 0 到 1 打造完整电商平台》系列第 24 篇。