[特殊字符] Redis 热门八股问答 —— 面试高频题精选
覆盖基础原理、数据结构、持久化、缓存三大问题、分布式锁、高可用架构等核心考点
每题附:答案 + 追问链路 + 面试加分话术
一、基础原理篇
Q1:Redis 为什么这么快?
答:Redis 单线程能支撑 10W+ QPS,核心原因有四:
- 纯内存操作:数据全部存在内存中,读写无磁盘 I/O(持久化是异步的)
- 单线程模型:避免了多线程的上下文切换和锁竞争开销
- I/O 多路复用:基于 epoll/kqueue 实现单线程同时处理大量连接
- 高效数据结构:SDS、跳表、压缩列表等底层结构针对场景深度优化
加分话术:“Redis 6.0 引入了多线程 I/O(处理网络读写),但命令执行仍然是单线程,所以不会出现并发安全问题。多线程只是用来解决网络 I/O 瓶颈。”
追问:为什么不用多线程执行命令?
- 多线程需要加锁,引入锁竞争反而降低性能
- Redis 的瓶颈在网络 I/O 而非 CPU,单线程足够
- 单线程模型简单,不会有死锁、上下文切换等问题
Q2:Redis 有哪些数据类型?底层数据结构是什么?
答:
| 数据类型 | 底层编码 | 说明 |
|---|---|---|
| String | int / embstr / raw (SDS) | 最常用,可存字符串、整数、浮点数 |
| List | listpack(quicklist) | 有序列表,支持头尾操作 |
| Hash | listpack / hashtable | 哈希表,适合存对象 |
| Set | intset / hashtable | 无序集合,自动去重 |
| ZSet | listpack / skiplist+hashtable | 有序集合,按 score 排序 |
| Bitmap | String 的二进制操作 | 位图,适合统计在线状态 |
| HyperLogLog | 概率算法 | 基数统计,误差 0.81%,极省内存 |
| Stream | radix tree + listpool | 消息队列,支持消费者组 |
底层数据结构对照:
| 底层结构 | 说明 |
|---|---|
| SDS (Simple Dynamic String) | 二进制安全的动态字符串,O(1) 获取长度 |
| listpack | 紧凑列表,替代 ziplist(Redis 7.0+) |
| quicklist | 双向链表 + listpack 的混合结构 |
| skiplist | 跳表,ZSet 的核心结构,O(log N) 查找 |
| hashtable | 哈希表,渐进式 rehash |
| intset | 整数集合,元素全为整数时使用 |
加分话术:“Redis 7.0 用 listpack 彻底替代了 ziplist,解决了 ziplist 的级联更新问题。listpack 不保存前一个节点的长度,所以修改一个节点不会导致连锁更新。”
Q3:String 类型的底层 SDS 和 C 字符串有什么区别?
答:
| 对比项 | C 字符串 | SDS |
|---|---|---|
| 获取长度 | O(n) 遍历 | O(1)维护 len 字段 |
| 二进制安全 | ❌ 遇 \0 截断 | ✅ 以 len 判断结束 |
| 缓冲区溢出 | 可能 | 不会(自动扩容) |
| 内存分配 | 每次修改重新分配 | 空间预分配 + 惰性释放 |
| 减少重分配 | 无 | 扩容时多分配(<1MB 翻倍,≥1MB 加 1MB) |
Q4:ZSet 为什么用跳表而不用红黑树?
答:
- 范围查询更高效:跳表底层是有序链表,找到起点后沿链表遍历即可;红黑树需要中序遍历
- 实现更简单:跳表代码量远小于红黑树,不易出 bug
- 插入删除更灵活:跳表只需修改指针,红黑树需要旋转和重着色
- 并发友好:跳表可以局部加锁,红黑树旋转影响范围大
- 内存友好:跳表可以通过调整层间距平衡空间和时间
加分话术:“跳表的查找时间复杂度也是 O(log N),和红黑树一样,但跳表的常数因子更小,缓存局部性更好。LevelDB 的 memtable 也用了跳表。”
Q5:Redis 的 hashtable 是怎么实现的?什么时候扩容?
答:
Redis 的 hashtable 采用渐进式 rehash:
- 结构:包含两个哈希表 ht[0] 和 ht[1],正常时只用 ht[0]
- 触发扩容:
- 负载因子 > 1(没有 BGSAVE 时)
- 负载因子 > 5(BGSAVE 时,避免 rehash 与子进程竞争)
- 渐进式 rehash:
- 为 ht[1] 分配空间(大小为第一个大于等于 ht[0].used×2 的 2^n)
- 维护一个 rehashidx 索引,每次 CRUD 操作迁移 ht[0] 的一个桶到 ht[1]
- 期间的新增操作直接写 ht[1],查找/删除先查 ht[0] 再查 ht[1]
- 全部迁移完成后,释放 ht[0],ht[1] 变成 ht[0]
加分话术:“渐进式 rehash 避免了一次性 rehash 导致的服务卡顿,类似于 Java ConcurrentHashMap 的分段迁移思想。”
二、持久化篇
Q6:RDB 和 AOF 有什么区别?
答:
| 对比项 | RDB | AOF |
|---|---|---|
| 原理 | 定时生成内存快照(二进制) | 追加写命令日志(文本) |
| 触发方式 | save/bgsave/配置自动 | 每次写/每秒/手动 |
| 文件大小 | 小(压缩二进制) | 大(文本命令) |
| 恢复速度 | 快 | 慢(重放命令) |
| 数据安全 | 可能丢失最后一次快照后的数据 | 最多丢 1 秒(everysec) |
| fork 开销 | 有(bgsave 需要 fork 子进程) | 无(bgrewriteaof 时有) |
| 适合场景 | 冷备份、灾难恢复 | 数据安全性要求高 |
AOF 重写机制:
- AOF 文件会不断增长,需要定期重写压缩
- 重写时 fork 子进程,将当前内存数据转为命令写入新 AOF
- 重写期间的新命令写入 AOF 重写缓冲区,重写完成后追加
加分话术:“Redis 4.0 引入了混合持久化(aof-use-rdb-preamble yes),AOF 重写时先写 RDB 格式再追加增量 AOF,兼顾了恢复速度和数据安全。”
Q7:RDB 的 bgsave 为什么不会阻塞主线程?
答:
bgsave调用时,Redis 主进程执行fork()创建子进程fork()使用写时复制(Copy-On-Write, COW):- fork 时父子进程共享同一份内存数据
- 只有当主进程修改某块内存页时,才会复制该页给子进程
- 子进程负责将数据写入 RDB 文件,主进程继续处理命令
- 因此 bgsave 期间主进程基本不受影响,只有 fork 瞬间和 COW 时有短暂开销
追问:COW 有什么风险?
- 如果 fork 之后有大量写操作,会产生大量内存页复制,导致内存占用翻倍
- 生产环境建议:
maxmemory不要设置超过物理内存的 70%
Q8:AOF 的三种同步策略是什么?
答:
| 策略 | 配置 | 行为 | 安全性 | 性能 |
|---|---|---|---|---|
| always | appendfsync always | 每次写命令都同步磁盘 | 最高(不丢数据) | 最差 |
| everysec | appendfsync everysec | 每秒同步一次 | 最多丢 1 秒 | 推荐 |
| no | appendfsync no | 由 OS 决定何时同步 | 可能丢较多数据 | 最好 |
生产环境推荐
everysec,兼顾安全性和性能。
三、缓存三大问题篇
Q9:缓存穿透是什么?怎么解决?
答:
定义:查询一个根本不存在的数据,缓存永远不命中,每次都打到数据库。通常是恶意攻击。
解决方案:
| 方案 | 原理 | 优缺点 |
|---|---|---|
| 缓存空值 | 查不到也写入缓存(设短 TTL,如 30s) | 简单,但浪费内存 |
| 布隆过滤器 | 在缓存前加一层布隆过滤器,拦截不存在的 key | 内存极小(0.1% 误判率),但不支持删除 |
| 参数校验 | 接口层校验非法参数(如 id<0) | 基本防护 |
布隆过滤器原理:
- 一个 bit 数组 + 多个哈希函数
- 插入元素:用 k 个哈希函数计算 k 个位置,全部置 1
- 查询元素:k 个位置全为 1 → “可能存在”;有 0 → “一定不存在”
- 不支持删除(因为可能影响其他元素)
加分话术:“Redis 可以用 RedisBloom 模块实现布隆过滤器,命令是 BF.ADD 和 BF.EXISTS。也可以用 Redisson 的 RBloomFilter。”
// Redisson 布隆过滤器示例RBloomFilter<String>filter=redisson.getBloomFilter("userFilter");filter.tryInit(1000000L,0.01);// 预计100万元素,1%误判率filter.add("user:1001");if(!filter.contains("user:9999")){// 一定不存在,直接返回returnnull;}Q10:缓存击穿是什么?怎么解决?
答:
定义:某个热点 key 过期的瞬间,大量并发请求同时打到数据库。
解决方案:
| 方案 | 原理 | 优缺点 |
|---|---|---|
| 互斥锁 | 缓存 miss 时,用分布式锁保证只有一个线程查 DB 并回写缓存 | 强一致,但性能差 |
| 逻辑过期 | key 不设物理 TTL,在 value 中存逻辑过期时间;发现过期时异步更新 | 高性能,但短暂数据不一致 |
| 热点 key 永不过期 | 不设 TTL,由后台定时刷新 | 简单,但需要额外维护 |
互斥锁实现:
publicStringget(Stringkey){Stringvalue=redis.get(key);if(value==null){// 尝试获取分布式锁StringlockKey="lock:"+key;if(redis.setnx(lockKey,"1",30,TimeUnit.SECONDS)){try{value=redis.get(key);// 双重检查if(value==null){value=db.query(key);redis.set(key,value,60,TimeUnit.SECONDS);}}finally{redis.del(lockKey);}}else{// 未获取到锁,短暂等待后重试Thread.sleep(50);returnget(key);}}returnvalue;}Q11:缓存雪崩是什么?怎么解决?
答:
定义:大量 key 同时过期或Redis 宕机,导致请求全部打到数据库。
与穿透/击穿的区别:
| 问题 | 触发条件 | 影响范围 |
|---|---|---|
| 穿透 | 查询不存在的数据 | 单个 key |
| 击穿 | 热点 key过期 | 单个热点 key |
| 雪崩 | 大量 key同时过期 / Redis 宕机 | 大面积 |
解决方案:
| 方案 | 原理 |
|---|---|
| 随机过期时间 | TTL 加随机值,避免集中过期(如 base + random(0, 300)) |
| 多级缓存 | 本地缓存(Caffeine) + Redis + DB,层层拦截 |
| 限流降级 | 对数据库加限流,超过阈值直接返回默认值 |
| 集群高可用 | Redis Sentinel / Cluster,避免单点故障 |
| 熔断机制 | 数据库压力过大时触发熔断,返回兜底数据 |
Q12:缓存和数据库的双写一致性怎么保证?
答:
这是经典的分布式一致性问题,没有完美方案,只有权衡。
四种策略:
| 策略 | 操作顺序 | 一致性 | 性能 |
|---|---|---|---|
| Cache Aside | 先删缓存 → 更新 DB | 最终一致 | 好 |
| Read/Write Through | 缓存层统一代理读写 | 强一致 | 中 |
| Write Behind | 只更新缓存,异步刷 DB | 最终一致 | 最好 |
| 延迟双删 | 先删缓存 → 更新 DB → sleep → 再删缓存 | 最终一致 | 好 |
Cache Aside 模式(最常用):
读:先读缓存 → miss 则读 DB → 回写缓存 写:先删缓存 → 再更新 DB延迟双删解决并发问题:
// 1. 先删缓存redis.del(key);// 2. 更新数据库db.update(key,newValue);// 3. 延迟一段时间(略大于一次读请求的耗时)Thread.sleep(500);// 4. 再删一次缓存(删掉并发读请求写入的旧值)redis.del(key);追问:延迟双删的延迟时间怎么确定?
- 略大于一次读请求的总耗时(网络 + DB 查询 + 缓存写入)
- 一般 300ms ~ 1s,可以通过监控读请求 P99 耗时来确定
加分话术:“如果对一致性要求很高,可以用 Canal 监听 MySQL binlog,通过 MQ 异步更新 Redis,实现最终一致性。这是目前生产环境最主流的方案。”
四、分布式锁篇
Q13:Redis 分布式锁怎么实现?
答:
最基础的实现:SETNX + EXPIRE
# 原子操作:设置 key + 过期时间SET lock_key unique_value NX PX30000# NX: 不存在才设置# PX: 过期时间(毫秒)释放锁(Lua 脚本保证原子性):
ifredis.call("get",KEYS[1])==ARGV[1]thenreturnredis.call("del",KEYS[1])elsereturn0end为什么不直接用 DEL?因为要校验 value 是否是自己设置的,防止误删别人的锁。
追问:SETNX 和 EXPIRE 不是两条命令吗?会不会不原子?
- Redis 2.6.12 之后,SET 命令支持 NX + PX 参数,一条命令搞定,天然原子。
Q14:Redisson 是怎么实现分布式锁的?
答:
Redisson 对 Redis 分布式锁做了大量增强:
| 特性 | 实现方式 |
|---|---|
| 可重入 | Hash 结构记录锁的持有者和重入次数 |
| 自动续期(Watch Dog) | 后台线程每 10 秒检查锁是否还持有,自动续期 30 秒 |
| 阻塞等待 | 通过 Redis 的 Pub/Sub 订阅锁释放事件,避免忙轮询 |
| 主从一致性 | RedLock 算法(向 N 个独立 Redis 实例加锁,多数成功才算成功) |
RLocklock=redisson.getLock("myLock");try{// 尝试加锁,最多等待 10 秒,自动续期if(lock.tryLock(10,-1,TimeUnit.SECONDS)){// 业务逻辑}}finally{if(lock.isHeldByCurrentThread()){lock.unlock();}}Watch Dog 机制详解:
tryLock()传 -1 时,不指定 leaseTime,启用 Watch Dog- 默认 lockWatchdogTimeout = 30 秒
- 每 10 秒(lockWatchdogTimeout / 3)续期一次
- 如果持有锁的客户端宕机,Watch Dog 停止,30 秒后锁自动释放
Q15:RedLock 算法是什么?有什么争议?
答:
RedLock 步骤:
- 获取当前时间 T1
- 依次向 N 个(通常 5 个)独立的 Redis 实例加锁,设置较短超时
- 获取当前时间 T2,如果在 N/2+1 个以上实例加锁成功,且总耗时 < 锁的 TTL → 加锁成功
- 锁的有效时间 = TTL - (T2 - T1)
- 如果加锁失败,向所有实例释放锁
争议:
- Martin Kleppmann(《Designing Data-Intensive Applications》作者)指出 RedLock 有安全问题:
- 依赖时钟同步,如果某节点时钟跳跃,可能导致锁提前过期
- GC 停顿可能导致客户端认为自己持有锁,但实际上已过期
- Antirez(Redis 作者)回应:可以通过 fencing token 解决
加分话术:“如果对一致性要求不是特别高,单 Redis 实例 + Redisson 的 Watch Dog 就够了。如果要求极高,应该用 ZooKeeper 或 etcd 的临时节点方案。”
五、高可用篇
Q16:Redis 主从复制是怎么工作的?
答:
全量同步(首次连接): 1. 从节点发送 PSYNC ? -1 2. 主节点执行 BGSAVE 生成 RDB,发送给从节点 3. 从节点加载 RDB 4. 主节点将期间的写命令发送给从节点 增量同步(断线重连): 1. 从节点发送 PSYNC replid offset 2. 主节点检查 replid 是否一致,offset 是否在 repl_backlog 内 3. 如果在,发送 offset 之后的增量数据 4. 如果不在,退化为全量同步关键配置:
# 主节点:至少 N 个从节点在线才接受写入 min-replicas-to-write 1 min-replicas-max-lag 10Q17:哨兵模式(Sentinel)是怎么工作的?
答:
Sentinel 负责监控、通知、自动故障转移:
- 监控:每秒向主从节点发送 PING,检测是否存活
- 通知:主节点下线时,通过 Pub/Sub 通知客户端
- 自动故障转移:
- 多个 Sentinel 投票确认主节点下线(客观下线)
- Sentinel 选举 leader(Raft 算法)
- Leader 从从节点中选一个升级为主节点(优先级 > offset > runid)
- 通知其他从节点切换主节点
主观下线 vs 客观下线:
- 主观下线(SDOWN):单个 Sentinel 认为主节点不可达
- 客观下线(ODOWN):quorum 个 Sentinel 都认为主节点不可达
Q18:Redis Cluster 分片集群是怎么工作的?
答:
核心原理:
- 16384 个哈希槽(slot),分布在多个节点上
- key 通过
CRC16(key) % 16384计算属于哪个槽 - 每个节点负责一部分槽
节点 A: 0 ~ 5460 节点 B: 5461 ~ 10922 节点 C: 10923 ~ 16383客户端请求流程:
- 客户端发送命令到任意节点
- 节点计算 key 的槽号
- 如果在自己负责的槽 → 执行
- 如果不在 → 返回 MOVED 重定向到正确节点
ASK 重定向(槽迁移期间):
- 槽从节点 A 迁移到节点 B 时,A 返回 ASK,客户端需先发 ASKING 到 B 再执行命令
为什么是 16384 个槽?
- 作者 antirez 的解释:心跳包中需要携带槽信息,16384 个槽只需 2KB(bitmap),163840 个槽需要 20KB,开销太大
- 对于集群规模不超过 1000 节点的场景,16384 个槽足够
六、内存管理篇
Q19:Redis 的过期删除策略是什么?
答:
Redis 采用惰性删除 + 定期删除的组合策略:
| 策略 | 原理 | 优缺点 |
|---|---|---|
| 惰性删除 | 访问 key 时才检查是否过期 | 对 CPU 友好,但可能浪费内存 |
| 定期删除 | 每秒执行 10 次(默认),每次随机抽取 20 个 key 检查 | 折中方案 |
定期删除的流程:
每秒执行 10 次: 1. 随机抽取 20 个设置了过期时间的 key 2. 删除其中已过期的 key 3. 如果过期比例 > 25%,重复步骤 1 4. 每次执行时间不超过 25ms(避免阻塞主线程)追问:如果大量 key 过期但没被访问,会不会撑爆内存?
会。这就是为什么还需要内存淘汰策略。
Q20:Redis 的内存淘汰策略有哪些?
答:
Redis 4.0+ 有 8 种淘汰策略(maxmemory-policy):
| 策略 | 范围 | 淘汰依据 |
|---|---|---|
| noeviction | — | 不淘汰,写入报错(默认) |
| allkeys-random | 所有 key | 随机淘汰 |
| allkeys-lru | 所有 key | LRU(最近最少使用) |
| allkeys-lfu | 所有 key | LFU(最不经常使用) |
| volatile-random | 设了 TTL 的 key | 随机淘汰 |
| volatile-lru | 设了 TTL 的 key | LRU |
| volatile-lfu | 设了 TTL 的 key | LFU |
| volatile-ttl | 设了 TTL 的 key | TTL 越小越先淘汰 |
推荐:缓存场景用
allkeys-lfu(Redis 4.0+),它比 LRU 更准确——LRU 可能淘汰掉频繁访问但最近没访问的 key,LFU 综合考虑了访问频率和时间。
Redis 的 LRU 是近似 LRU:
- 不是维护一个全局 LRU 链表(太耗内存)
- 而是随机采样 5 个 key(
maxmemory-samples),淘汰其中最久未访问的 - 采样数越大越精确,但越慢
七、实战场景篇
Q21:如何用 Redis 实现延迟队列?
答:
方案一:ZSet 实现
# 添加延迟消息,score 为执行时间戳ZADD delay_queue1687000000"task:1"# 消费者轮询(每秒一次)ZRANGEBYSCORE delay_queue0<当前时间戳>LIMIT010# 取出后删除ZREM delay_queue"task:1"方案二:Redis Stream(Redis 5.0+)
# 生产者XADD tasks * name"send_email"delay60000# 消费者组XGROUP CREATE tasks mygroup $ XREADGROUP GROUP mygroup consumer1 COUNT10BLOCK2000STREAMS tasks>加分话术:“如果对延迟精度要求高,建议用 RocketMQ 的延迟队列或 Kafka + 时间轮方案。Redis 方案适合对精度要求不高的场景。”
Q22:如何用 Redis 统计网站 UV(独立访客)?
答:
| 方案 | 适用场景 | 内存 | 精度 |
|---|---|---|---|
| Set | 精确统计 | 大(每个用户一个元素) | 精确 |
| Bitmap | 用户 ID 连续 | 极小 | 精确 |
| HyperLogLog | 大规模统计 | 极小(固定 12KB) | 0.81% 误差 |
# HyperLogLog(推荐)PFADD uv:20260618"user:1001""user:1002""user:1001"# 自动去重PFCOUNT uv:20260618# 返回 2# Bitmap(用户 ID 为整数时)SETBIT uv:2026061810011# 用户 1001 访问SETBIT uv:2026061810021# 用户 1002 访问BITCOUNT uv:20260618# 返回 2# 多天合并BITOP OR uv:week uv:20260616 uv:20260617 uv:20260618Q23:Redis 的大 key 怎么处理?
答:
什么是大 key?
- String 类型 value > 10KB
- Hash/List/Set/ZSet 元素数量 > 5000 个
大 key 的危害:
- 读写耗时长,阻塞其他请求
- 内存不均衡(Cluster 模式下某个节点压力大)
- DEL 删除时阻塞主线程
- 网络带宽占用大
发现大 key:
redis-cli--bigkeys# 扫描大 keyredis-cli--memkeys# 按内存排序MEMORY USAGE<key># 查看单个 key 的内存DEBUG OBJECT<key># 查看 key 的详细信息删除大 key(避免阻塞):
# Redis 4.0+:异步删除UNLINK<key># 非阻塞删除# Hash:分批删除HSCAN myhash0COUNT100HDEL myhash field1 field2...# List:分批删除LTRIM mylist0-101# 每次保留前 100 个之外的# ZSet:分批删除ZREMRANGEBYRANK myzset099# 每次删除 100 个Q24:Redis 的 Pipeline 是什么?为什么能提升性能?
答:
普通模式:
客户端 → 发送命令1 → 等待响应1 → 发送命令2 → 等待响应2 → ... 每次 RTT(Round Trip Time)都是一次网络往返Pipeline 模式:
客户端 → 批量发送命令1,2,3,...,N → 批量接收响应1,2,3,...,N 只需一次 RTT// Jedis Pipeline 示例Pipelinepipeline=jedis.pipelined();for(inti=0;i<1000;i++){pipeline.set("key:"+i,"value:"+i);}pipeline.sync();// 一次性发送性能对比:
- 普通模式 10000 次 SET:约 5 秒(每次 0.5ms RTT)
- Pipeline 10000 次 SET:约 0.1 秒(一次 RTT)
注意:Pipeline 不是原子操作,中间可能插入其他客户端的命令。如果需要原子性,用 Lua 脚本或事务。
Q25:Redis 事务和 Lua 脚本有什么区别?
答:
| 对比项 | MULTI/EXEC 事务 | Lua 脚本 |
|---|---|---|
| 原子性 | 命令排队一起执行,但不支持回滚 | 真正原子,要么全执行要么全不执行 |
| 事务隔离 | EXEC 前其他客户端命令可插入 | 单线程执行,天然隔离 |
| 编程能力 | 只能执行已有命令 | 支持条件判断、循环等逻辑 |
| 性能 | 一般 | 更好(减少网络往返) |
| 错误处理 | 语法错误才中断,运行时错误不回滚 | 脚本错误全部中断 |
# Redis 事务MULTI SET a1SET b2EXEC# Lua 脚本(原子操作)EVAL"redis.call('SET', KEYS[1], ARGV[1]); redis.call('SET', KEYS[2], ARGV[2])"2a b12加分话术:“Redis 事务的 MULTI/EXEC 不支持回滚,这是设计决策——Redis 认为事务失败应该由应用层处理,而不是数据库回滚。而且运行时错误(如对 String 执行 LPUSH)不应该影响其他正确命令的执行。”
八、高频追问链路
面试官追问路线图
Q: Redis 为什么快? → Q: 单线程为什么不用多线程? → Q: Redis 6.0 的多线程是什么? → Q: I/O 多路复用的原理? Q: 缓存穿透怎么解决? → Q: 布隆过滤器原理? → Q: 布隆过滤器误判率怎么控制? → Q: 布隆过滤器支持删除吗?用什么替代? Q: 缓存击穿怎么解决? → Q: 分布式锁怎么实现? → Q: Redisson 的 Watch Dog 机制? → Q: RedLock 算法有争议你怎么看? Q: Redis 持久化? → Q: RDB 的 COW 有什么风险? → Q: AOF 重写的原理? → Q: 混合持久化怎么配置? Q: Redis Cluster 怎么分片? → Q: 为什么是 16384 个槽? → Q: MOVED 和 ASK 重定向的区别? → Q: 槽迁移过程中数据会丢吗?📋 Redis 面试速查表
| 考点 | 一句话回答 |
|---|---|
| 为什么快 | 内存 + 单线程 + I/O 多路复用 + 高效数据结构 |
| 数据类型 | String/List/Hash/Set/ZSet + Stream/Bitmap/HyperLogLog |
| ZSet 底层 | 跳表(范围查询快、实现简单、并发友好) |
| RDB vs AOF | RDB 快照恢复快,AOF 日志丢数据少 |
| 缓存穿透 | 查询不存在的数据 → 布隆过滤器 / 缓存空值 |
| 缓存击穿 | 热点 key 过期 → 互斥锁 / 逻辑过期 |
| 缓存雪崩 | 大量 key 同时过期 → 随机 TTL / 多级缓存 / 限流 |
| 双写一致性 | Cache Aside + 延迟双删 / Canal + MQ |
| 分布式锁 | SET NX PX + Lua 释放 / Redisson + Watch Dog |
| 过期策略 | 惰性删除 + 定期删除 |
| 内存淘汰 | allkeys-lfu(推荐) |
| 主从复制 | 全量同步(RDB) + 增量同步(repl_backlog) |
| 哨兵 | 监控 + 自动故障转移(Raft 选 leader) |
| Cluster | 16384 槽 + CRC16 哈希 + MOVED/ASK 重定向 |
| 大 key | UNLINK 异步删除 + HSCAN/LTRIM 分批删除 |
| Pipeline | 批量发送命令,减少 RTT |
| 事务 vs Lua | Lua 更强(真正原子 + 可编程) |