臭名昭著aTrust隔离计划1.臭名昭著aTrust隔离计划2.VPN原理与aTrust隔离网络实践3.docker-easyconnect到底做了什么4.TUN(tunnel-隧道-虚拟网卡)模式
个人项目
personal(个人展示)GitHub Actions 自动部署个人展示项目
基于Quartz搭建的个人博客1.Quartz个人博客使用教程2.使用 rsync 增量部署 Quartz 博客3.使用 GitHub Actions 自动部署 Quartz 博客5.域名绑定6.CDN加速7.Google 搜索收录排查:为什么关键词搜得到别人,却搜不到我的网站?Obsidian、双向链接与知识图谱
架构设计
分布式分布式事务RPC原理
旅行
行程攻略青岛三日游行程攻略
前端
NginxNginx配置与反向代理入门
Nodenpm与npx的区别
数据库
mysql01.MySQL基础语法与约束02.MySQL数据库类型03.MySQL多表查询04.MySQL 索引05.MySQL 事务06.MySQL 隔离级别07.MySQL MVCC08.MySQL 锁09.EXPLAIN10.慢查询与 SQL 优化
mysql面试题01.索引问题02.事务与隔离级别03.锁机制04.存储引擎与性能优化05.用户权限、字符集与备份恢复
mysql实战01.EXPLAIN实战02.测试环境菜单误删排查与恢复03.MySQL IN 详解
网络
网络安全1.无线监听网卡苹果M系列虚拟机安装Kali Linux教程3.kali怎么使用RTL8812AU4.kali基础命令
网络基础进程之间怎样通过网络传递数据
网址常用网址
中间键
esElasticsearch 入门
mq
RabbitMQRabbitMQ高级RabbitMQ基础
1. MQ 基础2. MQ 核心角色与代码对应3. MQ Consumer Group 与消费模式4. SOFAMQ 账户变更代码实践5. MQ 可靠性问题6. 消息幂等
redis缓存击穿、穿透与雪崩Redis 分布式锁Redis 缓存问题Redis 数据结构
AI
使用说明
第三方插件&技能简介Archify使用与安装指南geekgeekRun使用OfferFlow使用指南Ponytail使用指南Skills Manager使用指南
CodexCodex CLI与IDE区别及使用指南Codex Hook单独配置与提交通知Codex MCP安装与使用指南Codex第三方插件安装与使用指南
Agent开发0-学习路线1.LLM、Token、上下文窗口与模型参数2.大模型 API、请求参数与响应结构3.Spring AI ChatClient4.Prompt、System Prompt、Prompt 模板6.结构化输出、JSON Schema7.SSE 流式响应8.会话 ID、聊天记录、Redis9.超时、重试、限流、降级10.*完成可运行聊天接口11.Function Calling、Tool Calling 原理12.用 Java 方法定义 Tool13.工具参数设计和校验14.实现订单查询工具15.实现物流查询工具16.实现库存查询工具17.多个工具自动选择18.工具异常、超时、重试
diy主机
主机零件学习1.CPU2.主板3.内存4.显卡5.硬盘6.散热7.风扇8.机箱热门型号价格表
捡垃圾diy-13600kf+5060diy-14600kf+5070
English高频词汇英文基础语法简单句子音标
GitGit常用命令Sublime Merge 使用指南
Java
工具库Caffeine本地缓存Reactor教程
工作实践如何使用@Async线程池使用与排查线上服务 CPU 使用率过高排查压测下RPC接口大量失败排查记录
基础1.HashMap2.ConcurrentHashMap3.HashSet4.final5.static6.volatile7.AtomicInteger8.String动态代理反射锁与线程同步线程注解
面试题1.HashMap 存储在方法内是线程安全的吗2.ArrayList参数传递与引用重新赋值3.== 和 equals 的区别、hashcode4.重载和重写的区别5.String、StringBuffer、StringBuilder区别及使用场景6.接口和抽象类7.ArrayList 与 LinkedList 的区别和扩容机制8.HashMap 和 Hashtable 的区别及底层实现9.Object 和 Objects 的区别10.ThreadLocal的原理和使用场景11.字节码是什么及作用12.Java类加载器有哪些13.双亲委派模型14.Java异常体系15.GC是什么16.GC如何判断对象可以被回收
面试总结0.HR面试模板1.面试一2.面试二3.面试三笔试
网络与 IO(黑马程序员-纯理论)01-网络编程基础02-IO模型与零拷贝03-BIO04-NIO基础与缓冲区05-NIO通道与选择器06-AIO
juc00-目录01-进程与线程02-线程同步与通信03-并发设计模式04-Java内存模型05-CAS与原子类06-ThreadLocal07-阻塞队列08-线程池使用与原理09-任务调度10-ForkJoin与享元模式11-AQS12-ReentrantLock13-读写锁14-并发协作工具15-ConcurrentHashMap16-CopyOnWrite与同步容器17-SkipListMap18-非阻塞队列
juc(工作常见)Java并发编程三个重点拆解
jvm1. JVM运行时数据区
Spring1.Spring IoC、DI与构造方法注入CGLIB、Byte Buddy 与 Spring AOPSpring 常用注解详解Spring Bean 生命周期
Spring MVC异常处理机制
linux常用命令
mac快捷键终端常用操作
python
基础01.Python是什么,为什么要安装Python02.Python项目中.venv是什么03.Python基础语法
基础实战sql替换
HTTPXHRMS员工详情接口调用(Python HTTPX)
windowswindows激活

