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

商品详情实现与缓存问题(穿透、击穿、雪崩)解决方案

一、学习目标

  • 商品详情功能实现
  • 缓存同步处理
  • 缓存穿透问题解决
  • 缓存击穿问题解决
  • 缓存雪崩问题解决

二、商品详情的实现方案

2.1 网页静态化方案

网页静态化是提升商品详情页访问性能的重要方式,核心步骤如下:

  1. 创建商品详情的 Thymeleaf 模板,定义页面展示结构;
  2. 开发消息接收服务,当商品数据变更时,触发静态页面生成;
  3. 搭建 Nginx 服务器,直接返回生成的静态页面,减少后端服务压力。

2.2 Redis 缓存商品信息方案

采用 Redis 缓存商品核心数据,降低数据库访问频次,业务逻辑如下:

根据商品 ID 查询 Redis 缓存:

  • 缓存命中:直接返回数据;
  • 缓存未命中:查询 MySQL 数据库,将数据写入 Redis,并设置缓存有效期(默认 1 天,可按需调整)。

三、商品详情功能开发

3.1 创建 power_shop_detail 工程

3.1.1 工程结构

基于 Spring Boot 搭建独立的商品详情服务,集成 Nacos 服务发现与 Feign 远程调用。

3.1.2 核心配置

pom.xml

xml

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.prowershop</groupId> <artifactId>power_shop_item_feign</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.prowershop</groupId> <artifactId>common_utils</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>

application.yml

yaml

server: port: 8094 spring: application: name: power-shop-detail cloud: nacos: discovery: server-addr: 192.168.204.129:8848

启动类

java

package com.powershop; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class PowerShopDetailApp { public static void main(String[] args) { SpringApplication.run(PowerShopDetailApp.class, args); } }

3.2 商品信息查询实现

3.2.1 power_shop_item 服务改造(核心)

配置文件(application.yml)

yaml

# 缓存Key前缀 ITEM_INFO: ITEM_INFO BASE: BASE DESC: DESC PARAM: PARAM # 缓存有效期(秒) ITEM_INFO_EXPIRE: 86400

Service 层实现(商品基础信息)

ItemServiceImpl.java

