首页 / 资讯中心 / 文章详情

面试不怂之 Redis 与缓存大全

面试不怂之 Redis 与缓存大全 ★ FEATURED ARTICLE
一、缓存基础认知1.1 什么是缓存缓存本质上是一块访问速度更快、通常容量更小、价格更高的存储介质用来存放热点数据或计算结果从而减少对后端慢速存储的访问。计算机体系里到处都有缓存CPU 的 L1/L2/L3、操作系统 Page Cache、浏览器 HTTP 缓存、应用层的 Redis、Memcached、Caffeine 等。一句话概括用空间换时间用更贵更快的介质换取更低的延迟和更高的吞吐。数据库每秒可能只能抗住几千次查询而 Redis 单机轻松支撑十万级 QPS。1.2 为什么需要缓存降低延迟内存读取在微秒级磁盘或网络访问在毫秒级。提升并发能力把大部分读请求拦截在缓存层保护关系型数据库等稀缺资源。减轻数据库压力避免热点数据被打爆防止连接池耗尽、CPU 飙升。屏蔽底层差异即使后端服务短暂抖动缓存仍可提供部分兜底能力。1.3 缓存的分类分类维度类型说明按位置本地缓存Caffeine、Guava Cache、HashMap进程内访问快多实例不共享按位置分布式缓存Redis、Memcached多实例共享需要网络访问按层级多级缓存本地缓存 分布式缓存 数据库逐级回源按更新方式主动缓存应用主动读取、写入缓存按更新方式被动缓存由框架或中间件自动维护如 MyBatis 二级缓存1.4 常见的缓存读写模式Cache Aside旁路缓存是面试中出现频率最高的模式读请求先查缓存命中直接返回未命中则查数据库回填缓存并返回。写请求先更新数据库再删除缓存让后续读请求重新回源重建缓存。优点是实现简单、对业务侵入小缺点是首次访问必然 miss读写并发时存在短暂不一致窗口。Read Through / Write Through应用不直接操作缓存由缓存中间件统一负责读写数据库适合对一致性要求较高的场景但实现复杂。Write Behind异步回写写请求先落到缓存由缓存异步批量刷回数据库。写入延迟极低但宕机可能丢数据适合日志、计数等场景。二、Redis 快速入门2.1 Redis 是什么RedisRemote Dictionary Server是一个基于内存的高性能键值数据库支持丰富的数据结构常被用作缓存、消息队列、分布式锁、排行榜、计数器、会话存储等。核心特点内存存储、单线程命令执行、丰富数据结构、持久化、主从与集群、过期机制、发布订阅和 Lua 脚本。2.2 Redis 为什么这么快纯内存操作绝大部分读写都在内存完成不涉及磁盘 IO。单线程模型命令执行由单线程串行完成避免线程切换、锁竞争。IO 多路复用基于 epoll、kqueue 等机制单线程即可监听大量连接。高效的数据结构SDS、跳表、哈希表等都做了大量优化。简单协议和事件驱动RESP 协议解析快。2.3 单线程模型的澄清标准答案Redis 6.0 之前真正处理命令的网络模块和命令执行是单线程的但 Redis 并非纯单线程它还有后台线程处理 AOF 刷盘、关闭文件、异步删除大 key 等工作。Redis 6.0 之后引入了多线程 IO但命令的实际执行仍然是单线程多线程只负责网络数据的读写解析。为什么执行命令要坚持单线程因为 Redis 的核心数据结构并非为并发设计多线程会引入大量锁和原子操作成本在内存操作和 IO 复用足够快的前提下单线程反而能获得更好的可预测性和吞吐。三、Redis 数据类型与底层结构3.1 五种基础数据类型类型说明常用命令典型场景String二进制安全字符串可存数字、JSONSET、GET、INCR、SETNX缓存对象、计数、分布式锁、SessionHashfield-value 映射可单独更新字段HSET、HGET、HINCRBY用户信息、购物车、商品详情List双向链表两端压入弹出可重复LPUSH、RPUSH、LPOP、LRANGE消息队列、最新消息、时间线Set无序且唯一SADD、SINTER、SUNION标签、共同好友、去重统计ZSet带 score 排序成员唯一ZADD、ZRANGE、ZREVRANGE排行榜、延迟队列、优先级队列3.2 特殊数据类型Bitmap基于 String 的位操作适合签到、在线状态、布隆过滤器基础数据。HyperLogLog基数统计以极小内存估算 UV误差约 0.81%。GEO地理位置存储与距离计算适合附近的人。StreamRedis 5.0 引入支持消费者组可用于可靠消息队列。Bitfield对位数组进行精细操作。3.3 底层数据结构对外类型底层实现说明StringSDS简单动态字符串记录长度、预分配空间、二进制安全Listquicklist双向链表 压缩列表兼顾内存与效率Hashlistpack / hashtable小数据量用紧凑结构大数据量转哈希表Set整数集合 / hashtable全整数且数量少时用 intset否则转哈希表ZSetlistpack / skiplistdict跳表保证范围有序字典保证按成员快速定位SDS 的优势O(1) 获取长度杜绝缓冲区溢出减少内存重分配二进制安全。跳表skiplist多层链表结构通过随机层数实现 O(log N) 的查找、插入和删除实现比红黑树简单适合范围查询。ZSet 同时维护跳表和字典。跳表 vs 红黑树跳表实现更简单、范围查询更友好、并发环境下更适合做局部锁红黑树占用的额外空间更稳定。Redis 选择跳表兼顾实现复杂度和功能。四、Redis 持久化4.1 RDBRedis DataBaseRDB 在某个时间点对内存数据生成快照并保存到磁盘。触发方式有手动SAVE、BGSAVE以及配置文件中的自动触发规则。核心是写时复制BGSAVE会 fork 子进程子进程在 fork 时刻共享父进程内存页父进程继续处理写请求时被修改的内存页会被操作系统复制一份子进程读到的始终是快照那一刻的数据。优点文件紧凑、恢复速度快、适合冷备。缺点两次快照之间宕机会丢失数据fork 子进程在大内存场景下可能阻塞主线程。4.2 AOFAppend Only FileAOF 以追加写日志的方式记录每条写命令Redis 重启时重放这些命令恢复数据。三个刷盘策略策略含义安全性性能always每条命令都同步写盘最高最差everysec每秒刷盘一次默认推荐最多丢 1 秒折中no由操作系统决定最低最好AOF 重写fork 子进程根据当前内存数据生成一份更紧凑的新 AOF重写期间的新增写命令记录在重写缓冲区最后合并。4.3 混合持久化Redis 4.0 推出混合持久化结合两者优点AOF 重写时前半段写入 RDB 格式的当前快照后半段追加重写期间的增量 AOF 日志。恢复时先用 RDB 部分快速恢复再重放少量 AOF。4.4 对比总结维度RDBAOF混合持久化数据安全可能丢失较多默认最多丢 1 秒接近 AOF文件体积小大需重写适中恢复速度快慢需重放命令较快性能影响fork 时可能阻塞刷盘有 IO 开销重写开销综合可控生产环境通常开启混合持久化并配置合理的重写阈值如果只是纯缓存且允许丢失也可完全关闭持久化。五、Redis 过期策略与内存淘汰5.1 过期删除策略Redis 采用惰性删除 定期删除的组合惰性删除客户端访问 key 时先检查是否过期过期则删除并返回空。实现简单但过期 key 一直不被访问会一直占用内存。定期删除周期性随机抽取一批设置了过期时间的 key 检查并删除。平衡了 CPU 和内存。结论Redis 的过期删除是「惰性 定期」结合既不是只靠定时器到点删除也不是访问才删除。5.2 内存淘汰策略当内存使用达到maxmemory上限时Redis 需要淘汰数据。淘汰策略共八种分三类不淘汰noeviction写请求直接报错。对所有 key 淘汰allkeys-lru、allkeys-lfu、allkeys-random。对设置了过期时间的 key 淘汰volatile-lru、volatile-lfu、volatile-random、volatile-ttl。LRU vs LFULRU 关注「最近是否被使用」LFU 关注「访问频率」能避免偶发的一次访问被当成热数据。缓存场景通常选allkeys-lru或allkeys-lfu默认值是noeviction。5.3 过期时间相关命令shellSET user:1001 zhangsan EX 300 # 设置 key 并指定 300 秒过期 EXPIRE user:1001 600 # 为已有 key 设置过期时间 TTL user:1001 # 查看剩余过期时间-1 表示永不过期-2 表示不存在 PERSIST user:1001 # 移除过期时间六、缓存三大经典问题6.1 缓存穿透定义查询一个数据库和缓存中都不存在的数据。缓存永远不命中请求每次都会穿透到数据库。解决方案缓存空值查询不存在时把空结果也缓存起来并设置较短过期时间。实现简单但会占用内存仍无法彻底防止恶意随机 key。布隆过滤器在缓存前面加一层布隆过滤器快速判断 key 是否可能存在不存在则直接拦截。内存占用极小但存在误判率删除元素困难。参数校验与限流对明显非法的 id 做前置校验对高频恶意请求做令牌桶等限流。6.2 缓存击穿定义某个热点 key 在某一瞬间过期同时有大量并发请求访问这个 key这些请求会瞬间全部打到数据库。与穿透的区别穿透查询的是缓存和数据库都不存在的数据击穿查询的是真实存在、但恰好过期了的热点数据。常见解决方案互斥锁发现缓存 miss 后先尝试获取分布式锁拿到锁的线程负责查数据库并回填缓存其他线程等待或稍后重试。逻辑过期不给热点 key 设置物理过期时间而是在 value 中保存逻辑过期时间。发现逻辑过期后由单独线程异步重建缓存其他线程先返回旧值。热点 key 永不过期对秒杀商品、首页配置等极端热点数据让 key 不自带过期时间改由后台任务定时刷新。6.3 缓存雪崩定义大量缓存 key 在同一时间失效或者 Redis 缓存层整体宕机导致大量请求瞬间全部落到数据库。常见触发原因大批 key 设置了相同或接近的过期时间到期后集中失效。Redis 节点故障整个缓存层不可用。常见解决方案过期时间加随机值在基础过期时间上增加随机偏移避免大量 key 在同一时刻失效。多级缓存采用本地缓存、Redis、数据库逐级兜底。限流与降级对打到数据库的请求做限流必要时返回默认值或降级结果。Redis 高可用通过主从、哨兵或集群保证缓存层不会因为单点故障整体不可用。6.4 三大问题对比问题查询对象触发条件核心解法缓存穿透缓存和数据库都不存在的数据恶意请求或非法 key缓存空值、布隆过滤器、参数校验缓存击穿真实存在的热点数据热点 key 瞬间过期互斥锁、逻辑过期、永不过期缓存雪崩大量 key 同时失效或 Redis 宕机过期时间集中、缓存层故障随机过期时间、高可用、限流降级七、缓存与数据库一致性7.1 为什么会出现不一致缓存是数据库的一份副本副本更新天然存在时间差。只要存在并发读写就可能在某个时刻出现缓存和数据库数据不一致。以 Cache Aside 模式为例先更新数据库再删除缓存时如果有一个读请求在「数据库已更新、缓存尚未删除」的窗口内读到旧缓存就会出现短暂不一致。因此面试时不要夸口「我们的方案做到了强一致」更稳妥的回答是明确这是最终一致或强一致需求并说明自己如何缩短不一致窗口。7.2 常见更新策略1. 先删除缓存再更新数据库缺点是删缓存后、数据库更新完成前如果有读请求回源读到旧数据会把旧数据重新写回缓存导致较长时间的不一致。并发较高时问题更明显因此通常会让写请求再延迟删除一次缓存也就是延迟双删。2. 先更新数据库再删除缓存这是目前接受度最高的方案。因为缓存本身应该能从数据库重建删除缓存后后续读请求会回源读新数据再回填缓存。即使删除缓存失败也可以通过消息队列重试删除最终恢复一致。javapublic void update(String key, Object newValue) { db.update(key, newValue); redis.del(key); }3. 延迟双删先删除缓存再更新数据库然后延迟一段时间再次删除缓存。第二次删除用来清掉并发读请求可能写回的旧缓存。javapublic void update(String key, Object newValue) { redis.del(key); db.update(key, newValue); Thread.sleep(500); redis.del(key); }4. 订阅数据库变更日志通过监听 MySQL binlog、Canal 等机制感知数据库变化再驱动缓存更新或删除。相比业务代码里手动删缓存这种方式对业务侵入更小也更适合多服务共享缓存的场景。7.3 强一致与最终一致怎么选强一致通常需要引入分布式事务、加锁或让读请求直接走数据库代价是延迟和吞吐下降。绝大多数展示类、商品详情类、用户信息类场景更适合最终一致允许短暂不一致重点保证最终能追平。面试总结缓存一致性没有银弹核心是根据业务容忍度选择方案并通过删除缓存、重试删除、延迟双删、灰度回源等手段尽量缩短不一致窗口。八、Redis 主从复制8.1 主从复制的作用数据冗余主库数据实时同步到从库主库故障时从库仍有数据。读写分离主库负责写从库负责读提升整体吞吐。高可用基础哨兵和集群都建立在主从复制之上。8.2 全量复制与增量复制全量复制从节点首次连接主节点时发送PSYNC主节点通过BGSAVE生成 RDB 快照发送给从节点从节点加载快照再同步复制缓冲区中的增量命令。增量复制从节点短暂断线后重连如果复制偏移量还能在复制积压缓冲区中找到就只补发断线期间的写命令避免每次都全量同步。核心特点主节点负责写从节点异步复制数据无法保证强一致如果主节点故障且哨兵未及时切换可能出现已写入主节点但未同步到从节点的数据丢失。8.3 复制中的常见问题复制延迟从库读到的数据可能落后主库对一致性敏感场景要注意。全量复制开销大大内存实例 fork 和传输快照都可能阻塞主线程建议错峰或选择业务低峰同步。从库写请求会被拒绝默认从节点只读避免主从数据混乱。九、Redis 哨兵模式9.1 哨兵能做什么哨兵Sentinel是 Redis 官方提供的高可用方案主要职责是监控主从节点是否存活、当主节点故障时自动完成故障转移、并把新的主节点信息通知给客户端。哨兵本身需要部署多个实例通常为奇数个例如 3 个多个哨兵共同判断节点状态避免单个哨兵网络抖动导致误判。9.2 故障转移流程多个哨兵周期性地向主节点发送PING检测主节点是否在线。当哨兵发现主节点不可达后将其标记为主观下线并询问其他哨兵。达到指定数量的哨兵都认为主节点下线后主节点被标记为客观下线。哨兵集群选举出一个领导者哨兵负责从健康的从节点中选出新主节点。领导者哨兵让其他从节点复制新主节点并通知客户端新的主节点地址。9.3 主观下线和客观下线主观下线单个哨兵认为主节点不可达可能只是该哨兵网络问题。客观下线多个哨兵达成共识认为主节点真的故障。只有客观下线后才会触发故障转移。哨兵模式的局限它仍然只有一个主节点负责写无法水平扩展写能力当数据量超出单机内存时哨兵无法解决分片问题。十、Redis Cluster 集群10.1 为什么需要集群哨兵解决高可用但不能解决容量和写性能的横向扩展。Redis Cluster 是官方分布式方案把数据按哈希槽分布到多个主节点上每个主节点负责一部分数据整体可以突破单机内存和 CPU 限制。10.2 哈希槽与数据分片Redis Cluster 将整个数据空间划分为16384 个哈希槽。每个 key 通过CRC16(key) % 16384计算对应的槽然后路由到负责该槽的主节点。集群中的每个节点负责一部分哈希槽客户端向错误节点发起请求时节点会返回MOVED或ASK重定向信息客户端再请求正确节点。成熟客户端通常会自动完成槽位计算和重定向。10.3 集群下的常见限制多 key 操作受限不同 key 如果不在同一个槽事务、批处理等跨槽操作会复杂化可用 hash tag 把相关 key 映射到同一槽。数据维护成本更高需要关注槽位迁移、节点扩容和缩容。故障转移依赖从节点每个主节点配置从节点才能在主节点故障后自动提升。十一、Redis 分布式锁11.1 分布式锁要解决什么问题在单机应用中synchronized或ReentrantLock就能保证同一时刻只有一个线程进入临界区。但在多实例部署的分布式系统中两个实例可能同时执行同一段业务逻辑此时需要一把所有实例都能访问的全局锁Redis 就是常见实现载体。11.2 基础实现SET NX PX获取锁必须满足两个条件只有 key 不存在时才设置成功同时要设置过期时间防止持有锁的进程崩溃后锁永远无法释放。因此要使用一条原子命令完成shellSET lock:order:1001 request_id NX PX 30000其中NX表示只在 key 不存在时设置PX 30000表示 30 秒过期。value 使用唯一请求 id用于释放锁时判断「这把锁是不是自己加的」。javaboolean lock redis.set(lock:order:1001, requestId, NX, PX, 30000); if (lock) { try { // 执行业务逻辑 } finally { releaseLock(lock:order:1001, requestId); } }11.3 为什么要用 Lua 保证释放原子性释放锁不能先GET判断再DEL。假如判断成功后、删除前锁恰好过期别的线程已经拿到了新锁原线程的DEL就会误删别人的锁。正确的做法是用 Lua 脚本把「判断 value 是否一致」和「删除」合并成一步luaif redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end11.4 Redisson 与看门狗基础实现的最大问题是如果业务执行时间超过锁的过期时间锁会被提前释放其他线程进来可能造成并发问题。Redisson 提供了看门狗机制在锁未手动释放时会自动续期默认每 10 秒检查一次并把锁续到 30 秒直到业务执行完成。看门狗解决的是「业务没执行完锁却过期」的问题。requestId Lua解决的是「释放错别人的锁」的问题。两者经常组合使用。11.5 红锁的适用边界RedLock 是 Redis 作者提出的方案希望在主从集群中降低因为主节点宕机导致锁丢失的风险。但红锁在工程界争议较大核心争议点在于它依赖多节点时钟和网络延迟极端故障下仍可能有并发窗口。面试时更务实的回答是大多数业务使用单 Redis 节点的SET NX PX 看门狗已经足够红锁理论上有更强容错但要理解其时钟假设不要盲目套用。十二、高频面试题与答题思路问题考察重点Redis 为什么快内存、单线程、IO 多路复用、数据结构Redis 是单线程吗命令执行单线程与多线程 IO 的区分RDB 和 AOF 的区别持久化原理、数据安全、恢复速度过期删除和内存淘汰有什么区别惰性删除、定期删除、八种淘汰策略穿透、击穿、雪崩分别怎么解决三个问题的概念区分和方案组合缓存和数据库如何保持一致更新顺序、延迟双删、binlog 订阅分布式锁怎么实现SET NX PX、Lua、看门狗、误删问题哨兵和集群有什么区别高可用与水平扩展的不同目标十三、总结Redis 与缓存面试的核心主线可以浓缩为基础认知缓存是什么、为什么用、分类、读写模式。Redis 入门为什么快、单线程模型澄清。数据类型五种基础类型 特殊类型 底层结构。持久化RDB、AOF、混合持久化及各自优缺点。过期与淘汰惰性 定期删除八种内存淘汰策略。三大经典问题穿透、击穿、雪崩的定义与解决方案。缓存一致性先更新数据库再删缓存、延迟双删、binlog 订阅。高可用主从复制、哨兵故障转移、Cluster 哈希槽分片。分布式锁SET NX PX、Lua 原子释放、Redisson 看门狗、红锁边界。
阅读完成 · 觉得有帮助?
咨询建站