当前位置: 首页 > news >正文

Django 从 0 到 1 打造完整电商平台:商品缓存优化(Redis)

IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我也会在其它其它平台发布最新文章,助你少走弯路。


上一篇我们用 Redis 做消息代理,配合 Celery 实现了异步任务队列。今天,Redis 的另一个核心用途——缓存,终于要登场了。

你是否有这样的感受:电商首页、商品列表页、商品详情页,每次访问都要查数据库,高峰期数据库压力山大,页面加载越来越慢。尤其是商品详情页,图片多、规格多、还有浏览量实时更新,SQL 查询次数居高不下。这时候就需要缓存来拯救性能——把频繁访问又不常变的数据放进 Redis,下次访问直接走内存,快到飞起。

今天我就带大家用 Django 内置的缓存框架 + Redis,给商品列表、详情页加上多级缓存,同时处理好缓存的更新和失效策略,让项目具备生产级的性能基础。


一、缓存策略分析

1.1 哪些数据该缓存?

电商项目中,典型的可缓存数据:

原则:读多写少才缓存,强一致性数据不缓存。

1.2 缓存更新策略

常见的三种策略:

本系列采用Cache Aside模式,这也是 Django 缓存框架的默认行为。


二、配置 Django 缓存

2.1 安装依赖

上一篇我们已经装了 Redis 和 Celery,redisPython 客户端已有。如果没有,执行:

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 0:Celery Broker

  • db 1:Celery Result Backend

  • db 2:Django 缓存

各司其职,互不干扰。


三、缓存商品分类树

商品分类变化极低,是最适合缓存的。我们在第 11 篇的分类视图中加入缓存逻辑。

编辑apps/products/views.py中的category_tree视图:

from django.core.cacheimportcache from django.views.decorators.cacheimportcache_page def category_tree(request):# 先从缓存取cache_key='category_tree'categories=cache.get(cache_key)ifcategories is None:# 缓存未命中,查数据库categories=list(Category.objects.filter(parent__isnull=True,is_active=True).prefetch_related('children__children').order_by('sort'))# 写入缓存,设置超时时间为 1 小时cache.set(cache_key, categories,timeout=3600)returnrender(request,'products/category_tree.html',{'categories':categories})

说明

  • cache.get()从 Redis 读取,如果 key 不存在返回None

  • 命中缓存时,完全不走数据库,响应时间从 20ms 降到 2ms。

  • timeout=3600意味着 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,sender=Category)@receiver(post_delete,sender=Category)def clear_category_cache(sender, **kwargs):"""当分类数据变更时,自动清除缓存""" cache.delete('category_tree')

这样,只要在 Admin 中新增、修改或删除分类,缓存立即清除,下次访问分类页时自动重建。


五、缓存商品列表(带参数)

商品列表比分类复杂,因为涉及筛选参数(分类、搜索关键词、排序、页码)。缓存键需要包含这些参数。

编辑apps/products/views.py中的sku_list视图:

def sku_list(request): query=request.GET.get('q','').strip()category_id=request.GET.get('category_id')sort_option=request.GET.get('sort','-create_time')page_number=request.GET.get('page',1)# 构建缓存键(包含所有查询参数)cache_key=f'sku_list:q={query}:cat={category_id}:sort={sort_option}:page={page_number}'# 尝试从缓存获取cached_data=cache.get(cache_key)ifcached_data is not None:# 缓存命中,直接返回returnrender(request,'products/sku_list.html', cached_data)# 缓存未命中,执行原查询逻辑skus=SKU.objects.filter(is_active=True).select_related('spu__category').prefetch_related('images')# ... 搜索过滤、分类过滤、排序逻辑不变 ...ifquery: skus=skus.filter(Q(name__icontains=query)|Q(spu__name__icontains=query)|Q(spu__brand__icontains=query)|Q(spu__desc__icontains=query))# ... 其余过滤排序分页代码同上 ...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,timeout=120)returnrender(request,'products/sku_list.html', context)

缓存键示例

sku_list:q=iPhone:cat=2:sort=price_asc:page=1

这样不同筛选条件互不干扰,各自缓存各自的页面数据。


六、缓存商品详情页

商品详情页访问频率最高,但数据也相对稳定(价格调整、库存变化不频繁)。我们给详情页加上短时缓存。

