缓存源码面试常考内容?

访客 源码剖析 2

从Redis核心机制到高并发实战解析

目录导读

  1. 缓存基础与设计原则
  2. Redis核心数据结构源码剖析
  3. 缓存淘汰策略与Redis实现
  4. 缓存穿透/击穿/雪崩及解决方案
  5. 分布式缓存一致性难题与解决思路
  6. 高频面试问答与真题解析
  7. 缓存源码学习的进阶路径

缓存基础与设计原则

缓存是计算机系统中用于解决“读写速度不匹配”问题的核心组件,在面试中,面试官常从“为什么用缓存”切入,考察你对局部性原理、热点数据识别、以及缓存与数据库协同工作的理解。

核心原则:

  • 只缓存读多写少、变化不频繁的数据。
  • 缓存的数据容量远小于数据库总量,命中率需监控。
  • 设置合理的过期时间(TTL),避免缓存无限膨胀。

Redis核心数据结构源码剖析

Redis不仅是缓存,更是高性能键值存储引擎,面试常考其底层实现:

数据结构 底层实现(源码层面) 特点与适用场景
String SDS(简单动态字符串) 避免C字符串拼接时内存频繁分配,兼容二进制安全
List 快速链表+压缩列表 消息队列、最新N条记录
Hash 字典+压缩列表 用户信息、对象属性缓存
Set 整数集合+字典 去重、交集/并集运算
Zset 跳表+字典 排行榜、延时队列

问答环节:
Q:Redis为什么用跳表而不是红黑树实现Zset?
A:跳表区间查询效率更高(O(logN)),且实现简单、支持无锁并发,跳表在插入/删除时只需调整邻居节点指针,而红黑树涉及复杂的旋转和颜色变换。


缓存淘汰策略与Redis实现

当缓存空间不足时,需要淘汰旧数据,常见策略:

  • FIFO:先进先出,易实现但不够智能。
  • LRU(Least Recently Used):淘汰最近最少使用的数据,Redis近似LRU算法采样5个键淘汰,避免遍历所有键。
  • LFU(Least Frequently Used):淘汰使用频率最低的数据,Redis 4.0引入,利用概率统计计数器。
  • TTL淘汰:优先淘汰即将过期的键。

源码关注点:

  • Redis中 evict.c 文件实现了淘汰逻辑,当内存超过 maxmemory 时,调用 freeMemoryIfNeeded() 循环淘汰。
  • 淘汰策略通过配置 maxmemory-policy 可选为 allkeys-lruvolatile-lruallkeys-random 等。

问答环节:
Q:LRU和LFU哪个更优?
A:取决于业务模式,LRU适合“间歇性热点”数据(如新闻短暂刷屏),LFU适合“长期频繁访问”数据(如用户支付接口),但LFU可能存在“缓存污染”——早期热点数据即使不再被访问,其频率计数依然很高,Redis采用“衰减策略”缓解这一问题。


缓存穿透/击穿/雪崩及解决方案

这是面试中的高频灾难场景,需要你给出完整防御措施。

1 缓存穿透

现象: 查询一个数据库中不存在的数据,请求直接打到数据库。
解决方案:

  • 布隆过滤器:将所有可能查询的key预先加入BloomFilter,过滤掉不在集合中的请求。
  • 缓存空对象:即使数据库返回null,也缓存一个短TTL的空值(例如5秒),避免重复穿透。

2 缓存击穿

现象: 某个热点key在失效瞬间,大量请求同时涌入数据库。
解决方案:

  • 互斥锁:比如使用Redis的SETNX命令,当key失效时,只有一个线程能重建缓存。
  • 逻辑过期:不设TTL,而将过期时间写在value里,通过后台线程异步更新。

3 缓存雪崩

现象: 大量缓存同时过期,或缓存服务宕机,导致所有请求直落数据库。
解决方案:

  • 设置随机过期时间:避免大量key同时失效。
  • 缓存高可用:使用Redis Sentinel或Cluster集群,部署多节点。
  • 限流与降级:当缓存不可用,对非核心请求返回降级数据(如默认值)。

分布式缓存一致性难题与解决思路

缓存和数据库如何保持数据一致?这是面试官最爱追问的点。

常见模式:

  • Cache-Aside(旁路缓存):读时先查缓存,未命中则查数据库并回写缓存;写时先更新数据库,再删除缓存。
  • Write-Through:写入数据库的同时更新缓存,适合读多写少场景。
  • Write-Behind:异步批量写入,风险是可能丢数据。

一致性难点:

  • 问题:先更新数据库再删除缓存,如果缓存删除失败,会出现脏数据。
  • 解决方案:引入消息队列,让异步任务确保缓存删除成功。
  • 终极方案:使用订阅数据库binlog(如Canal)进行缓存更新,做到最终一致性。

问答环节:
Q:为什么通常采用“删除缓存”而不是“更新缓存”?
A:删除更安全,更新缓存需要先读取旧值再计算新值,在高并发下容易产生“并发写覆盖”问题,而删除缓存后,下次请求会自动回写正确值,借助“写一致性”由数据库保证。


高频面试问答与真题解析

真题1: Redis为什么快?

  • 基于内存操作,CPU缓存友好。
  • 单线程模型避免锁竞争,多路复用I/O模型(epoll)。
  • 数据结构设计精简(如SDS节省内存)。

真题2: 在高并发下,Redis的set()和get()性能瓶颈在哪里?

  • 网络I/O:可以通过pipeline批量操作、连接池优化。
  • CPU切换:单线程处理大key时耗时,建议使用SCAN替代KEYS
  • 内存碎片:合理配置activedefrag自动整理。

真题3: 如何设计一个本地缓存(如Guava Cache)与Redis组合的二层缓存?

  • 第一层本地缓存:速度快但容量小,适合热点数据,使用LRU淘汰。
  • 第二层Redis:容量大,负责持久化与分布式共享。
  • 同步策略:本地缓存收到Redis的过期消息或监听数据库变更,主动失效本地key。

缓存源码学习的进阶路径

  1. 基础层:掌握Redis的数据结构(源码级)和淘汰策略实现。
  2. 应用层:理解缓存穿透、击穿、雪崩的防御方案,并能在代码中落地。
  3. 架构层:熟悉缓存与数据库的同步机制,能用binlog、消息队列解决一致性问题。
  4. 源码层:建议阅读Redis源码中redis.ht_string.cziplist.cskiplist.c等关键文件。
  5. 最佳实践:根据业务访问模式选择缓存策略;使用监控工具(如Prometheus)统计命中率与内存使用。

缓存是提升系统吞吐量最直接的手段,而源码级别的理解能让你在面试中从“会用”进化到“会设计”,希望这篇文章能帮你理清常见考点,从容应对面试挑战。


文章由技术博主整理,若需转载请联系授权。

标签: LFU

抱歉,评论功能暂时关闭!