... @Value("${ITEM_INFO}") private String ITEM_INFO; @Value("${BASE}") private String BASE; @Value("${PARAM}") private String PARAM; @Value("${ITEM_INFO_EXPIRE}") private Integer ITEM_INFO_EXPIRE; @Autowired private RedisClient redisClient; ... /** * 查询商品基础信息 * @param itemId 商品ID * @return 商品基础信息 */ @Override public TbItem selectItemInfo(Long itemId) { // 1. 查询缓存 TbItem tbItem = (TbItem) redisClient.get(ITEM_INFO + ":" + itemId + ":" + BASE); if (tbItem != null) { return tbItem; } // 2. 缓存未命中,查询数据库 tbItem = tbItemMapper.selectByPrimaryKey(itemId); // 3. 写入缓存并设置有效期 redisClient.set(ITEM_INFO + ":" + itemId + ":" + BASE, tbItem); redisClient.expire(ITEM_INFO + ":" + itemId + ":" + BASE, ITEM_INFO_EXPIRE); return tbItem; }

Service 层实现(商品描述 / 规格参数)商品描述和规格参数查询逻辑与基础信息一致,仅 Key 后缀分别为DESCPARAM,核心代码如下(以商品描述为例):

@Override public TbItemDesc selectItemDescByItemId(Long itemId) { // 1. 查询缓存 TbItemDesc tbItemDesc = (TbItemDesc) redisClient.get(ITEM_INFO + ":" + itemId + ":" + DESC); if (tbItemDesc != null) { return tbItemDesc; } // 2. 查询数据库 TbItemDescExample example = new TbItemDescExample(); example.createCriteria().andItemIdEqualTo(itemId); List<TbItemDesc> itemDescList = tbItemDescMapper.selectByExampleWithBLOBs(example); // 3. 写入缓存 if (itemDescList != null && itemDescList.size() > 0) { redisClient.set(ITEM_INFO + ":" + itemId + ":" + DESC, itemDescList.get(0)); redisClient.expire(ITEM_INFO + ":" + itemId + ":" + DESC, ITEM_INFO_EXPIRE); return itemDescList.get(0); } return null; }
3.2.2 Feign 远程调用(power_shop_item_feign)

定义 Feign 接口,供 power_shop_detail 服务调用:

java

@RequestMapping("/service/item/selectItemInfo") TbItem selectItemInfo(@RequestParam("itemId") Long itemId); @RequestMapping("/service/item/selectItemDescByItemId") TbItemDesc selectItemDescByItemId(@RequestParam("itemId") Long itemId); @RequestMapping("/service/itemParam/selectTbItemParamItemByItemId") TbItemParamItem selectTbItemParamItemByItemId(@RequestParam("itemId") Long itemId);
3.2.3 详情服务 Controller(power_shop_detail)

java

package com.powershop.controller; import com.bjpowershop.feign.ItemServiceFeign; import com.bjpowershop.pojo.TbItem; import com.bjpowershop.pojo.TbItemDesc; import com.bjpowershop.pojo.TbItemParamItem; import com.bjpowershop.utils.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/frontend/detail") public class DetailController { @Autowired private ItemServiceFeign itemServiceFeign; /** * 查询商品基础信息 */ @RequestMapping("/selectItemInfo") public Result selectItemInfo(Long itemId) { TbItem tbItem = itemServiceFeign.selectItemInfo(itemId); return tbItem != null ? Result.ok(tbItem) : Result.error("查无结果"); } /** * 查询商品描述 */ @RequestMapping("/selectItemDescByItemId") public Result selectItemDescByItemId(Long itemId) { TbItemDesc tbItemDesc = itemServiceFeign.selectItemDescByItemId(itemId); return tbItemDesc != null ? Result.ok(tbItemDesc) : Result.error("查无结果"); } /** * 查询商品规格参数 */ @RequestMapping("/selectTbItemParamItemByItemId") public Result selectTbItemParamItemByItemId(Long itemId) { TbItemParamItem tbItemParamItem = itemServiceFeign.selectTbItemParamItemByItemId(itemId); return tbItemParamItem != null ? Result.ok(tbItemParamItem) : Result.error("查无结果"); } }

3.3 功能测试

启动所有服务后,通过接口访问商品详情数据,验证缓存写入和查询逻辑是否正常。

四、缓存同步(练习)

后台修改 / 删除商品时,需主动删除 Redis 中对应商品的缓存数据,避免缓存与数据库数据不一致。核心逻辑:在商品修改 / 删除接口中,调用redisClient.del(key)删除对应缓存 Key。

五、缓存穿透问题解决

5.1 问题描述

缓存穿透是指缓存和数据库中均无对应数据,恶意请求(如不存在的商品 ID)会直接穿透缓存访问数据库,导致数据库压力剧增。

例如:-1是不存在的商品ID

5.2 解决方案:缓存空对象

当数据库查询无结果时,向 Redis 写入空对象并设置短有效期(如 30 秒),后续请求直接从缓存获取空对象,避免穿透到数据库。

5.3 代码改造(以商品基础信息为例)

java

@Override public TbItem selectItemInfo(Long itemId) { // 1. 查询缓存 TbItem tbItem = (TbItem) redisClient.get(ITEM_INFO + ":" + itemId + ":" + BASE); if (tbItem != null) { return tbItem; } // 2. 查询数据库 tbItem = tbItemMapper.selectByPrimaryKey(itemId); // 3. 解决缓存穿透:数据库无数据则缓存空对象 if (tbItem == null) { redisClient.set(ITEM_INFO + ":" + itemId + ":" + BASE, new TbItem()); redisClient.expire(ITEM_INFO + ":" + itemId + ":" + BASE, 30); return tbItem; } // 4. 数据库有数据,正常缓存 redisClient.set(ITEM_INFO + ":" + itemId + ":" + BASE, tbItem); redisClient.expire(ITEM_INFO + ":" + itemId + ":" + BASE, ITEM_INFO_EXPIRE); return tbItem; }

商品描述、规格参数的改造逻辑一致,均在数据库查询无结果时缓存空对象。

缓存穿透后,Redis效果:

六、缓存击穿问题解决

6.1 问题描述

缓存击穿是指热点商品 Key 过期瞬间,大量并发请求直接访问数据库,导致数据库压力骤增。

6.2 解决方案:分布式锁

通过分布式锁保证同一时间只有一个请求查询数据库并重建缓存,其他请求等待后从缓存获取数据。解决方案:

  1. 热点数据可设置永不过期;
  2. 分布式锁(Redis SETNX),del删除锁,finally或expire防止死锁,控制数据库查询并发。

6.3 代码改造

6.3.1 Redis 分布式锁工具(common_redis)

RedisClient.java

... /** * 分布式锁(SETNX) * @param key 锁Key * @param value 锁值(如商品ID) * @param time 锁有效期(秒) * @return 是否获取锁 */ public Boolean setnx(String key, Object value, long time) { try { return redisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 分布式锁 * @param key * @param value * @return */ public Boolean setnx(String key, Object value) { try { return redisTemplate.opsForValue().setIfAbsent(key, value); } catch (Exception e) { e.printStackTrace(); return false; } } ​
6.3.2 配置分布式锁 Key 前缀

yaml

# 分布式锁Key前缀 SETNX_LOCK_BASC: SETNX_LOCK_BASC SETNX_LOCK_DESC: SETNX_LOCK_DESC SETNX_LOCK_PARAM: SETNX_LOCK_PARAM
6.3.3 Service 层改造(以商品基础信息为例)

java

@Override public Item selectItemInfo(Long itemId) { //1、先查询redis缓存,有数据则return Item item = (Item) redisClient.get(ITEM_INFO + ":" + itemId + ":" + BASE); if (item != null){ return item; } /***************** 解决缓存击穿 1.setnx分布式锁 2.finally + del释放锁 *******************/ if (redisClient.setnx(SETNX_LOCK_BASC + ":" + itemId, itemId)) { try { //2、无数据则查询数据库 item = itemMapper.selectById(itemId); //数据库没有,解决缓存穿透问题 if (item == null) { item = new Item(); redisClient.set(ITEM_INFO + ":" + itemId + ":" + BASE, item); //将空对象缓存到Redis redisClient.expire(ITEM_INFO + ":" + itemId + ":" + BASE, 30); //设置过期时间30s return item; } //数据库有,则添加缓存 redisClient.set(ITEM_INFO + ":" + itemId + ":" + BASE, item); //3、设置过期时间 redisClient.expire(ITEM_INFO + ":" + itemId + ":" + BASE, ITEM_INFO_EXPIRE); }finally { //缓存击穿:删除锁 redisClient.del(SETNX_LOCK_BASC + ":" + itemId); } return item; }else { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } return selectItemInfo(itemId); //回调 } }

商品描述、规格参数的改造逻辑一致,仅锁 Key 前缀不同。

七、缓存雪崩问题解决

7.1 问题描述

缓存雪崩是指大量缓存 Key 在同一时间段集中过期,导致大量请求穿透到数据库,引发数据库宕机。

7.2 解决方案

  1. 缓存过期时间随机化:为不同商品的缓存设置随机过期时间(如 1 天 ± 随机分钟数),避免集中过期;
  2. 分类设置过期周期:不同分类商品的缓存有效期差异化(如热门分类 7 天,普通分类 1 天);
  3. 热点商品永不过期:核心热点商品缓存不设置过期时间,通过后台主动更新 / 删除缓存。

八、总结

本文围绕商品详情功能实现,详细讲解了 Redis 缓存的应用及缓存穿透、击穿、雪崩三大问题的解决方案:

  • 缓存穿透:缓存空对象,避免请求直达数据库;
  • 缓存击穿:分布式锁,控制热点 Key 过期后的并发查库;
  • 缓存雪崩:过期时间随机化 / 差异化,热点数据永不过期。 通过合理的缓存设计和问题优化,可大幅提升系统的稳定性和性能。
http://www.jsqmd.com/news/869273/

相关文章:

  • 如何让抓取手机日志---ADB 从入门到实战:小米14日志抓包与连接详解
  • 2026海口市最新黄金 白银 铂金 彩金回收收门店实力排行榜及联系方式推荐 - 大熊猫898989
  • 2026德阳市最新黄金 白银 铂金 彩金回收收门店实力排行榜及联系方式推荐 - 大熊猫898989
  • Gemini3.1Pro攻克长文本quot;迷失中间quot;难题
  • 从0到1万美元MRR:一个独立开发者的两年复盘
  • 3分钟部署OpenClaw最新版v2026.4.26指南,可视化小白可用操作简单
  • 【系统架构师-综合题(14)】数学与经济管理知识点
  • AI智能体安全防御:从代码数据分离到多代理系统架构实践
  • 企业内如何通过 Taotoken 实现 API 密钥的集中管理与访问审计
  • 2026邯郸市最新黄金 白银 铂金 彩金回收收门店实力排行榜及联系方式推荐 - 大熊猫898989
  • 简历上写“精通Vivado“,面试官问起时序约束就卡壳
  • 2026包头市最新黄金 白银 铂金 彩金回收收门店实力排行榜及联系方式推荐 - 大熊猫898989
  • 全球首创 XR+AGV 融合技术,超元力 XR 黑暗乘骑无轨AGV开启星际探险新纪元
  • NotebookLM关键词提取结果不一致?权威测试报告揭示模型版本、文档编码、上下文窗口三重耦合陷阱
  • Gemini3.1Pro如何使用代码教程
  • 2026临沂市最新黄金 白银 铂金 彩金回收收门店实力排行榜及联系方式推荐 - 大熊猫898989
  • 2026汉中市最新黄金 白银 铂金 彩金回收收门店实力排行榜及联系方式推荐 - 大熊猫898989
  • 代码都是AI写的,你问我要操作手册??别慌!这个skill:ManualGen 可以帮助你生成专业的用户操作手册
  • 内连接,左连接,右连接怎么区别开来?
  • 2026宝鸡市最新黄金 白银 铂金 彩金回收收门店实力排行榜及联系方式推荐 - 大熊猫898989
  • Radxa ROCK 5 ITX安装ubuntu22.04
  • 客户端设计(下):场景流派与实战设计方式
  • 可迪尔环境(DADAIR)造船喷涂废气治理项目验收,RTO蓄热燃烧炉厂家首选方案
  • 智能电表:解锁智能照明精细化能耗管控新密码
  • 中关村、首体院、京奥电竞三方签约,共探AI+电竞产学研一体化突破
  • 装上这个技能,让你的 OpenClaw 和 Hermes 变身私人旅行规划师
  • 用Gemini镜像站构建技术文档自动生成管道:从代码注释到开发者指南的全流程实践
  • adb常用命令
  • 《AI代码编辑器Cursor最新版深度体验:智能编程实战与VS Code平滑迁移指南》​
  • 2026保定市最新黄金 白银 铂金 彩金回收收门店实力排行榜及联系方式推荐 - 大熊猫898989