编辑apps/products/views.py中的spu_detail视图:

def spu_detail(request, spu_id): cache_key=f'spu_detail:{spu_id}'# 尝试从缓存获取cached_data=cache.get(cache_key)ifcached_data is not None:# 浏览量在缓存命中时仍要递增(不能用缓存数据)# 直接查库做原子更新,但不影响页面渲染active_skus=SKU.objects.filter(spu_id=spu_id,is_active=True)active_skus.update(views=F('views')+1)returnrender(request,'products/spu_detail.html', cached_data)spu=get_object_or_404(SPU.objects.prefetch_related('skus__images'),pk=spu_id)# 浏览量 +1active_skus=spu.skus.filter(is_active=True)active_skus.update(views=F('views')+1)# 重新获取更新后的 SKU 列表skus=spu.skus.filter(is_active=True).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_sku=skus.first()context={'spu':spu,'skus':skus,'specs':specs_list,'default_sku':default_sku,}# 缓存 5 分钟(300 秒)cache.set(cache_key, context,timeout=300)returnrender(request,'products/spu_detail.html', context)

注意:浏览量递增是写操作,不能被缓存“吞掉”。即使缓存命中,我们仍然执行update(views=F('views') + 1),保证浏览量实时更新。

更新策略:当 SKU 的库存、价格发生变化时,通过信号清除对应 SPU 的详情缓存。

apps/products/models.py中添加:

@receiver(post_save,sender=SKU)def clear_spu_cache_on_sku_change(sender, instance, **kwargs):"""SKU 变更时清除对应 SPU 的详情缓存""" cache.delete(f'spu_detail:{instance.spu_id}')

七、缓存模板片段(选择性缓存)

有些页面,大部分内容可以缓存,但个别区域需要动态更新(如导航栏的用户名、购物车数量)。Django 提供了模板片段缓存

templates/base.html中,我们可以缓存整个商品分类导航(因为它变化极低),但保留用户信息动态显示:

{% load cache %}...<!-- 缓存分类导航,1小时过期 -->{% cache3600category_nav %}{%forcatintop_categories %}<aclass="nav-link"href="{% url 'products:sku_list' %}?category_id={{ cat.id }}">{{cat.name}}</a>{% endfor %}{% endcache %}

但注意,模板缓存需要在settings.pyTEMPLATES中启用缓存相关的 context processor。或者直接在视图层做缓存更可控。我们以视图层缓存为主,模板片段缓存作为补充手段。


八、使用 Redis 计数器优化浏览量

在第 15 篇,我们直接在详情页用F('views') + 1更新数据库。如果并发很高,这会给数据库带来大量写压力。更优方案是用 Redis 做计数器,定时批量回写数据库

这里给出一个进阶思路(本系列不作强制实现,感兴趣可自行扩展):

  1. 每次访问详情页,执行redis.incr(f'views:{sku_id}')

  2. 编写一个 Celery 定时任务,每 5 分钟将 Redis 中的浏览量汇总写入数据库。

  3. 读取浏览量时,先从 Redis 取实时值,加上数据库中的基值。

这个方案适合超高并发场景,我们当前阶段保持F表达式更新即可。


九、监控缓存命中率

我们可以通过 Django 的缓存 API 来粗略统计命中率。在开发阶段,可以在视图中添加临时日志:

importlogging logger=logging.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_hitskeyspace_misses

redis-cli INFO stats|grepkeyspace

控制台输出:

keyspace_hits:1234 keyspace_misses:56

命中率 = 1234 / (1234 + 56) ≈ 95.6%,相当理想。


十、测试缓存效果

10.1 启动服务

确保 Redis 运行中,然后启动 Django:

python manage.py runserver
10.2 测试分类缓存
  1. 首次访问/products/categories/,控制台有 SQL 查询日志(如果有开启)。

  2. 刷新页面,控制台不再出现 SQL 查询,页面秒开。

  3. 进入 Admin 修改一个分类名称,保存。

  4. 再次访问分类页,显示最新数据(缓存已自动清除)。

终端对比:

