尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Java面试Redis高频考点全梳理:从数据类型到分布式锁

Java面试Redis高频考点全梳理:从数据类型到分布式锁 准备 Java 面试时Redis 几乎是绕不开的一关。简历上写过“使用 Redis 做缓存”项目里用过排行榜、分布式锁、计数器的同学大概率会被面试官连续追问Redis 为什么快缓存穿透和击穿到底怎么区分主从切换会不会把刚设置的锁丢掉这些题目不是靠背几十道零散八股文就能应付的。同一个考点面试官可以问概念、可以让你写命令、可以让你结合项目描述问题也可以直接给一个线上故障现象让你给出排查链路。如果只记住了结论没有把数据类型、持久化、缓存问题、分布式锁、集群部署这几块串起来很容易在追问环节露怯。下面按一条主线来整理 Java 面试中 Redis 的高频考点先理解 Redis 在 Java 项目里到底解决了什么问题再掌握底层机制和常用命令最后落到常见报错排查和项目表述。整份内容既面向面试准备也面向复习时动手验证。文末会给出三天复习清单适合在面试前做集中复盘。1. 先想清楚 Redis 面试复习的主线再决定背什么1.1 面试官为什么在 Java 环节反复问 RedisJava 后端项目里Redis 的使用频率非常高。大多数团队会把 Redis 当作缓存层使用但实际项目中远不止缓存分布式锁、接口幂等、滑动窗口限流、排行榜、签到、附近的人、消息队列都会用到 Redis。正因为它在业务系统里承担的角色多面试官很容易从一个关键词引出多个知识点。比如面试官看到简历上写着“使用 Redis 实现分布式锁”他可以继续追问为什么不用SETNX加个锁就完事锁过期了业务还没执行完怎么办释放锁时为什么不能先GET再DEL主从切换时锁丢失怎么解决这些问题覆盖了 Redis 命令、并发控制、分布式一致性、高可用全部落在同一个场景里。如果只是背过“Redisson 可以自动续期”却不知道续期基于什么机制也不知道自己设置过期时间会关闭看门狗追问两三轮就会卡住。所以复习的第一步不是马上背命令而是建立一条知识主线。这条主线建议是单机能力 - 高可用 - 分布式场景。先把单机 Redis 的数据结构、过期淘汰、持久化讲清楚再讲主从、哨兵、集群如何保证可用性最后结合分布式锁和缓存一致性谈业务取舍。1.2 3 天复习计划怎么拆三天时间不适合把每个细节都重学一遍更适合做“集中查漏 高频考点突击”。建议按下面的节奏安排天数复习重点核心任务验证方式Day 1数据结构、过期策略、内存淘汰、持久化能默写五种基本类型的命令能解释底层结构选型用redis-cli逐个类型写入并查询查看OBJECT ENCODINGDay 2缓存穿透、击穿、雪崩、分布式锁、缓存一致性能画出每个问题的解决流程图能说清方案代价用本机 Redis 验证SET NX PX和 Lua 释放锁Day 3主从、哨兵、Cluster、慢查询、排错能搭一套主从能说出生产拓扑要点和排查命令用 Docker 启动一主两从执行INFO replication观察同步状态这里的关键不是“做完这些就一定通过面试”而是把散落的八股文重新组合成一套答题体系。面试考的不只是“对不对”还有“能不能有逻辑地讲出来”。1.3 高频考点地图复习到后期可以用一张表自检。面试前如果看到考点能立刻说出答题框架基本就过关了。考点模块频率推荐深度典型追问数据类型与底层结构极高能写命令、能说编码String 为什么不用 C 字符串过期与内存淘汰高能讲策略、能配参数maxmemory-policy配错会怎样持久化高能对比 RDB/AOF进程重启后数据怎么恢复缓存穿透/击穿/雪崩极高能分别讲、能讲取舍布隆过滤器误判怎么办分布式锁高能写 SETNXLua能讲原理锁过期业务没执行完怎么办缓存与数据库一致性高能讲三种以上方案先删缓存还是先更新数据库主从/哨兵/Cluster中高能说拓扑、能讲故障转移主从延迟读到旧数据怎么办慢查询/大 key中能给出排查命令--bigkeys线上能随便跑吗这张表可以作为复习打卡表也可以当作模拟面试的提问清单。2. 数据类型把“五种类型”答成“场景 底层 排查”2.1 五种基本类型的标准答法面试官问“Redis 有哪些数据类型”如果只回答“String、Hash、List、Set、ZSet”属于背答案。更好的答法是每个类型都给“底层结构 典型命令 业务场景”让面试官能顺着你的回答继续问。类型底层结构常用命令典型场景StringSDS 简单动态字符串SETGETINCRDECR缓存对象、计数器、分布式 ID、SessionHashziplist/listpack hashtableHSETHGETHGETALL对象属性缓存、购物车ListquicklistLPUSHRPOPLRANGE消息队列、最新列表、栈Setintset hashtableSADDSREMSINTERSPOP去重、标签、抽奖、共同好友ZSetskiplist dict小数据量用紧凑编码ZADDZRANGEBYSCOREZSCORE排行榜、延时队列、滑动窗口以 String 为例面试官问“底层是什么”可以这样回答Redis 的 String 底层是 SDS不是 C 语言原生字符串。C 字符串用\0判断结尾二进制不安全拼接字符串时还需要频繁分配内存。SDS 记录了长度可以 O(1) 获取字符串长度追加时有预分配机制减少内存重新分配次数。这里有一个容易丢分的地方很多人以为 Redis 的 String 只能存文本。实际上 RM 的 String 是二进制安全的可以存序列化后的 Java 对象、图片字节、JSON 字符串。正因为很多 Java 项目会把对象序列化后放进 Redis才经常出现“key 能看到但内容是一串看不懂的二进制”的情况这其实不是错误而是序列化方式的问题。ZSet 也是常考项。可以补充一句ZSet 的典型实现是“哈希表 跳表”哈希表用于快速取ZSCORE跳表用于按分数排序和范围查询。Redis 没有使用红黑树一个原因是跳表实现简单、范围查询友好、内存和代码复杂度可控。这里不要说“红黑树一定不行”而是说这是作者在工程实现上的取舍。2.2 从类型延伸到过期、删除和淘汰五种类型讲完后面试官通常会顺势问“过期时间”。命令主要是EXPIRE user:123 300 TTL user:123 PERSIST user:123过期键不会在时钟一到就立刻物理删除。Redis 使用三种机制配合惰性删除读取 key 时判断是否过期过期才删除省 CPU但可能积累过期 key。定期删除后台周期性抽样检查删除部分过期 key控制删除操作对主线程的影响。内存淘汰当内存达到maxmemory限制时按配置策略淘汰 key。这个机制背后的原因是Redis 主线程是单线程执行命令时不能被大量删除操作阻塞。过期 key 如果全放在一个定时任务里删除在 key 数量很大的时候会造成明显卡顿。这也是为什么需要“惰性 定期 淘汰”三层兜底。2.3 容易拿分的扩展结构除了五种基本类型还有几个扩展结构面试中作为加分项提到会更有竞争力Bitmap基于 String 的位操作适合签到、在线状态、用户活跃统计。比如每天一个 bit 记录用户是否登录统计一个月活跃用户可以直接做位运算。HyperLogLog基数统计PFADD添加元素PFCOUNT统计数量。优点是内存极小但计数有误差适合 UV 统计这类不需要精确值的场景。GEO地理位置计算底层是 ZSet适合“附近的人”、门店距离排序。StreamRedis 5.0 引入的消息队列结构支持消费者组、消息持久化、ACK 机制比 List 更适合需要可靠消费的场景。Bloom Filter 不是 Redis 原生类型通常通过模块或 Redisson 的 RBloomFilter 使用在讲缓存穿透时可以主动提出来。它只能回答“一定不存在”和“可能存在”理解这一点很关键。3. 持久化RDB、AOF 和内存淘汰策略3.1 RDB 与 AOF 各自适合什么先看两种持久化的本质区别维度RDBAOF文件内容某一时刻的内存快照写命令追加日志数据丢失量两次快照之间可能丢数据取决于appendfsync策略恢复速度快慢文件大小相对小相对大写性能影响fork 子进程写磁盘按 fsync 策略影响不同RDB 默认触发方式可以在 redis.conf 里看到save 900 1 save 300 10 save 60 10000含义是900 秒内至少 1 次修改就生成快照300 秒内至少 10 次修改60 秒内至少 10000 次修改。这是“定期快照”思路数据安全性不高但恢复快。AOF 的典型配置appendonly yes appendfsync everysec auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mbappendfsync everysec表示每秒刷盘一次性能和数据安全相对均衡always每条命令都刷盘最安全但性能下降明显no交给操作系统决定性能最好但可能丢数据。还有一个重要概念是混合持久化。Redis 4.0 之后可以配置aof-use-rdb-preamble yes开启后 AOF 文件头部使用 RDB 格式保存已有数据后续再追加增量写命令。这样既能利用 RDB 恢复快的优点又能减少 AOF 日志体积。面试时把这一层说出来比单纯比较 RDB/AOF 要完整。3.2 面试时怎么讲持久化选择不要直接说“生产环境必须开 AOF”。更稳妥的答法是看数据丢失容忍度、恢复时间、写入压力。如果业务数据允许丢失几分钟比如纯缓存场景RDB 可能够用但要注意缓存冷启动后大量请求穿透到数据库的问题。如果数据比较重要比如订单状态、分布式锁、延迟队列至少要开 AOF。对恢复时间有要求且不想在重启时重放大量命令可以考虑混合持久化。面试官如果继续问“AOF 文件越来越大怎么办”需要知道触发重写的机制BGREWRITEAOF会生成一条最小命令集合来恢复当前数据集配置里的auto-aof-rewrite-percentage和auto-aof-rewrite-min-size控制自动触发条件。常见错误是只背了“RDB 快、AOF 安全”就结束。真正会讲的人会把“为什么”说出来RDB 恢复的是快照状态所以快AOF 需要逐条重放命令恢复慢但可以配合重写来压缩体积。3.3 内存淘汰策略是必问的延伸内存淘汰策略在实战中非常容易出现配置问题面试里也经常作为深入追问出现。核心配置maxmemory 2gb maxmemory-policy allkeys-lru maxmemory-samples 5常见策略策略含义适用场景noeviction内存满了不淘汰写命令返回错误需要严格保留数据的场景allkeys-lru从所有 key 中按 LRU 淘汰通用缓存场景volatile-lru只从设置了过期时间的 key 中淘汰部分 key 不能淘汰的场景allkeys-lfu从所有 key 中按 LFU 淘汰访问频率差异明显的场景volatile-ttl淘汰剩余存活时间短的 key缓存数据有过期时间的场景需要理解 Redis 的 LRU 是近似 LRU不是精确 LRU。它通过采样来估算maxmemory-samples控制采样数一般默认 5。LFU 则是根据访问频率和随时间衰减的方式来判断适合访问模式更稳定的场景。经典坑如果使用noeviction缓存写入超过maxmemory后SET会直接报错。很多项目把 Redis 同时当缓存和部分业务数据存储用又没设置淘汰策略一旦内存打满业务写入失败会连带影响主流程。4. 缓存穿透、击穿、雪崩先分清楚再背方案4.1 三类问题的判断方式这三个问题名称很像但在面试时必须快速区分。问题特征后果缓存穿透查询一个数据库里也不存在的数据缓存没有每次请求都打到 DB数据库压力激增恶意请求可能拖垮服务缓存击穿某个热点 key 在过期的瞬间大量请求同时打到 DB单个热点 key 失效导致高并发压力缓存雪崩大量 key 在同一时间过期或者 Redis 整体不可用大面积请求打到 DB可能引发系统雪崩一句话记忆穿透打的是“不存在的数据”击穿打的是“一个热点 key 失效的瞬间”雪崩打的是“大量 key 同时失效”。4.2 穿透的常用解法缓存穿透最典型的两种解法是空值缓存和布隆过滤器。空值缓存的思路是数据库查不到结果也把空值写进缓存设置较短过期时间。下面这段伪代码展示了基本思路Object value redis.get(key); if (value ! null) { return parseValue(value); } Object dbValue database.query(key); if (dbValue null) { redis.set(key, , 60, TimeUnit.SECONDS); return null; } String cacheValue serialize(dbValue); redis.set(key, cacheValue, 300, TimeUnit.SECONDS); return dbValue;注意空值缓存如果过期时间设置太长会产生大量无意义 key太短又起不到拦截效果。通常设置为几十秒到几分钟并根据是否可能有恶意请求来评估。布隆过滤器用于快速判断 key 是否可能存在。在请求进入缓存层前先判断如果 key 不存在直接返回不再查缓存和数据库。实现上可以选择 Guava 的 BloomFilter也可以使用 Redisson 的 RBloomFilter。需要说清楚布隆过滤器有误判率它只能保证“不存在的 key 一定不存在”但可能把不存在的 key 误判为存在所以通常还要配合空值缓存一起用。4.3 击穿与雪崩的取舍缓存击穿的标准解法包括互斥锁、逻辑过期、热点 key 预热。互斥锁的思路当缓存 miss 时只允许一个线程去查数据库并回填缓存其他线程等待一段时间后重试。优点是逻辑简单缺点是加锁期间请求会阻塞可能拉高接口响应时间。实现时要注意锁必须有超时时间否则线程崩溃会导致死锁。逻辑过期方案是把 value 包装成带过期时间的对象读到时发现逻辑过期后先返回旧值同时异步线程重建缓存。优点是读请求不会阻塞缺点是一段时间内可能读到旧数据。雪崩的解法比较固定过期时间加随机值避免 key 集中在同一秒过期。使用多级缓存比如本地 Caffeine 加 RedisRedis miss 时本地缓存还能撑住。Redis 本身做高可用比如主从加哨兵。服务层做限流和降级避免数据库被瞬间击穿。回答时一定要说代价互斥锁牺牲吞吐逻辑过期牺牲一致性布隆过滤器增加内存和维护成本。面试官最怕听到“我全用”但说不清为什么这么选。5. 分布式锁与缓存一致性Java 项目里的高频扩展题5.1 SETNX 实现的锁为什么不能直接上线先看最朴素的加锁写法SETNX order:pay:123 1SETNX在 key 不存在时才能设置成功可以用来模拟加锁。但问题很直接如果线程在业务执行中崩溃这个 key 永远不会被删除锁就变成死锁。改进版是设置过期时间SET order:pay:123 unique_value NX PX 30000NX保证 key 不存在时才写入PX 30000设置 30 秒过期解决了崩溃后锁不释放的问题。但释放锁时不能直接DEL因为可能删掉别人的锁。正确做法是先判断 value 是否是当前线程/请求的标识再删除。判断和删除必须保证原子性推荐用 Lua 脚本if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end这个脚本的核心价值是把“读取 value、比较、删除”合并成一个原子操作。面试时如果能写出这段 Lua说明理解到了“竞态条件”和“原子性”两个关键点。常见错误是分布写String value redis.get(key); if (uniqueValue.equals(value)) { redis.delete(key); }这段 Java 代码在并发场景下会有窗口当前线程判断后发现 value 匹配但随即锁过期另一个线程加锁成功当前线程再执行DELETE就会把别人的锁删除。必须用 Lua 或 Redisson 这类封装好的客户端规避。5.2 Redisson 封装了哪些能力Redisson 解决了几个容易踩坑的问题可重入、自动续期、释放锁时的原子操作。默认情况下Redisson 的锁有一个看门狗机制加锁后如果业务还没执行完会自动续期默认续期时间是 30 秒避免“锁过期但业务仍在执行”。这句是面试高频点。但要注意如果调用tryLock时手动指定了leaseTime看门狗就不会自动续期。这个细节很容易被忽略。示例代码RLock lock redissonClient.getLock(order:pay: orderId); boolean locked false; try { locked lock.tryLock(3, 30, TimeUnit.SECONDS); if (!locked) { return 系统繁忙请稍后重试; } // 执行订单支付或库存扣减逻辑 } finally { if (locked lock.isHeldByCurrentThread()) { lock.unlock(); } }这段代码里tryLock(3, 30, TimeUnit.SECONDS)表示等待锁最多 3 秒拿到锁后最多持有 30 秒。如果业务执行超过 30 秒且没有手动指定过期时间看门狗会续期但这里指定了 30 秒续期就不会生效。Redisson 也不是银弹。分布式锁对 Redis 主从切换的场景存在一个天然问题线程 A 在主节点加锁成功主节点还没同步到从节点就宕机从节点提升为主节点后没有这把锁线程 B 就能再次加锁成功。这类问题在要求强一致的场景下需要更谨慎的设计单纯靠 Redis 不一定能解决。5.3 缓存与数据库一致性怎么答面试官问“先更新数据库还是先更新缓存”很多人直接背“先删缓存再更新数据库”但不理解原因。常见方案是 Cache Aside读操作先读缓存命中直接返回未命中查数据库再回填缓存。写操作更新数据库然后删除缓存。为什么不更新缓存而是删除缓存因为写多读少的场景下更新缓存会浪费大量写入操作而且并发更新可能导致旧值覆盖新值。删除缓存让下一次读取时重建缓存更简单可靠。针对并发问题延迟双删是常见补充更新数据库。删除缓存。等待几百毫秒。再次删除缓存。这个设计是为了解决请求 A 更新数据库后删缓存请求 B 在缓存失效期间查了旧数据并回填旧值导致缓存里残留旧数据。第二次删除把这种残留清掉。缺点也很明显多了一个延迟操作系统更复杂。更可靠但更重的方案是订阅数据库 binlog 异步更新缓存比如 Canal。更新数据库后binlog 客户端解析变更消息再删除或更新缓存。优点是解耦缺点是引入新的中间件和消息链路延迟会更高。回答时要根据场景选择最终一致性能接受的场景用 Cache Aside 加延迟双删一致性要求极高的场景要谨慎不能只依赖缓存。6. 集群与主从从 Docker 速成到生产拓扑6.1 主从、哨兵、Cluster 解决什么问题单机 Redis 有两个明显问题一台机器能承载的数据量和请求量有限进程挂掉后服务直接不可用。主从、哨兵、Cluster 分别解决不同阶段的问题。组件解决什么问题核心机制复杂度主从复制数据冗余、读写分离主库写、从库异步复制低哨兵 Sentinel自动故障转移监控主库、选新主、通知客户端中Cluster水平扩展、自动分片CRC16 计算槽位、槽位迁移高主从复制解决的是“数据有副本”和“读压力可以被分摊”。但主库宕机后还需要人工把某个从库提升为主库所以引入哨兵做自动故障转移。数据量继续增长、单机内存不够时再用 Cluster 把 key 分布到多个分片节点。Cluster 的关键概念是槽位。Redis Cluster 把 key 空间分成 16384 个槽每个 key 通过 CRC16 计算后用16384取模决定落到哪个槽。客户端访问时可能收到MOVED重定向错误需要重新路由到正确的节点。6.2 用 Docker 快速搭一套主从环境面试前最好动手跑一遍因为很多理解只有在实际看到主从同步数据后才深刻。下面的docker-compose.yml可以快速启动一主两从services: redis-master: image: redis:7-alpine container_name: redis-master ports: - 6379:6379 command: [redis-server, --appendonly, yes] redis-replica-1: image: redis:7-alpine container_name: redis-replica-1 ports: - 6380:6379 depends_on: - redis-master command: [redis-server, --replicaof, redis-master, 6379] redis-replica-2: image: redis:7-alpine container_name: redis-replica-2 ports: - 6381:6379 depends_on: - redis-master command: [redis-server, --replicaof, redis-master, 6379]启动命令docker compose up -d验证主从状态docker exec -it redis-master redis-cli info replication docker exec -it redis-master redis-cli set user:1 zhangsan docker exec -it redis-replica-1 redis-cli get user:1从库默认是只读的写入从库会报错。如果看到role:slave或role:replica并且从库能读到主库写入的数据说明主从复制正常。注意这是
返回列表