一、💡 一句话理解

核心结论

Redis 缓存的价值是用内存换速度、减轻数据库压力;面试重点不是“会用 GET/SET”,而是能讲清楚 Cache Aside、数据一致性、过期与淘汰,以及异常流量如何保护数据库。

二、🧭 理论:缓存是怎么用的

2.1 为什么使用缓存

数据库适合可靠地保存数据,但磁盘访问、复杂查询和并发竞争都可能增加响应时间。Redis 把热点数据放在内存中,让大量读请求不必每次查询数据库。

缓存中的数据通常只是数据库数据的副本,所以要接受两个事实:

  • 缓存可能短暂过期或不一致。
  • 缓存丢失后,数据必须能够从数据库重新加载。

2.2 Cache Aside 是什么

Cache Aside(旁路缓存)由应用程序同时管理 Redis 和数据库,是最常见的缓存模式。

读取:先查 Redis → 命中直接返回 → 未命中查数据库 → 写入 Redis
写入:先更新数据库 → 再删除 Redis 中的旧缓存

Redis 官方 Cache Aside 示例也是读取时先查缓存,未命中后读取主数据源并回填;写入主数据源后删除缓存,让下一次读取重新加载。

三、⚙️ 理论:常见缓存问题

3.1 缓存与数据库不一致

最常见方案是:先更新数据库,再删除缓存。

为什么通常删除缓存,而不是直接更新缓存?

  • 删除逻辑简单,下一次读取会从数据库加载最新值。
  • 一个数据库对象可能对应多个缓存结构,直接更新容易漏字段或漏 Key。
  • 有些数据写多读少,写入时立刻计算并更新缓存会浪费资源。
更新请求:更新数据库成功 → 删除缓存
下一次查询:缓存未命中 → 查询数据库 → 回填新值

如果删除缓存失败,可以通过重试、消息队列或订阅数据库变更日志再次删除。普通业务一般接受短暂的最终一致性;强一致性要求高时,不能只依赖普通 Cache Aside。

为什么不建议“先删缓存,再更新数据库”

删除后若有并发读请求,它可能把数据库中的旧值重新写回缓存;随后数据库虽然更新成功,缓存却继续保留旧值。

3.2 过期时间和内存淘汰

这两个概念不要混淆:

机制什么时候发生解决什么问题
TTL 过期Key 到达业务设置的有效期限制数据陈旧时间,避免缓存永久存在
内存淘汰Redis 内存超过 maxmemory按 maxmemory-policy 释放空间

常见淘汰策略:

  • noeviction:不淘汰,新增数据时返回错误。
  • allkeys-lru:优先淘汰最近最少使用的 Key。
  • allkeys-lfu:优先淘汰访问频率最低的 Key。
  • volatile-*:只从设置了 TTL 的 Key 中淘汰。

纯缓存场景常考虑 allkeys-lru 或 allkeys-lfu,但最终要根据访问模式和命中率选择。Redis 的 LRU、LFU 都是近似实现,不是对全部 Key 做精确排序。

3.3 热 Key 和大 Key

  • 热 Key:少数 Key 承担大量访问,可能让单个 Redis 节点、网络或 CPU 成为瓶颈。可使用本地缓存、读副本、拆分 Key、限流等方式分散压力。
  • 大 Key:单个 String、Hash、List、Set 等保存过多数据,会增加网络传输、阻塞时间、删除成本和内存压力。应拆分数据、分页读取,并避免一次处理全部元素。

面试时不必死背“大 Key 必须超过多少字节”。它取决于业务延迟、元素数量和网络环境,重点是能识别影响并提出拆分方案。

