ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Redis面试核心:分布式锁、缓存与集群实战解析

Redis面试核心:分布式锁、缓存与集群实战解析 面试 Redis 时很多人会遇到这样的情况网上收藏了一堆 85 问、100 题翻来覆去背得滚瓜烂熟可真到面试官面前一句“你们项目里 Redis 怎么用的”就把节奏打乱了。Redis 面试从来不是考零散的命令记忆而是考你能不能从业务场景出发把数据结构、分布式锁、缓存、集群这些知识串成一条线。本文围绕 Redis 面试最高频的三大主题——分布式锁、缓存、集群从原理到实战帮大家理清主线同时覆盖核心数据结构和持久化知识尽量做到一篇文章形成完整面试闭环。1. Redis 面试到底在考什么1.1 Redis 的定位与核心价值Redis 是一个基于内存的键值存储系统但它的价值远远不只是“快”。它把常见的数据结构直接做成了服务端命令比如字符串、哈希、列表、集合、有序集合还有后来的 Stream、Bitmap、HyperLogLog、地理坐标等。这让开发者可以用较少代码实现分布式环境下的缓存、计数器、排行榜、消息队列等能力。Redis 之所以能成为面试常客是因为它几乎成为后端系统的标配组件。缓存是它最广为人知的用途但分布式锁、分布式会话、接口限流、排行榜、Feed 流、实时统计等场景也经常选择 Redis。面试官问 Redis本质上是在考察候选人是否理解“分布式环境下如何处理共享状态、怎么做高并发读写、怎么保证数据一致性”这一系列问题。所以准备 Redis 面试不能只背命令和数据结构更应该把每个知识点还原到业务场景中去理解。比如面试官问“Redis 为什么快”如果你只回答“内存操作、单线程”就会漏掉 IO 多路复用、高效数据结构、避免上下文切换等关键点同样面试官问“分布式锁怎么实现”如果只回答 setnx那后面关于误删、过期续期、集群脑裂的连环追问就很难接住。1.2 Redis 面试知识地图结合近几年的高频面试反馈Redis 面试常考板块基本稳定在以下五块板块高频考点面试官到底想听什么数据结构五种基本类型、底层编码、跳表、SDS能讲清楚使用场景和原理持久化RDB、AOF、混合持久化、fork 阻塞知道如何取舍生产怎么配分布式锁setnx 演进、过期时间、Redisson 看门狗、RedLock 争议能指出坑并给出方案对比缓存设计穿透、击穿、雪崩、缓存一致性能从并发场景推导出方案集群高可用主从复制、哨兵、Cluster、槽位与重定向能画出整体架构并说明故障处理流程本文按照这个地图展开后半部分再给出一份高频速查表和答题建议。如果时间紧建议优先掌握第 2 节的数据结构、第 4 节的分布式锁和第 5 节的缓存设计这三块是面试中出现频率最高的。2. Redis 核心数据结构与底层原理2.1 五种基本类型的面试回答模板先看最基础的String、Hash、List、Set、ZSet。面试中经常出现的一种问法是“你在项目里用过哪些 Redis 数据类型分别用在什么场景”。这里不要只说类型名最好给出一个具体场景和命令。类型典型场景核心命令面试加分点String缓存、计数器、分布式 ID、限流计数set、get、incr、setnx、setex底层是 SDSincr 是原子操作Hash对象缓存、购物车、用户资料hset、hget、hgetall、hscan比 String 存 JSON 更省内存支持字段级更新List消息队列、时间轴、简单任务队列lpush、rpop、blpop、llen阻塞版本 blpop 可实现简易队列Set去重、共同好友、抽奖sadd、sismember、sinter、sdiff集合运算适合做社交关系计算ZSet排行榜、延时队列、限流zadd、zrangebyscore、zscore、zrevrank跳表实现可排序还可按分数范围查询回答时可以按“场景 数据结构 为什么选它”的结构来组织。例如排行榜使用 ZSet 是因为它天然支持分数和排序zadd 可以更新分数zrevrange 可以快速取 Top N并且插入和查询的时间复杂度相对可控。只背命令名称很难给面试官留下印象但如果你能说出“排行榜用 ZSet因为分数排序是它的核心能力”效果会好很多。2.2 底层编码与常见面试追问五种类型只是对外暴露的接口底层的编码方式会根据数据规模动态切换。面试中常被追问的有几个点String 底层为什么用 SDSSimple Dynamic String而不是 C 字符串因为 SDS 记录了长度获取长度是 O(1)SDS 在扩容时做了预分配减少内存重分配的次数同时 SDS 是二进制安全的可以存储图片、序列化对象等包含 \0 的数据。此外 C 字符串拼接容易造成缓冲区溢出SDS 在 API 层面做了保护。ZSet 为什么用跳表而不是红黑树跳表实现简单范围查询时和链表类似可以顺序遍历红黑树也能做范围查询但代码更复杂并且需要维护更多指针。Redis 在内存和缓存友好的前提下选择了跳表同时在字典中保存成员与分数的映射保证 ZSCORE 等命令能 O(1) 完成。Set 和 Hash 在元素较少时用什么编码这是一个常见的版本敏感问题。较老版本使用 intset 和 ziplist 做小规模优化较新版本逐步用 listpack 替换 ziplist。面试时可以说“Redis 会对小数据量的 key 使用紧凑编码以减少内存占用数据量超过阈值后转换为哈希表或跳表等常规结构”具体阈值需要以实际版本配置为准。了解了底层原理回答问题会明显更有底气因为面试官很容易顺着“为什么选 ZSet”继续追问“底层怎么实现排序”。如果只停留在 API 使用层面这一轮追问很容易暴露出准备不足。3. Redis 持久化RDB 与 AOF 的取舍3.1 RDB 快照RDB 是 Redis 在指定时间间隔内生成内存数据快照的文件。默认配置会按触发条件自动执行 bgsave主进程通过 fork 一个子进程来完成快照写入。使用子进程的好处是主进程可以继续处理命令减少对线上服务的影响。RDB 的优点很明显文件紧凑适合做定时备份和灾难恢复恢复速度比 AOF 快。但缺点也很致命如果 Redis 在最后一次快照之后崩溃这段时间的写操作会全部丢失。所以如果业务对数据丢失非常敏感不能只依赖 RDB。触发 RDB 的常见方式有 save、bgsave以及配置中的 save m n 规则。注意 save 命令是同步阻塞的生产环境基本不会手动使用bgsave 会 fork 子进程在写多或内存大的场景下依旧可能出现短暂阻塞这是面试中经常追问的点。面试官想听的不是“RDB 是快照”而是你知道它的触发方式、阻塞风险和取舍逻辑。3.2 AOF 日志AOF 以追加日志的方式记录每次写命令恢复时重新执行日志即可。通过 appendfsync 配置控制刷盘策略配置行为丢数据风险性能always每次写命令都 fsync 到磁盘基本不丢最差everysec每秒 fsync 一次最多丢 1 秒数据较好no由操作系统决定刷盘时机可能丢较多最好生产环境常用 everysec因为它在性能和可靠性之间比较均衡。AOF 也有一个需要关注的点日志文件会不断增长所以 Redis 支持 AOF 重写也就是在后台生成一条精简后的日志来替代旧日志。重写过程中使用子进程同时主进程的写命令会缓冲到 AOF 重写缓冲区确保新老文件切换时数据不丢失。3.3 混合持久化与现代生产实践从 Redis 4.0 开始可以开启混合持久化在 AOF 文件头部先放一个 RDB 快照后面再追加增量写命令。这样加载速度比纯 AOF 快又比纯 RDB 丢数据少。开启方式是在 redis.conf 中配置 aof-use-rdb-preamble yes具体配置名在不同版本中可能略有差异需要以实际版本文档为准。面试中的回答思路可以总结为RDB 恢复快但丢数据多AOF 安全但文件大混合持久化是常见折中方案生产环境应该同时开启 AOF 和 RDB并定期手动备份不要因为“内存数据库”就忽略持久化。这一节不算最难但很容易被忽略一旦被问到“Redis 重启后数据还在吗”很多人会答不上来。4. Redis 分布式锁从 setnx 到 Redisson4.1 为什么需要分布式锁在多实例部署下多个服务节点同时操作同一份共享资源比如扣减库存、重复下单、任务调度如果只靠本地锁每个 JVM 进程内部各自锁住一部分无法实现跨进程互斥。分布式锁的作用就是让多个进程只能有一个拿到锁拿到锁的节点执行完业务后再释放锁。Redis 实现分布式锁的思路是借助单个 Redis 节点的串行执行能力谁先成功写入某个 key谁就获得锁结束后删除 key 释放锁。这个思路简单但要真正用到生产环境需要解决很多细节问题。面试官一般会从“你用过分布式锁吗”开始一路追问到实现细节这一节的内容基本就是完整的追问链路。4.2 基础版本SET NX EX早期很多文章会用 setnx expire 两条命令来实现锁但这有严重问题setnx 成功之后如果 expire 设置失败或进程崩溃锁会永远不释放。所以正确写法要使用一条命令同时设置过期时间和 NX 条件# 只有 key 不存在时才能设置成功同时设置 30 秒过期时间 SET lock:order:1001 6f8a2d91-b0d4-4e1c-9c3a-2a6f1e5c9b01 NX EX 30这里的 value 要使用一个每次请求都不同的唯一 ID比如 UUID。这样做的原因是释放锁时需要校验 value防止当前线程把别的线程刚获取到的锁给删掉。如果只用固定的 “1” 作为 value就会出现一个线程释放了另一个线程的锁这种典型事故。释放锁的 Java 示例思路如下需要先判断 value 是否一致再删除并且这两个步骤要保证原子性所以推荐使用 Lua 脚本// 核心逻辑先校验再删除保证原子操作 String script if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end; // 通过 RedisTemplate 或 Jedis 执行该 Lua 脚本 // KEYS[1] 锁的 key // ARGV[1] 加锁时写入的 requestId如果不用 Lua 脚本而是先 get 判断再删那么在 get 和 del 之间一旦发生时间片切换依然存在误删风险。这一点面试中经常考察一定要能主动讲出来。很多候选人能说出 set nx ex却说不清为什么释放锁要用 Lua这就是扣分点。4.3 过期时间与看门狗机制加锁时必须设置过期时间否则节点宕机后锁永远不会释放。但过期时间怎么定如果业务执行时间超过过期时间锁被自动释放其他线程进入临界区就会导致并发冲突如果过期时间设得太长一旦持有锁的节点异常其他节点又需要等很久。Redisson 提供了一个经典方案可重入锁加看门狗续期。加锁后如果业务没有执行完后台会每隔一段时间自动给锁续期默认情况下锁的 watchdog 超时时间是 30 秒续期可以保证业务未完成时锁不会提前释放。使用 Redisson 的代码大致是这样RLock lock redissonClient.getLock(lock:order:1001); boolean locked false; try { // 无 leaseTime 参数时Redisson 默认启用看门狗自动续期 // waitTime 表示获取锁的最大等待时间 locked lock.tryLock(3, TimeUnit.SECONDS); if (!locked) { // 获取锁失败做降级或抛出异常 } // 业务逻辑 } finally { if (locked) { lock.unlock(); } }需要理解的是看门狗续期并不是无限续期它是尽最大努力保证“业务没做完锁还在”。如果持有锁的节点宕机续期线程也会终止锁最终会因过期时间自动释放不会死锁。这里建议深入看一下 Redisson 的看门狗源码面试时能说出实现原理会明显拉开和其他候选人的差距。4.4 可重入、RedLock 优缺点与选型对比可重入是指同一个线程在已经持有锁的情况下可以再次获取同一把锁。Redisson 的 RLock 使用 Hash 结构记录线程标识和重入次数重入时加一释放时减一减到零才真正删除。如果只用 setnx 自己实现默认不可重入需要自己维护线程标识和计数复杂度会明显上升。关于 RedLock面试中常被问到。RedLock 的思想是向多个相互独立的 Redis 节点同时申请锁超过半数节点成功就认为加锁成功以此来降低单点故障影响。但分布式领域有知名专家对 RedLock 提出过质疑主要问题包括主从切换可能导致锁丢失客户端 GC 暂停可能导致锁过期而其他节点已进入临界区节点时钟跳跃会影响过期时间判断。对于绝大多数业务来说更务实的方案是使用单实例或哨兵模式下的 Redis 分布式锁并在业务层做好幂等和兜底如果对锁的可靠性要求极高可以考虑 ZooKeeper 或 etcd 实现的分布式锁。选型对比时可以参考下表方案优点缺点适用场景Redis SETNX实现简单、性能高存在锁丢失和过期续期问题大多数互联网业务缓存型互斥Redisson开箱即用、有看门狗和可重入依赖 Redis 自身可靠性中大型项目常用方案ZooKeeper 临时顺序节点强一致、无锁丢失问题性能较低、运维成本高对锁可靠性要求高的金融/分布式任务etcd强一致、lease 续期引入新组件云原生场景常见回答分布式锁题目时最好的节奏是先画出一条演进线原生 setnx
返回列表