# 首次:[28/May/2026 09:15:00]"GET /products/categories/ HTTP/1.1"2003456# SQL: SELECT ... FROM tb_category ...# 缓存命中:[28/May/2026 09:15:05]"GET /products/categories/ HTTP/1.1"2003456# 无 SQL 查询
10.3 测试详情页缓存
  1. 访问 iPhone 15 详情页/products/spu/1/,首次加载正常。

  2. 刷新页面,响应速度明显加快。

  3. 查看浏览量:每次刷新都有递增(缓存不影响浏览量更新)。

  4. 在 Admin 中修改该 SPU 下任意 SKU 的价格,详情页再次访问时数据已更新(信号清除了缓存)。


十一、缓存最佳实践总结


十二、总结与下集预告

今天我们用 Redis 为项目装上了缓存加速引擎:

  • 配置了 Django 的 Redis 缓存后端;

  • 实现了分类树、商品列表、商品详情页的多级缓存;

  • 通过信号自动清除缓存,保证数据一致性;

  • 了解了模板片段缓存和 Redis 计数器的进阶用法。

现在,商品相关页面的访问速度有了质的飞跃。但缓存只是性能优化的一环,代码中的异常处理和日志记录同样关键。第 25 篇,我将带大家系统搭建Django 日志与异常处理体系,包括分级日志、异常邮件告警、请求追踪等,让项目的运维能力更上一层楼。

想了解更多也可以去其它平台搜索「IT策士」,一起升级 IT 思维 !


本文为《Django 从 0 到 1 打造完整电商平台》系列第 24 篇。

http://www.jsqmd.com/news/895080/

相关文章:

  • 智能体评估误区:为何Token消耗不是衡量AI工作价值的关键指标
  • 内网环境RPA自动化实践:自定义API与离线运行方案
  • 48小时基于Google Cloud构建多智能体AI系统:架构、实现与优化
  • 领域特定AI聊天机器人架构设计:从通用模型到专属专家的构建指南
  • 单片机+RA8889 | RUI Builder 可视化 UI 工具 + 自研多国语言显示方案
  • 保姆级教程:在AMD Ryzen电脑上用VMware 16.2.5搞定macOS Monterey (12.x) 虚拟机
  • 纯视觉GUI智能体Mano-P:OSWorld榜首开源项目解析与实践
  • 八年Java老兵,三个月投了上百份简历没找到下家——2026年的招聘市场到底怎么了?
  • Seatable 4.3 数据迁移翻车实录:从Ubuntu到CentOS,我踩了哪些坑?
  • 如何搭建第一个AI智能体?零代码Coze完整教程
  • 从74LS283到Verilog:手把手教你用硬件描述语言‘复刻’经典BCD加法器(附完整代码与Testbench)
  • springboot - jar包启动指定具体的jdk执行
  • 构建语音控制AI智能体:从LLM意图解析到安全文件操作的实战指南
  • AI代理循环成本优化:Lumin本地代理层实现请求瘦身与缓存压缩
  • STM32F103C8T6串口收发控制LED灯:一个标准库项目搞定硬件交互与调试
  • 面试官让我现场写代码,我却跟他聊了半小时哲学——一个非典型计算机研究生的自白
  • 面试题 - GIL全局解释器锁 :为什么Python多线程不能利用多核?GIL对I/O密集和CPU密集任务的影响?如何绕过GIL(多进程、C扩展)
  • 使用Taotoken后API调用延迟与稳定性有哪些可观测的改善
  • 修复误删系统文件导致电脑屏幕有时黑屏问题
  • ADHD幸存者偏差
  • 【从零开始学习Go语言 | 第六篇】Go语言基础之流程控制
  • 2024年十大技术趋势抢先看
  • HSM - 分层状态机
  • 2026年5月鸽哒IM即时通讯原生双端APP源码解析:支持视频通话与实时语音(附实测数据/可二开
  • 活久见的突发:AI比人贵了?微软禁自家工程师用AI
  • 2026年恒温恒湿试验箱厂家筛选与老化试验箱厂家推荐 从研发产能到定制服务全方位解析选型要点 - 栗子测评
  • Android内存泄漏检测利器:LeakCanary深度解析与实践指南
  • 淘来的二手Mellanox CX4 25G网卡,用lspci命令怎么快速验货和看关键信息?
  • Unity PC端内嵌网页开发避坑指南:从Embedded Browser 3.1.0插件安装到与Vue页面交互
  • 构建可靠多智能体系统:从记忆、验证到工具链的工程实践