3.4 穿透、击穿和雪崩

问题核心原因主要办法
缓存穿透查询 Redis 和数据库都不存在的数据参数校验、缓存空值、布隆过滤器
缓存击穿单个热点 Key 失效,大量请求同时查数据库互斥重建、逻辑过期
缓存雪崩大量 Key 同时失效,或 Redis 整体不可用随机 TTL、高可用、限流和降级

详细原理和回答模板见 缓存击穿、穿透与雪崩。

四、🚀 实践:从配置到验证

4.1 前置准备

本篇目标是面试表达,不搭建完整项目。使用 Redis 和 redis-cli 即可验证 TTL、内存策略和命中率。

4.2 可以拿来干什么

4.2.1 给缓存设置 TTL

SET cache:product:1 "{\"name\":\"键盘\"}" EX 1800
GET cache:product:1
TTL cache:product:1

结果:商品缓存 30 分钟后自动失效。生产环境批量写入时可在基础 TTL 上增加随机值,避免大量 Key 同时过期。

4.2.2 查看缓存命中情况

INFO stats

重点观察:

  • keyspace_hits:查询命中次数。
  • keyspace_misses:查询未命中次数。
  • evicted_keys:因内存不足被淘汰的 Key 数量。
  • expired_keys:因 TTL 到期被删除的 Key 数量。

命中率可粗略计算为:

keyspace_hits / (keyspace_hits + keyspace_misses) × 100%

4.3 完整实践:一次 Cache Aside 查询

面试时可以用下面的伪代码说明完整流程:

Product queryProduct(long id) {
    // 1. 先查 Redis。命中后直接返回,不访问数据库。
    String cacheKey = "cache:product:" + id;
    String json = redis.get(cacheKey);
    if (json != null) {
        // 命中空值标记时直接返回不存在,不能把标记当成商品 JSON 解析。
        return "__NULL__".equals(json) ? null : deserialize(json);
    }
 
    // 2. 缓存未命中,再查询数据库。
    Product product = database.findById(id);
    if (product == null) {
        // 3. 不存在的数据缓存一个短期空值,减少缓存穿透。
        redis.set(cacheKey, "__NULL__", 120, SECONDS);
        return null;
    }
 
    // 4. 回填缓存,并给 TTL 增加随机值,降低集中失效风险。
    long ttl = 1800 + random.nextLong(301);
    redis.set(cacheKey, serialize(product), ttl, SECONDS);
    return product;
}
 
void updateProduct(Product product) {
    // 5. 先让数据库成为最新的权威数据源。
    database.update(product);
 
    // 6. 再删除旧缓存;删除失败时应记录日志并进入重试流程。
    redis.delete("cache:product:" + product.getId());
}

验证重点:第一次读取访问数据库并回填缓存,第二次读取命中 Redis;更新数据库并删除缓存后,下一次读取重新加载最新值。

五、🎤 面试回答模板

5.1 一分钟回答

Redis 缓存通常采用 Cache Aside:读请求先查缓存,未命中再查数据库并回填;写请求先更新数据库,再删除缓存。这样实现简单,但只能保证最终一致性,删除失败要通过重试或消息机制补偿。缓存 Key 应设置合理 TTL,并通过随机过期时间避免集中失效;Redis 达到 maxmemory 后还会根据 LRU、LFU 等策略淘汰数据。另外要关注热 Key、大 Key、缓存穿透、击穿和雪崩,核心目标都是保护 Redis 和数据库,避免流量直接压垮后端。

5.2 常见追问

为什么更新数据库后是删除缓存,而不是更新缓存?

删除更简单,下一次读取会按数据库最新值重建缓存,也能避免复杂缓存结构更新不完整。

删除缓存失败怎么办?

记录失败任务并重试;可靠性要求更高时,可通过消息队列或数据库变更日志异步删除缓存。

设置了 TTL,为什么还需要淘汰策略?

TTL 解决业务过期问题;淘汰策略处理 Redis 已达到内存上限但仍要写入新数据的问题,两者触发条件不同。

六、📌 总结

  • Cache Aside:读缓存,未命中查数据库并回填;写数据库后删除缓存。
  • 普通缓存通常追求最终一致性,删除失败必须有重试或补偿。
  • TTL 是业务过期,内存淘汰是容量保护,二者不能混为一谈。
  • 热 Key 要分散流量,大 Key 要拆分数据。
  • 记忆句:读时先缓存,写时删缓存;过期管时间,淘汰管空间。

七、🔗 官方资料