一、 基础入门篇(初 / 中级必问,占比 30%)
1. Redis 是什么?它的核心优势有哪些?
答案要点
- 定义:开源的内存型键值对数据库,支持丰富数据结构,可用于缓存、分布式锁、消息队列等场景。
- 核心优势:
- 高性能:基于内存操作 + 单线程模型 + IO 多路复用,QPS 可达 10w+。
- 丰富数据结构:String、Hash、List、Set、ZSet 五种基本结构,支持 BitMap、HyperLogLog 等扩展结构。
- 持久化:支持 RDB、AOF 两种持久化方式,保证内存数据不丢失。
- 高可用:支持主从复制、哨兵、集群模式,避免单点故障。
- 原子操作:所有命令都是原子性的,支持事务、Lua 脚本,适合并发场景。
2. Redis 支持哪些数据结构?分别有哪些典型应用场景?
答案要点
| 数据结构 | 核心特性 | 典型场景 |
|---|---|---|
| String | 二进制安全,支持字符串、数字、二进制数据 | 缓存用户信息、计数器(阅读量)、分布式锁(SETNX) |
| Hash | 键值对集合,适合存储对象 | 缓存商品详情(field 对应属性)、用户信息 |
| List | 双向链表,支持两端增删 | 消息队列(LPUSH+BRPOP)、最新消息列表 |
| Set | 无序不重复集合,支持交集 / 并集 / 差集 | 标签系统、用户关注关系、抽奖(SPOP) |
| ZSet | 有序不重复集合,按 score 排序 | 排行榜(销量榜、积分榜)、延迟队列 |
3. Redis 的过期策略有哪些?内存淘汰机制有哪些?
答案要点
- 过期策略 (三种结合):
- 定时过期:为每个过期 key 设置定时器,到期立即删除 → 内存友好,CPU 压力大。
- 惰性过期:访问 key 时才判断是否过期,过期则删除 → CPU 友好,内存压力大。
- 定期过期:每隔一段时间扫描过期 key 字典,随机抽取部分 key 删除 → 平衡 CPU 和内存。
- 内存淘汰机制 (内存达到 maxmemory 时触发):
- volatile-lru:淘汰设置过期时间的 key 中,最近最少使用的。
- allkeys-lru:淘汰所有 key 中最近最少使用的(最常用)。
- volatile-ttl:淘汰过期时间最近的 key。
- noeviction:默认策略,不淘汰,内存不足时拒绝写入(报错 OOM)。
4. Redis 的持久化机制 RDB 和 AOF 有什么区别?如何选择?
答案要点
| 特性 | RDB | AOF |
|---|---|---|
| 原理 | 定时生成内存数据的快照文件(二进制) | 记录所有写命令到日志文件(文本) |
| 数据完整性 | 差,可能丢失最后一次快照后的所有数据 | 好,支持 appendfsync 三种策略(always/everysec/no) |
| 文件大小 | 小,二进制压缩 | 大,命令日志,可重写压缩 |
| 恢复速度 | 快,直接加载快照 | 慢,需要重新执行所有命令 |
| 性能影响 | 低,fork 子进程执行,不阻塞主线程(大数据量 fork 会阻塞) | 较高,写命令实时追加,everysec 模式性能适中 |
| 选择建议 | 1. 追求高性能、可接受数据少量丢失 → RDB2. 追求数据完整性、不允许丢失 → AOF(everysec 模式)3. 生产环境推荐 RDB + AOF 混合持久化(Redis 4.0+ 支持) |
5. 什么是缓存穿透?如何解决?
答案要点
- 定义 :请求不存在的 key,缓存和数据库都查询不到,请求直接打穿到数据库,高并发下导致 DB 压力过大。
- 解决方案 :
- 缓存空值:查询不到的数据也缓存(设置短过期时间,如 5 分钟)。
- 布隆过滤器:在缓存前加一层布隆过滤器,判断 key 是否存在,不存在则直接拒绝请求(适合海量数据场景)。
二、 进阶原理篇(中 / 高级必问,占比 40%)
1. Redis 为什么是单线程的?为什么单线程还能这么快?
答案要点
- 为什么单线程 :
- Redis 的性能瓶颈是 内存和网络带宽,不是 CPU,单线程足够处理。
- 单线程避免了线程切换和锁竞争的开销,简化了并发编程模型。
- 单线程快的核心原因 :
- 基于内存操作:所有数据都在内存中,读写速度远超磁盘。
- 高效的 IO 多路复用模型:利用 epoll/kqueue 等机制,单线程监听多个 Socket,实现高并发网络 IO。
- 命令处理简单:Redis 命令都是原子性的,处理逻辑简单,无复杂计算。
- 避免了锁开销:单线程无需加锁,省去了锁的获取和释放时间。
2. Redis 6.0 引入的多线程是干什么的?和之前的单线程有什么区别?
答案要点
- 多线程的作用 :Redis 6.0 的多线程仅用于处理网络 IO 的读写 (Socket 读数据、写响应),命令执行仍然是单线程。
- 和单线程的区别 :
- Redis < 6.0:网络 IO + 命令执行 都由主线程完成。
- Redis 6.0+:主线程负责命令执行,多线程负责网络读写,解决了网络带宽瓶颈(大报文场景性能提升明显)。
- 核心原则:命令执行的单线程模型不变,保证了 Redis 的原子性和简洁性。
3. 如何用 Redis 实现分布式锁?有哪些坑?如何解决?
答案要点
- 基础实现(RedisTemplate) :
- 加锁:
SET key value NX PX 30000(NX = 不存在才设置,PX = 过期时间,原子操作)。 - 解锁:用 Lua 脚本保证原子性(判断 value 是否为自己的,是则删除,避免误删其他线程的锁)。
- 加锁:
- 常见坑 & 解决方案 :
- 死锁:必须设置过期时间,避免服务宕机导致锁无法释放。
- 锁过期 :业务逻辑未执行完锁就过期 → 用 Redisson 的看门狗机制(自动续期锁)。
- 误删锁:解锁前必须验证 value(唯一标识,如 UUID)→ Lua 脚本原子解锁。
- 单点故障 :Redis 主从切换时锁丢失 → 用 Redisson 的红锁(多节点加锁,半数以上成功才算锁成功)。
- 工业级方案 :推荐用 Redisson,封装了分布式锁的所有细节(可重入锁、公平锁、看门狗),无需手写代码。
4. 缓存击穿和缓存雪崩有什么区别?分别如何解决?
答案要点
| 问题 | 定义 | 解决方案 |
|---|---|---|
| 缓存击穿 | 热点 key 过期瞬间,大量并发请求直接打穿到 DB | 1. 热点 key 永不过期 + 后台异步更新2. 加分布式锁,只有一个线程去 DB 查询并更新缓存3. 缓存预热,提前加载热点 key |
| 缓存雪崩 | 大量 key 同时过期,或 Redis 宕机,导致所有请求打穿到 DB | 1. 给 key 设置随机过期时间,避免同时过期2. 部署 Redis 集群(主从 + 哨兵),提高可用性3. 加熔断降级机制,保护 DB4. 本地缓存兜底(如 Caffeine) |
5. Redis 主从复制的原理是什么?数据同步的过程是怎样的?
答案要点
- 主从复制的作用:数据备份、读写分离(主库写,从库读)、负载均衡。
- 数据同步过程(全量同步 + 增量同步) :
- 建立连接 :从库发送
SLAVEOF命令,与主库建立连接。 - 全量同步 :
- 主库执行
bgsave生成 RDB 快照,发送给从库。 - 从库加载 RDB 快照,恢复数据。
- 主库将快照生成期间的写命令,记录到复制缓冲区,发送给从库。
- 主库执行
- 增量同步:全量同步完成后,主库将后续的写命令实时发送给从库,从库执行命令,保持数据一致。
- 建立连接 :从库发送
- 核心优化 :Redis 2.8+ 支持 PSYNC 命令,断线重连时只同步增量数据(基于偏移量),无需全量同步。
三、 高级优化篇(资深必问,占比 20%)
1. Redis 集群(Redis Cluster)的原理是什么?如何实现数据分片和高可用?
答案要点
- 核心目的:解决 Redis 单机内存和并发的瓶颈,实现水平扩容。
- 数据分片 :
- 采用 CRC16 哈希算法 ,将 key 映射到 0~16383 的槽位(Slot)。
- 集群中每个节点负责一部分槽位(如主节点 A 负责 0~5000 槽位)。
- 客户端根据 key 的槽位,直接访问对应的节点。
- 高可用 :
- 集群采用 主从架构,每个主节点对应一个或多个从节点。
- 主节点宕机后,从节点通过投票机制(半数以上主节点同意)晋升为主节点。
- 哨兵模式负责节点的故障检测和自动切换。
- 核心特性 :支持自动分片 、故障转移,但不支持跨节点的事务和 Lua 脚本。
2. Redis 性能优化有哪些手段?(内存、网络、命令层面)
答案要点
- 内存优化 :
- 选择合适的数据结构:如用 Hash 存储对象,比 String 更节省内存(ziplist 编码)。
- 设置合理的过期策略:及时淘汰无用数据,避免内存溢出。
- 开启内存压缩:Redis 支持对 ziplist、intset 等编码的数据进行压缩。
- 避免大 key:大 key 会导致内存碎片、网络阻塞,拆分大 key(如大 Hash 拆分为多个小 Hash)。
- 网络优化 :
- 开启 Redis 6.0 的多线程 IO,提升网络读写性能。
- 使用 Pipeline 批量命令:将多个命令打包发送,减少网络往返次数。
- 合理设置 TCP 配置:增大 TCP 缓冲区,减少延迟。
- 命令优化 :
- 避免使用 O(N) 命令(如
KEYS *、HGETALL),改用SCAN命令(分批遍历)。 - 禁用慢查询命令,设置 slowlog-log-slower-than 记录慢查询,优化慢命令。
- 使用 Lua 脚本:将多个命令封装为 Lua 脚本,减少网络往返,保证原子性。
- 避免使用 O(N) 命令(如
3. Redis 的慢查询日志是什么?如何排查慢查询问题?
答案要点
- 慢查询日志 :Redis 记录执行时间超过 slowlog-log-slower-than(默认 10ms)的命令日志,用于排查性能问题。
- 排查步骤 :
- 查看慢查询日志:
SLOWLOG GET 10(获取最近 10 条慢查询)。 - 分析慢命令:常见慢命令有
KEYS *、HGETALL、SMEMBERS等 O (N) 命令。 - 优化方案:
- 替换 O (N) 命令为 SCAN 命令(分批遍历)。
- 拆分大 key,减少单次命令处理的数据量。
- 优化命令参数,避免全量查询。
- 查看慢查询日志:
4. Redis 主从复制中,为什么会出现数据不一致?如何解决?
答案要点
- 数据不一致的原因 :
- 网络延迟:主库的写命令同步到从库有延迟,主从切换时从库数据可能落后。
- 异步复制:Redis 主从复制是异步的,主库发送命令后不等待从库确认。
- 脑裂问题:主库宕机后,从库晋升为主库,原主库恢复后成为从库,数据可能被覆盖。
- 解决方案 :
- 配置 min-replicas-to-write (主库至少有 N 个从库连接)和 min-replicas-max-lag(从库最大延迟时间),主库达不到条件则拒绝写命令。
- 采用 Redis Cluster 或 哨兵模式,减少主从切换的时间。
- 业务层最终一致性:通过定时任务校验主从数据,不一致时手动修复。
四、 实战方案篇(架构设计必问,占比 10%)
1. 缓存和数据库的一致性如何保证?有哪些方案?
答案要点
- 核心原则 :缓存的更新策略要避免并发场景下的数据不一致,推荐 Cache-Aside Pattern(旁路缓存模式)。
- 方案 1:先更新数据库,再删除缓存(推荐)
- 写操作:更新 DB → 删除缓存(而非更新缓存)。
- 读操作:读缓存 → 缓存未命中 → 读 DB → 写入缓存。
- 优势:避免了并发更新缓存导致的不一致,实现简单。
- 注意:删除缓存失败时,需重试或记录日志,人工修复。
- 方案 2:延迟双删(解决并发写问题)
- 写操作:删除缓存 → 更新 DB → 延迟 1 秒再删除一次缓存。
- 适用场景:高并发写场景,避免更新 DB 后,其他线程读取旧数据写入缓存。
- 方案 3:基于 Canal 的增量更新
- 利用 Canal 监听 MySQL 的 binlog 日志,当数据变更时,自动更新或删除缓存。
- 优势:无需业务代码侵入,适合大规模分布式系统。
2. 高并发秒杀场景下,如何用 Redis 做限流和防超卖?
答案要点
- 限流方案(防止请求过载) :
- Redis + Lua 脚本 :基于
INCR命令,限制用户 / IP 的请求次数(如每分钟 10 次)。 - Redisson 令牌桶算法 :
RRateLimiter实现平滑限流,避免流量突刺。
- Redis + Lua 脚本 :基于
- 防超卖方案(核心是分布式锁 + 原子操作) :
- 库存预扣减 :将库存存入 Redis,秒杀时用
DECR命令原子扣减库存,判断结果是否 ≥ 0。 - 分布式锁:用 Redisson 锁锁住商品 ID,确保同一商品同一时间只有一个线程扣减库存。
- 最终一致性:Redis 扣减库存成功后,异步写入 MySQL,失败时回滚 Redis 库存。
- 库存预扣减 :将库存存入 Redis,秒杀时用
- 兜底方案 :
- 前端限流(按钮置灰)+ 网关限流(Nginx)+ 后端限流,多层防护。
- 熔断降级:当 Redis 宕机时,直接返回 "活动火爆",保护后端系统。