Day30:Redis 缓存策略 + 菜单实战缓存 + 三大缓存问题(穿透 / 击穿 / 雪崩)

一、今日学习目标

  1. 掌握字典缓存、分布式缓存概念
  2. 吃透缓存穿透、缓存击穿、缓存雪崩原理 + 解决方案
  3. 实战:把菜单树、角色权限加入 Redis 缓存
  4. 适配 DDD 架构,缓存和业务解耦
  5. 整理高频面试题标准答案

二、基础概念

1. 本地内存缓存 vs 分布式缓存

本地缓存(MemoryCache)

  • 进程内缓存,每个服务独立一份
  • 集群 / 多实例不共享
  • 优点:速度最快、无网络开销
  • 缺点:数据不一致、无法跨服务共享

分布式缓存(Redis)

  • 独立中间件,所有服务共用一份缓存
  • 集群、微服务、多实例数据共享
  • 可设置过期、持久化、支持多种数据结构
  • 后台管理系统必用 Redis

2. 字典缓存

不常变动、全局共用的数据放缓存:

  • 菜单树形列表
  • 角色列表
  • 数据字典
  • 系统配置特点:改动少、查询多、适合全局缓存

三、三大缓存问题 原理 + 企业级解决方案(面试必背)

1. 缓存穿透

现象

请求数据库不存在的数据,缓存也没有,每次都直接打数据库。例如:查不存在的用户 ID、恶意伪造大量不存在 Key。

解决方案

  1. 缓存空值,设置短期过期
  2. 布隆过滤器拦截不存在 Key
  3. 接口参数校验、非法参数直接拦截

2. 缓存击穿

现象

热点 Key 刚好过期,瞬间大量请求同时打到数据库。例如:首页菜单、热门角色配置。

解决方案

  1. 互斥锁(分布式锁)同一时间只放行一个请求查库
  2. 热点 Key 永不过期
  3. 后台定时主动刷新缓存

3. 缓存雪崩

现象

大量缓存 Key 同一时间过期,瞬间全部请求压垮数据库;或 Redis 宕机。

解决方案

  1. 给过期时间加随机偏移,避免同时失效
  2. Redis 集群高可用(主从 + 哨兵)
  3. 多级缓存:本地缓存 + Redis
  4. 服务熔断、降级限流

四、高频面试题 + 标准答案

1. 为什么菜单、角色适合做缓存?

菜单 / 角色改动少、查询频率极高,每次登录、侧边栏渲染都要查库,缓存后大幅减少 DB 压力,提升接口响应速度。

2. 分布式缓存和本地缓存区别?

本地缓存进程独享、不支持多实例共享;Redis 分布式缓存全局共享,适合集群、微服务项目,支持过期、持久化、原子操作。

3. 怎么保证缓存和数据库一致性?

  • 更新 / 删除时:先改库,再删缓存
  • 不做更新缓存,直接删除,下次查询自动重建
  • 强一致性场景可用:队列异步延迟删缓存

4. 为什么不建议缓存永久不过期?

数据变更后缓存不会自动更新,容易脏数据,必须手动删缓存刷新。


五、实战练习:菜单信息接入 Redis 缓存(DDD 架构直接可用)

业务思路

  1. 查询菜单树 → 先查 Redis
  2. 有缓存直接返回
  3. 无缓存查数据库,写入 Redis 再返回
  4. 新增 / 编辑 / 删除菜单、分配角色菜单时 → 清空对应缓存,保证一致性

1. 缓存 Key 规范

复制代码
menu:tree:all        // 全量菜单树
menu:user:{userId}  // 当前用户菜单树

① 获取全量菜单树(加缓存)

cs 复制代码
// 缓存Key
private const string AllMenuTreeKey = "menu:tree:all";

public async Task<R<List<MenuVo>>> GetTreeListAsync()
{
    // 1.先查Redis
    var cacheData = await _redis.GetAsync<List<MenuVo>>(AllMenuTreeKey);
    if (cacheData != null && cacheData.Any())
    {
        return R<List<MenuVo>>.Success(cacheData);
    }

    // 2.缓存没有,查库
    var allMenus = await _uow.MenuRepository.GetAllAsync();
    var voList = _mapper.Map<List<MenuVo>>(allMenus);
    var tree = BuildMenuTree(voList, 0);

    // 3.写入Redis 缓存30分钟
    await _redis.SetAsync(AllMenuTreeKey, tree, 30);

    return R<List<MenuVo>>.Success(tree);
}

② 获取当前用户菜单树(加缓存)

cs 复制代码
public async Task<R<List<MenuVo>>> GetUserMenuTreeAsync(long userId)
{
    string key = $"menu:user:{userId}";

    // 1.读缓存
    var cacheData = await _redis.GetAsync<List<MenuVo>>(key);
    if (cacheData != null && cacheData.Any())
    {
        return R<List<MenuVo>>.Success(cacheData);
    }

    // 2.查库逻辑不变...
    // ...省略原有查角色、查菜单、建树代码

    // 3.写入缓存
    await _redis.SetAsync(key, tree, 30);

    return R<List<MenuVo>>.Success(tree);
}

③ 菜单新增 / 修改 / 删除、角色分配菜单 清空缓存

任意菜单变更、角色分配菜单后,删除所有菜单相关缓存

cs 复制代码
// 清除菜单缓存
await _redis.DeleteAsync(AllMenuTreeKey);
// 可按需清空所有用户菜单缓存,简单项目直接清全量即可

规范:改库 → 删缓存 → 下次访问自动重建缓存


六、今日任务清单

✅ 理解本地缓存 / 分布式缓存区别

✅ 背熟 缓存穿透、击穿、雪崩 原理 + 方案

✅ 自定义菜单缓存 Key 规范

✅ 改造菜单树接口接入 Redis

✅ 菜单变动主动清空缓存,保证数据一致

✅ 整理本周 Redis 面试题,可直接背面试

相关推荐
2501_901200531 小时前
Laravel 大批量数据填充时的内存泄漏与性能优化指南
jvm·数据库·python
ID_180079054731 小时前
除了JSON,淘宝店铺商品API接口还支持哪些数据格式?
android·数据库
newnazi1 小时前
RedHart安装Oracle 12C
数据库·oracle
霸道流氓气质2 小时前
Spring AI ChatMemory 对话记忆配置JDBC方式到Mysql数据库实战示例与原理讲解
数据库·人工智能·spring
与数据交流的路上2 小时前
Redis-jedis连接池配置错误导致Redis CPU飙高
数据库·redis·缓存
杂家2 小时前
Windows部署Redis
数据库·windows·redis
YL200404262 小时前
035LRU缓存
java·leetcode·缓存
IpdataCloud2 小时前
电商防刷单:如何用IP风险识别工具拦截虚假交易?实操指南
数据库
m0_740796362 小时前
golang如何实现工作流引擎_golang工作流引擎实现要点
jvm·数据库·python