Skip to content

Redis 专项知识库

面向 Java 后端学习、项目设计与中高级面试。本文按“基础概念 → 数据结构 → 持久化 → 高可用 → 分布式 → 性能优化 → Java 集成 → 场景方案 → 深度面试题”的顺序整理。

目录

  • **#1. Redis 总览**
  • **#2. Redis 为什么快**
  • **#3. Redis 基础命令与通用规则**
  • **#4. Redis 数据类型与底层结构**
  • **#5. Key 设计与过期策略**
  • **AOF**
  • **#7. 内存管理与淘汰策略**
  • **#8. 事务、Lua 与原子性**
  • **#9. 发布订阅、Stream 与消息队列**
  • **#10. 主从复制、哨兵与集群**
  • **#11. 缓存经典问题与解决方案**
  • **#12. 分布式锁与并发控制**
  • **#13. Redis 在 Java 中的使用**
  • **#14. 常见业务场景设计**
  • **#15. 性能诊断与线上排障**
  • **#16. 安全与运维**
  • **#17. Redis 深度面试题**
  • **#18. 速查表**

1. Redis 总览

1.1 Redis 是什么

Redis(Remote Dictionary Server)是一个基于内存的高性能 Key-Value 数据库,常用于:

  • 缓存
  • 分布式锁
  • 计数器
  • 排行榜
  • 会话存储
  • 限流
  • 延迟队列
  • 消息队列
  • 地理位置查询
  • 分布式 ID 辅助生成
  • 热点数据加速

1.2 Redis 与传统数据库的区别

维度RedisMySQL/PostgreSQL 等关系型数据库
存储位置以内存为主,可持久化到磁盘以磁盘为主,依赖 Buffer Pool 缓存
数据模型Key-Value,多种数据结构表、行、列、关系模型
查询能力按 key 或结构命令访问,不擅长复杂 SQL支持复杂 SQL、事务、索引、Join
性能极高,适合低延迟访问更适合强一致、复杂查询和持久存储
事务能力简单事务,不支持回滚ACID 事务完善
典型定位缓存、并发辅助、实时数据结构主数据源、业务事实存储

1.3 Redis 适合与不适合做什么

适合:

  • 高频读、低延迟访问
  • 数据结构天然适配业务,如 Set 去重、ZSet 排行榜
  • 可以接受最终一致性的缓存场景
  • 临时状态、短生命周期数据
  • 原子计数、限流、锁等并发辅助能力

不适合:

  • 作为复杂关系查询的唯一数据库
  • 存储超大 Value 或海量冷数据
  • 需要严格多表事务的核心账务系统
  • 完全替代消息中间件处理复杂 MQ 语义
  • 不做持久化与高可用规划就承载关键数据

2. Redis 为什么快

Redis 快不是单一原因,而是多因素叠加:

  1. 基于内存:大多数操作直接访问内存,避免磁盘随机 IO。
  2. 高效数据结构:SDS、dict、skiplist、quicklist、listpack 等结构针对常见操作优化。
  3. 单线程命令执行模型:避免多线程锁竞争和上下文切换,逻辑简单。
  4. IO 多路复用:使用 epoll/kqueue/select 等机制处理大量连接。
  5. 命令粒度小:大多数命令时间复杂度低,常见读写为 O(1) 或 O(logN)。
  6. 协议简单:RESP 协议解析成本低。
  7. 渐进式 rehash、惰性删除等机制:减少阻塞峰值。

2.1 Redis 真的是单线程吗

需要区分:

  • 命令执行主流程:长期以来主要是单线程执行,保证单命令原子性。
  • 后台任务:RDB 生成、AOF 重写、异步释放内存、复制等可由子进程或后台线程处理。
  • 网络 IO 多线程:较新版本支持多线程处理部分网络读写,但命令执行仍保持核心串行模型。

结论:Redis 不是整个进程只有一个线程,而是核心命令执行路径以单线程串行为主。

2.2 单线程为什么还能高并发

因为 Redis 的瓶颈通常不是 CPU,而是内存访问和网络 IO。单线程配合 IO 多路复用可以在一个线程中管理大量连接事件。只要每个命令足够快,就能达到很高吞吐。

危险点:单线程模型怕慢命令。比如 KEYS *、大 key 删除、超大集合全量遍历、复杂 Lua 脚本都会阻塞后续请求。


3. Redis 基础命令与通用规则

3.1 Key 通用命令

命令作用注意点
EXISTS key判断 key 是否存在可同时判断多个 key
DEL key删除 key大 key 删除可能阻塞
UNLINK key异步删除 key更适合大 key 删除
EXPIRE key seconds设置秒级过期返回 1 表示成功
PEXPIRE key ms设置毫秒级过期精度更高
TTL key查看剩余秒数-1 无过期,-2 不存在
PTTL key查看剩余毫秒同上
TYPE key查看数据类型排障常用
SCAN cursor渐进式遍历 key替代 KEYS

3.2 命令时间复杂度意识

Redis 命令文档都会标注时间复杂度。线上使用必须关注:

  • O(1):通常安全,如 GET、SET、HGET。
  • O(logN):通常可接受,如 ZADD、ZRANGE 按范围。
  • O(N):谨慎,如 LRANGE 大范围、SMEMBERS、HGETALL。
  • O(N*M):高危,尤其在大 key 上。

3.3 不推荐的线上命令

命令风险替代方案
KEYS *全库扫描,阻塞主线程SCAN
FLUSHALL清空全库运维审批、指定 DB、备份
HGETALL 大 Hash大量返回阻塞网络与主线程HSCAN、分页字段设计
SMEMBERS 大 Set一次性返回全量成员SSCAN
DEL 大 key同步释放内存阻塞UNLINK
长 Lua 脚本阻塞所有命令拆分、限时、预估复杂度

4. Redis 数据类型与底层结构

Redis 的价值不只是 Key-Value,而是“带原子操作的数据结构服务器”。

4.1 String

使用场景

  • 缓存对象 JSON
  • 计数器
  • 分布式 ID 计数
  • Session Token
  • 短验证码
  • Bitmap 的底层载体

常用命令

命令作用
`SET key value [EX seconds] [NXXX]`
GET key获取值
MGET k1 k2批量获取
INCR key自增
INCRBY key n按指定值自增
DECR key自减
SETNX key value不存在才设置,常用于锁基础
GETSET key value设置新值并返回旧值

底层结构 SDS

Redis 没有直接使用 C 字符串,而是使用 SDS(Simple Dynamic String)。优点:

  • O(1) 获取字符串长度
  • 二进制安全,可以存储任意字节
  • 减少缓冲区溢出风险
  • 预分配和惰性释放降低频繁扩容成本

面试重点

String 最大 Value 有上限,但实际项目中不应该存超大 Value。超大 JSON 会导致网络传输慢、序列化慢、内存碎片严重、更新粒度粗。

4.2 Hash

使用场景

  • 用户信息对象
  • 商品库存局部字段
  • 配置项
  • 小对象缓存

常用命令

命令作用
HSET key field value设置字段
HGET key field获取字段
HMGET key f1 f2批量获取字段
HINCRBY key field n字段自增
HDEL key field删除字段
HLEN key字段数量
HSCAN key cursor渐进遍历字段

String JSON vs Hash

维度String JSONHash
读完整对象简单需要取多个字段或 HGETALL
更新局部字段需要反序列化整体写回可直接 HSET 单字段
序列化成本
结构演进灵活但缺少字段级操作字段清晰
内存小对象 Hash 可能更省大量 field 也会有开销

建议:完整对象读多写少用 String JSON;字段频繁局部更新用 Hash。

4.3 List

使用场景

  • 简单队列
  • 最新列表
  • 消息缓冲
  • 时间线片段

常用命令

命令作用
LPUSH/RPUSH左/右插入
LPOP/RPOP左/右弹出
BLPOP/BRPOP阻塞弹出
LRANGE key start stop范围读取
LTRIM key start stop裁剪列表

底层结构 quicklist

Redis List 早期使用 ziplist + linkedlist,后来演进为 quicklist:多个紧凑列表节点组成双向链表,兼顾内存紧凑与插入删除效率。

注意点

List 可以做简单队列,但缺少可靠消息确认、重试、死信等能力。可靠队列更适合 Redis Stream 或专业 MQ。

4.4 Set

使用场景

  • 去重
  • 标签集合
  • 共同好友
  • 抽奖
  • 黑名单/白名单
  • 点赞用户集合

常用命令

命令作用
SADD key member添加成员
SREM key member删除成员
SISMEMBER key member判断是否存在
SCARD key成员数量
SINTER交集
SUNION并集
SDIFF差集
SRANDMEMBER随机取成员
SPOP随机弹出

底层结构

  • 整数集合:成员全是整数且数量较小时使用 intset。
  • 哈希表:通用场景使用 dict。

4.5 Sorted Set / ZSet

使用场景

  • 排行榜
  • 延迟队列
  • 权重排序
  • 热度榜
  • 时间线排序
  • 范围检索

常用命令

命令作用
ZADD key score member添加或更新分数
ZSCORE key member获取分数
ZRANGE key start stop [WITHSCORES]正序排名
ZREVRANGE倒序排名
ZRANGEBYSCORE按分数范围查
ZREM删除成员
ZCARD成员数量
ZINCRBY分数自增
ZRANK/ZREVRANK获取排名

底层结构

ZSet 通常由:

  • dict:member → score,支持 O(1) 查分数。
  • skiplist:按 score 排序,支持范围查询和排名。

小数据量时可能使用 listpack 编码以节省内存。

跳表为什么适合 Redis

跳表比红黑树更容易实现范围遍历,插入删除逻辑简单,性能期望 O(logN),适合 ZSet 的排名和范围查询。

4.6 Bitmap

Bitmap 本质是 String 的 bit 位操作。

使用场景

  • 用户签到
  • 活跃用户统计
  • 布尔状态记录
  • 大规模开关标记

常用命令

命令作用
SETBIT key offset value设置 bit
GETBIT key offset获取 bit
BITCOUNT key统计 1 的数量
BITOP AND/OR/XOR/NOT位运算
BITFIELD按位域读写

示例:签到

key:sign:user:1001:2026-05

  • 第 1 天 offset = 0
  • 第 2 天 offset = 1
  • 签到:SETBIT sign:user:1001:2026-05 11 1
  • 统计:BITCOUNT sign:user:1001:2026-05

4.7 HyperLogLog

使用场景

  • UV 统计
  • 大规模去重计数

特点

  • 极省内存。
  • 统计结果是近似值,有误差。
  • 不保存具体元素,不能反查成员。

命令:

  • PFADD key element
  • PFCOUNT key
  • PFMERGE dest src1 src2

4.8 GEO

GEO 用于地理位置存储和附近查询,底层基于 ZSet。

常用命令:

  • GEOADD
  • GEOPOS
  • GEODIST
  • GEOSEARCH

场景:附近商家、附近车辆、门店距离排序。

4.9 Stream

Stream 是 Redis 提供的日志型消息结构。

核心概念

概念说明
Stream消息流
Entry一条消息,包含 ID 和字段
Consumer Group消费者组
Consumer组内消费者
PELPending Entries List,待确认消息列表
ACK消费成功确认

常用命令

命令作用
XADD添加消息
XREAD读取消息
XGROUP CREATE创建消费者组
XREADGROUP消费者组读取
XACK确认消息
XPENDING查看待确认消息
XCLAIM转移长时间未确认消息
XTRIM裁剪流长度

Stream 与专业 MQ 对比

Redis Stream 适合轻量消息流和缓存体系内部异步任务,但如果需要复杂路由、事务消息、海量堆积、严格消息治理,RocketMQ/Kafka/RabbitMQ 更合适。


5. Key 设计与过期策略

5.1 Key 命名规范

推荐格式:

text
业务:模块:实体:标识:属性

示例:

text
user:profile:1001
order:detail:202605120001
shop:goods:list:shopId:10:page:1
lock:order:pay:202605120001
rate:user:1001:api:sendSms

原则:

  • 可读性强。
  • 避免过长。
  • 避免特殊字符混乱。
  • 同类 key 保持一致。
  • 集群场景需要考虑 hash tag,如 order:{1001}:detail

5.2 过期时间设计

过期时间不是随便设,要根据数据类型设计:

数据推荐 TTL
验证码1-5 分钟
登录 token与登录态策略一致
热点商品数分钟到数小时,可随机扰动
字典配置较长 TTL + 主动更新
分布式锁必须设置短 TTL,防死锁
限流 key与窗口周期一致

5.3 TTL 随机化

为避免大量 key 同时过期导致缓存雪崩,可以给 TTL 加随机扰动:

text
TTL = 基础时间 + random(0, 扰动时间)

例如商品缓存 30 分钟,可设置 30-35 分钟随机过期。


6. 持久化机制 RDB/AOF

Redis 是内存数据库,但支持持久化。持久化解决“重启后数据恢复”问题,不等于强一致落盘。

6.1 RDB

RDB 是某个时间点的内存快照。

触发方式

  • 手动 SAVE:阻塞主线程,不推荐线上使用。
  • 手动 BGSAVE:fork 子进程生成快照。
  • 配置规则自动触发。
  • 主从复制初次同步时触发。
  • 关闭 Redis 时可能触发。

优点

  • 文件紧凑,适合备份。
  • 恢复速度相对快。
  • 对主线程影响较小,因为主要由子进程写文件。

缺点

  • 可能丢失最近一次快照后的数据。
  • fork 子进程时可能有内存和延迟开销。
  • 大内存实例生成快照压力大。

6.2 AOF

AOF 记录写命令日志,通过重放命令恢复数据。

appendfsync 策略

策略含义风险
always每次写都刷盘最安全但最慢
everysec每秒刷盘常用,最多丢约 1 秒数据
no交给操作系统性能高但丢失风险大

AOF 重写

AOF 会越来越大,Redis 会重写 AOF:将当前数据状态转换成最小命令集合,减少文件体积。

6.3 RDB 与 AOF 对比

维度RDBAOF
文件内容数据快照写命令日志
恢复速度较快取决于日志大小
数据安全可能丢较多everysec 下最多约 1 秒
文件大小通常更大
适合备份、灾难恢复尽量减少数据丢失

6.4 混合持久化

混合持久化将 RDB 快照和 AOF 增量日志结合:前半部分是 RDB,后半部分是增量 AOF。优点是恢复快且丢失较少。

6.5 fork 与 Copy-On-Write

RDB/AOF 重写常用 fork 子进程。fork 后父子进程共享内存页;当父进程写入数据时,操作系统复制被修改的页,这就是 COW。写入越频繁,COW 内存开销越大。


7. 内存管理与淘汰策略

7.1 过期删除策略

Redis 删除过期 key 主要有两种方式:

  1. 惰性删除:访问 key 时检查是否过期,过期则删除。
  2. 定期删除:后台周期性抽样检查并删除过期 key。

为什么不定时删除每个 key?因为为每个 key 创建定时器成本太高。

7.2 内存淘汰策略

当内存达到 maxmemory 后,Redis 按策略淘汰 key。

策略说明
noeviction不淘汰,写入报错
allkeys-lru从所有 key 中淘汰最近最少使用
volatile-lru从设置过期时间的 key 中淘汰 LRU
allkeys-random从所有 key 中随机淘汰
volatile-random从设置过期时间的 key 中随机淘汰
volatile-ttl淘汰即将过期的 key
allkeys-lfu从所有 key 中淘汰最不常用
volatile-lfu从设置过期时间的 key 中淘汰 LFU

缓存场景常用 allkeys-lruallkeys-lfu。如果 Redis 里混有不能丢的数据,要慎重配置。

7.3 LRU 与 LFU

  • LRU:最近最少使用,看最近访问时间。
  • LFU:最不经常使用,看访问频率。

LRU 对偶发扫描污染敏感;LFU 更适合长期热点识别。

7.4 大 key 问题

大 key 指 Value 很大或集合元素过多。

风险:

  • 单命令耗时长,阻塞主线程。
  • 网络传输慢。
  • 删除释放内存慢。
  • 迁移、持久化、复制压力大。
  • 集群数据倾斜。

排查:

  • redis-cli --bigkeys
  • MEMORY USAGE key
  • SCAN + 类型检测
  • 慢日志与监控

治理:

  • 拆分 key。
  • 分页读取。
  • 使用 UNLINK 异步删除。
  • 控制集合长度,如 LTRIMXTRIM
  • 业务上限制单对象大小。

7.5 热 key 问题

热 key 是访问极高的 key,可能导致单节点 CPU 或网络打满。

解决:

  • 本地缓存 Caffeine。
  • 热 key 拆分副本,如 hot:goods:1:0~N
  • 读写分离。
  • CDN/边缘缓存。
  • 请求合并与互斥重建。
  • 热点探测与动态扩散。

8. 事务、Lua 与原子性

8.1 Redis 事务

Redis 事务使用:

text
MULTI
命令1
命令2
EXEC

特点:

  • 命令入队后统一执行。
  • 执行期间不会插入其他客户端命令。
  • 不支持传统数据库事务回滚。
  • 命令执行失败不会自动回滚之前命令。

8.2 WATCH 乐观锁

WATCH key 可以监控 key,如果事务执行前 key 被其他客户端修改,则 EXEC 失败。

适合 CAS 场景,但高并发下重试成本可能较高。

8.3 Lua 脚本

Lua 脚本在 Redis 中原子执行,适合多命令组合。

优点:

  • 减少网络往返。
  • 多步骤逻辑原子化。
  • 避免并发穿插。

注意:

  • 脚本不能太慢。
  • 不要在脚本中遍历大 key。
  • 集群中脚本访问的 key 通常需要落在同一 slot。
  • 需要用 KEYS[] 传 key,用 ARGV[] 传参数。

8.4 原子性边界

Redis 单条命令原子,Lua 脚本整体原子,但不代表业务端到端原子。例如“扣库存 + 写数据库 + 发消息”不能只靠 Redis 原子性保证全局一致。


9. 发布订阅、Stream 与消息队列

9.1 Pub/Sub

发布订阅命令:

  • PUBLISH channel message
  • SUBSCRIBE channel
  • PSUBSCRIBE pattern

特点:

  • 实时广播。
  • 不持久化消息。
  • 消费者离线会丢消息。
  • 不适合可靠消息队列。

9.2 List 队列

使用 LPUSH + BRPOP 可实现阻塞队列。

问题:

  • 消息被 pop 后如果消费者宕机,消息丢失。
  • 缺少 ACK、重试、死信。

可用 RPOPLPUSHBRPOPLPUSH 做处理中队列,但复杂度上升。

9.3 Stream 队列

Stream 支持消息 ID、消费者组、ACK、Pending 列表,更适合可靠消费。

但 Redis Stream 仍不是所有场景都替代 Kafka/RocketMQ。消息量巨大、长时间堆积、复杂路由和事务消息建议使用专业 MQ。


10. 主从复制、哨兵与集群

10.1 主从复制

Redis 支持一主多从。

用途:

  • 读写分离。
  • 数据冗余。
  • 支撑哨兵故障转移。

复制过程:

  1. 从节点连接主节点。
  2. 主节点生成 RDB 快照并发送给从节点。
  3. 从节点加载 RDB。
  4. 主节点持续发送复制缓冲区中的增量命令。
  5. 后续保持命令流同步。

10.2 全量复制与部分复制

  • 全量复制:传输完整 RDB,成本高。
  • 部分复制:根据复制偏移量和 repl backlog 传输缺失命令。

如果从节点断开太久,backlog 覆盖了缺失数据,就只能全量复制。

10.3 复制延迟

原因:

  • 主节点写入过高。
  • 网络延迟。
  • 从节点机器性能差。
  • 从节点正在执行慢命令。
  • 大 key 或大批量写。

影响:读从库可能读到旧数据。强一致读应读主库或设计读写一致性策略。

10.4 哨兵 Sentinel

Sentinel 负责监控、通知、自动故障转移。

核心功能:

  • 监控主从节点状态。
  • 主观下线和客观下线判断。
  • 选举 Leader Sentinel。
  • 选择新主节点。
  • 通知客户端新主地址。

主观下线:单个 Sentinel 认为主节点不可用。

客观下线:多个 Sentinel 达成 quorum 后确认主节点不可用。

10.5 Redis Cluster

Cluster 用于数据分片和高可用。

Slot 机制

Redis Cluster 有 16384 个 hash slot。key 通过 CRC16 计算落到某个 slot,slot 分布在不同主节点。

hash tag

使用 {} 可让多个 key 落到同一个 slot:

text
order:{1001}:detail
order:{1001}:items

这对多 key 操作、Lua 脚本很重要。

MOVED 与 ASK

  • MOVED:slot 已迁移到别的节点,客户端应更新路由缓存。
  • ASK:slot 正在迁移,客户端临时访问目标节点。

10.6 Cluster 限制

  • 多 key 操作要求 key 在同一 slot。
  • 数据倾斜会导致某些节点压力大。
  • 大 key 迁移成本高。
  • 客户端必须支持 Cluster 协议。

11. 缓存经典问题与解决方案

11.1 缓存穿透

问题

查询一个数据库也不存在的数据,缓存中没有,每次请求都打到数据库。

解决方案

  1. 缓存空值:数据库查不到也写入空标记,设置短 TTL。
  2. 布隆过滤器:判断请求 key 是否可能存在,不存在直接拒绝。
  3. 参数校验:非法 ID、异常参数直接拦截。
  4. 风控限流:对恶意请求限流。

注意

缓存空值要设置较短 TTL,否则新数据创建后可能短时间读不到。

11.2 缓存击穿

问题

热点 key 过期瞬间,大量请求同时打到数据库。

解决方案

  1. 互斥锁重建缓存:只有一个线程查库并回填,其余等待或返回旧值。
  2. 逻辑过期:缓存不物理过期,后台异步刷新。
  3. 热点 key 永不过期 + 主动更新
  4. 预热与续期

11.3 缓存雪崩

问题

大量 key 同时过期,或 Redis 整体不可用,导致请求集中打到数据库。

解决方案

  1. TTL 加随机值。
  2. 多级缓存。
  3. Redis 高可用集群。
  4. 限流、降级、熔断。
  5. 缓存预热。
  6. 关键数据逻辑过期。

11.4 缓存与数据库一致性

常见策略:

策略说明风险
先更新 DB 再删缓存常用删除失败会脏读
先删缓存再更新 DB不推荐并发下旧值可能回填缓存
更新 DB 后更新缓存写复杂,容易乱序并发覆盖风险
延迟双删先删缓存,更新 DB,延迟再删经验型方案,不是强保证
Binlog 订阅删缓存通过 Canal 等监听 DB 变更架构复杂

推荐基础方案:先更新数据库,再删除缓存。如果删除失败,使用重试队列、消息表、binlog 订阅或定时校验补偿。

11.5 为什么是删除缓存而不是更新缓存

因为缓存通常是复杂查询结果或聚合结果。直接更新容易遗漏关联缓存,并发下可能旧请求后写覆盖新值。删除后让下一次读重新构建,更简单可靠。

11.6 双写一致性深度分析

场景:线程 A 更新 DB,线程 B 读取。

推荐流程:

  1. 写请求更新 DB。
  2. 写请求删除缓存。
  3. 读请求缓存未命中,查 DB 回填。

极端风险:删除缓存失败,或主从数据库延迟导致回填旧值。解决靠补偿、读主库、延迟删除、版本号校验。


12. 分布式锁与并发控制

12.1 分布式锁基本要求

一个合格分布式锁至少要满足:

  • 互斥性:同一时刻只有一个客户端持有锁。
  • 防死锁:客户端宕机后锁能自动释放。
  • 不误删:只能释放自己的锁。
  • 可重入性:视业务需要。
  • 自动续期:长任务需要。
  • 高可用:Redis 故障时有策略。

12.2 SET NX EX

基础加锁:

text
SET lock:order:1 randomValue NX EX 30

释放锁必须比较 value 后删除,不能直接 DEL

lua
if redis.call('get', KEYS[1]) == ARGV[1] then
  return redis.call('del', KEYS[1])
else
  return 0
end

12.3 为什么 value 要唯一

如果客户端 A 获得锁后超时,锁自动释放;客户端 B 获得锁。此时 A 恢复后如果直接 DEL,会删掉 B 的锁。唯一 value 用于确认“这是我的锁”。

12.4 锁过期时间如何设置

锁 TTL 应大于业务正常执行时间,但不能无限长。长任务需要续期机制。Redisson 的 watchdog 会在锁未释放时自动续期。

12.5 Redisson

Redisson 提供:

  • 可重入锁
  • 公平锁
  • 读写锁
  • 联锁
  • 红锁
  • 信号量
  • 分布式集合
  • watchdog 自动续期

12.6 RedLock 争议

RedLock 试图在多个独立 Redis 节点上获取多数锁,提高容错。争议在于网络分区、时钟漂移、客户端停顿等情况下是否足够安全。

实践建议:

  • 普通业务互斥可用单 Redis/哨兵/集群 + 唯一值 + Lua 释放。
  • 极高一致性场景不要只依赖 Redis 锁,应考虑数据库事务、唯一约束、ZooKeeper/etcd、幂等和状态机。

12.7 分布式锁不等于幂等

锁只能降低并发冲突,不能替代幂等。支付、扣库存、订单状态流转必须依赖业务唯一键、状态机、数据库约束等最终兜底。


13. Redis 在 Java 中的使用

13.1 常见 Java 客户端

客户端特点
Jedis同步阻塞,API 接近 Redis 命令,早期常用
Lettuce基于 Netty,线程安全,支持同步/异步/响应式
Redisson提供分布式对象和锁,抽象更高
Spring Data RedisSpring 生态封装,常配合 RedisTemplate 使用

Spring Boot 默认常用 Lettuce。

13.2 RedisTemplate 与 StringRedisTemplate

  • RedisTemplate<Object, Object>:可自定义序列化,适合对象操作。
  • StringRedisTemplate:key/value 都按字符串处理,适合简单字符串和 JSON。

13.3 序列化选择

序列化优点缺点
JDK 序列化默认可用可读性差、体积大、跨语言差
String简单直观需要自行处理对象
JSON可读、跨语言体积略大、类型处理要规范
Kryo/Protostuff性能好、体积小引入复杂度、兼容性要管理

建议:业务缓存常用 String key + JSON value,避免 JDK 默认序列化带来的乱码 key 和迁移问题。

13.4 Spring Cache 集成

常用注解:

注解作用
@Cacheable查询时缓存结果
@CachePut执行方法并更新缓存
@CacheEvict删除缓存
@Caching组合多个缓存操作

注意:

  • 自调用不会触发代理。
  • key 设计要明确。
  • TTL 需要配置。
  • 复杂一致性场景不要盲目依赖注解。

13.5 Pipeline

Pipeline 将多个命令一次性发送,减少网络 RTT。

适合:

  • 批量写入。
  • 批量查询。
  • 初始化缓存。

注意:Pipeline 不是事务,不保证中间不插入其他命令。

13.6 连接池与超时

关键配置:

  • 连接超时。
  • 读写超时。
  • 最大连接数。
  • 最大空闲连接。
  • 最小空闲连接。
  • 命令超时。

线上必须设置超时,不能无限等待 Redis,避免线程池被拖死。

13.7 Java 缓存 Aside 模式伪代码

java
public ProductDTO getProduct(Long id) {
    String key = "product:detail:" + id;
    String json = redis.get(key);
    if (json != null) {
        if ("__NULL__".equals(json)) {
            return null;
        }
        return JSON.parseObject(json, ProductDTO.class);
    }

    Product product = productMapper.selectById(id);
    if (product == null) {
        redis.setex(key, 60, "__NULL__");
        return null;
    }

    redis.setex(key, 1800 + random.nextInt(300), JSON.toJSONString(product));
    return convert(product);
}

13.8 Java 分布式锁伪代码

java
String key = "lock:order:" + orderId;
String value = UUID.randomUUID().toString();
boolean locked = redis.set(key, value, NX, EX, 30);
if (!locked) {
    throw new BusyException();
}
try {
    // business
} finally {
    // use Lua: compare value then delete
    unlockByLua(key, value);
}

14. 常见业务场景设计

14.1 短信验证码

设计:

text
sms:code:{phone} -> code, TTL 5min
sms:limit:{phone} -> count, TTL 1min

要点:

  • 验证码短 TTL。
  • 发送频率限制。
  • 校验失败次数限制。
  • 成功后删除验证码。
  • 防止手机号枚举和短信轰炸。

14.2 用户签到

使用 Bitmap:

text
sign:user:{userId}:{yyyyMM}
  • 签到:SETBIT key day-1 1
  • 当月签到次数:BITCOUNT key
  • 连续签到:从当天向前扫描 bit。

14.3 排行榜

使用 ZSet:

text
rank:score:{biz}:{period}
  • 加分:ZINCRBY rank 10 userId
  • TopN:ZREVRANGE rank 0 99 WITHSCORES
  • 用户排名:ZREVRANK rank userId

要点:

  • 周榜/月榜分 key。
  • 历史榜单设置合理 TTL 或归档。
  • 分数相同时可组合时间权重。

14.4 限流

固定窗口计数器

text
rate:{api}:{userId}:{yyyyMMddHHmm}

INCR 后首次设置过期。

优点简单;缺点是窗口边界可能突刺。

滑动窗口

使用 ZSet 记录请求时间戳:

  1. 删除窗口外记录。
  2. 统计窗口内数量。
  3. 未超限则写入当前请求。
  4. 设置 TTL。

适合精确限流,但成本比固定窗口高。

14.5 延迟队列

使用 ZSet:

text
delay:queue:orderCancel
score = executeTimestamp
member = taskId/orderId

消费者轮询 ZRANGEBYSCORE now,抢到后 ZREM,再执行业务。

风险:

  • 轮询延迟。
  • 消费失败需重试。
  • 多消费者要确保抢占原子性,可用 Lua。

14.6 秒杀库存

Redis 常用于秒杀预扣库存:

  • 商品库存预热到 Redis。
  • Lua 原子判断库存和用户是否重复购买。
  • 成功后写入消息队列异步下单。
  • 数据库最终扣减,唯一索引防重复。

关键:Redis 只是前置削峰,最终一致性和账务正确性必须由数据库约束、消息可靠性和补偿机制保证。

14.7 购物车

可用 Hash:

text
cart:{userId}
field = skuId
value = count 或 JSON

优点:局部增删改方便。

14.8 关注关系

使用 Set:

text
followings:{userId}
followers:{userId}
  • 关注:两个 Set 同步写。
  • 共同关注:SINTER followings:A followings:B

注意大 V 粉丝 Set 可能成为大 key,需要拆分或落库为主。

14.9 Feed 流

  • 推模式:用户发动态时推到粉丝收件箱,读快写慢。
  • 拉模式:读时拉取关注用户动态,写快读慢。
  • 推拉结合:普通用户推,大 V 拉。

Redis 可存时间线索引,正文落 DB/对象存储。

14.10 布隆过滤器

用于缓存穿透防护。特点:

  • 判断不存在一定不存在。
  • 判断存在可能误判。
  • 不能直接删除普通元素。

可用 RedisBloom 模块或应用本地布隆过滤器。


15. 性能诊断与线上排障

15.1 慢查询

命令:

text
SLOWLOG GET 10
SLOWLOG LEN
SLOWLOG RESET

注意:慢查询统计的是命令执行时间,不包含网络传输和排队时间。

15.2 latency monitor

用于分析 Redis 内部延迟事件:

text
LATENCY DOCTOR
LATENCY LATEST

15.3 INFO 关键指标

常看模块:

模块关注点
clients连接数、阻塞客户端
memoryused_memory、碎片率、evicted_keys
persistencerdb/aof 状态
statsops、hits、misses、rejected_connections
replication主从延迟、复制状态
cpuCPU 使用
cluster集群状态
keyspacekey 数量和过期数量

15.4 缓存命中率

命中率:

text
keyspace_hits / (keyspace_hits + keyspace_misses)

命中率低可能原因:

  • TTL 太短。
  • key 设计不一致。
  • 缓存预热不足。
  • 访问模式不适合缓存。
  • 大量不存在请求穿透。

15.5 Redis 变慢排查顺序

  1. 查看慢日志。
  2. 查看 CPU 是否打满。
  3. 查看网络流量和连接数。
  4. 检查是否有大 key、热 key。
  5. 查看内存和淘汰情况。
  6. 查看 fork、RDB/AOF 重写。
  7. 查看主从延迟。
  8. 查看客户端连接池耗尽或超时。
  9. 检查是否使用了 KEYS/HGETALL/SMEMBERS 等危险命令。

15.6 常见线上故障

故障可能原因处理
Redis CPU 高热 key、慢命令、Lua 复杂查 slowlog、热 key 拆分
内存暴涨大 key、TTL 缺失、缓存污染bigkeys、设置 TTL、拆分
频繁淘汰maxmemory 太小、策略不当扩容、调整策略
连接数满连接泄漏、池配置不当查 clients、修复连接池
主从延迟大写入高峰、大 key、网络慢限流、拆分、扩容
AOF 重写卡顿fork/COW 压力调整重写时机、扩容内存

16. 安全与运维

16.1 安全配置

  • 不要暴露公网。
  • 设置密码/ACL。
  • 禁用或重命名危险命令。
  • 使用最小权限账号。
  • 开启 TLS(如环境需要)。
  • 绑定内网地址。
  • 配置防火墙和安全组。

16.2 备份策略

  • 定期 RDB 备份。
  • 备份文件异地存储。
  • 定期演练恢复。
  • 核心数据不能只放 Redis。

16.3 容量规划

需要考虑:

  • 原始数据大小。
  • Redis 对象开销。
  • 过期 key 比例。
  • 内存碎片。
  • fork COW 额外内存。
  • 主从复制 backlog。
  • 峰值写入。

16.4 上线检查清单

  • [ ] 是否设置 maxmemory
  • [ ] 是否选择合适淘汰策略。
  • [ ] 是否设置密码/ACL。
  • [ ] 是否禁用危险命令。
  • [ ] 是否配置持久化。
  • [ ] 是否配置主从/哨兵/集群。
  • [ ] 是否有监控和告警。
  • [ ] 是否存在大 key。
  • [ ] 是否存在无 TTL 的缓存 key。
  • [ ] 客户端是否设置超时和连接池。
  • [ ] 是否有降级方案。

17. Redis 深度面试题

17.1 Redis 为什么快?只答“因为内存”够吗?

不够。Redis 快的原因包括:基于内存、高效数据结构、单线程避免锁竞争、IO 多路复用、RESP 协议简单、命令复杂度低、后台任务与主线程分离等。真正面试时要强调:Redis 快的前提是命令不能太慢,一旦出现大 key、慢 Lua、全量扫描,单线程模型会放大阻塞影响。

17.2 Redis 单线程为什么还能支持高并发?

Redis 使用 IO 多路复用在一个线程中处理大量连接事件,命令执行通常是内存操作,耗时极短。单线程避免了多线程锁竞争和上下文切换。高并发不等于并行执行命令,而是事件处理效率高、单命令足够快。

17.3 Redis 真的只有一个线程吗?

不是。核心命令执行路径主要是单线程,但 RDB、AOF 重写、异步释放、网络 IO 的部分处理、复制等可能使用子进程或后台线程。面试应区分“命令执行单线程”和“Redis 进程只有一个线程”这两个概念。

17.4 Redis 事务为什么不支持回滚?

Redis 事务更像批量命令队列。它保证 EXEC 执行时命令连续执行,但不提供关系型数据库那种回滚机制。原因是 Redis 命令失败多属于编程错误,且回滚会增加复杂度和性能成本。业务上需要自己保证命令正确性,或用 Lua 做原子校验。

17.5 Redis 的过期 key 是如何删除的?

惰性删除 + 定期删除。访问 key 时发现过期会删除;后台周期性抽样检查过期 key 并删除。这样避免为每个 key 建定时器的高成本,同时也避免只靠惰性删除导致过期 key 长期占内存。

17.6 Redis 内存满了怎么办?

如果配置 maxmemory,达到上限后根据淘汰策略处理:noeviction 直接拒绝写入,LRU/LFU/random/TTL 等策略会淘汰 key。线上缓存通常选择 allkeys-lru 或 allkeys-lfu。但如果 Redis 中存放不可丢数据,就不能随意淘汰,必须隔离实例或调整设计。

17.7 LRU 和 LFU 有什么区别?

LRU 看最近是否访问,适合时间局部性强的场景;LFU 看访问频率,更能识别长期热点。LRU 容易被偶发批量扫描污染,LFU 对稳定热点更友好。

17.8 RDB 和 AOF 的区别?怎么选?

RDB 是快照,文件小、恢复快,但可能丢失最近一次快照后的数据。AOF 是写命令日志,数据安全性更好,但文件更大、恢复可能更慢。一般生产可使用 AOF everysec 或混合持久化,同时保留 RDB 做备份。具体选择取决于数据丢失容忍度和性能要求。

17.9 AOF 重写会阻塞 Redis 吗?

AOF 重写主要由子进程完成,不会全程阻塞主线程。但 fork 子进程、COW 内存复制、磁盘 IO 压力可能造成延迟抖动。大内存实例在重写期间如果写入很高,COW 开销会明显增加。

17.10 什么是缓存穿透、击穿、雪崩?

  • 穿透:查不存在的数据,每次都打 DB。解决:空值缓存、布隆过滤器、参数校验。
  • 击穿:热点 key 过期瞬间大量请求打 DB。解决:互斥重建、逻辑过期、热点永不过期。
  • 雪崩:大量 key 同时失效或 Redis 整体不可用。解决:TTL 随机化、高可用、限流降级、多级缓存。

17.11 如何保证缓存与数据库一致性?

常用策略是先更新数据库,再删除缓存。删除失败要有补偿机制,如重试队列、消息表、binlog 监听。不要轻易更新缓存,因为复杂查询缓存容易漏更新,并发下可能旧值覆盖新值。强一致场景不能只靠缓存策略,要以数据库事务和约束为准。

17.12 为什么不推荐“先删缓存再更新数据库”?

因为并发下可能出现:线程 A 删除缓存后准备更新 DB;线程 B 读缓存未命中,查到旧 DB 并回填缓存;线程 A 再更新 DB。最终缓存中是旧值,直到过期才恢复。

17.13 延迟双删能彻底解决一致性吗?

不能。延迟双删是降低旧值回填概率的经验方案,不是严格一致性证明。延迟时间很难精确设置,受数据库事务时间、主从延迟、业务执行时间影响。关键业务应使用补偿、版本号、binlog 订阅或直接读主库。

17.14 Redis 分布式锁怎样才安全?

基础要求:SET key value NX EX seconds 原子加锁;value 必须唯一;释放锁用 Lua 比较 value 后删除;锁必须有过期时间;长任务需要续期;业务必须幂等。不能使用 SETNX 后单独 EXPIRE 的非原子组合,也不能直接 DEL 释放。

17.15 Redisson watchdog 是什么?

Redisson 获取锁后,如果没有显式指定 leaseTime,会启动 watchdog 自动续期,默认周期性延长锁过期时间,防止业务未执行完锁就过期。业务完成释放锁后续期停止。如果服务宕机,watchdog 停止,锁最终过期释放。

17.16 RedLock 是否一定安全?

不能说一定安全。RedLock 通过多个独立 Redis 节点多数派加锁提高可用性,但在网络分区、进程长暂停、时钟漂移等场景仍有争议。高价值强一致资源建议使用数据库约束、状态机、ZooKeeper/etcd 或业务幂等兜底。

17.17 Big Key 会造成什么问题?

大 key 会导致单命令耗时长、网络传输慢、删除阻塞、持久化和复制压力增大、集群迁移困难、内存倾斜。治理方式是拆分、分页读取、限制集合长度、使用 UNLINK 异步删除、避免一次性 HGETALL/SMEMBERS。

17.18 Hot Key 如何发现和解决?

发现方式包括 Redis 监控、客户端埋点、代理层统计、slowlog、网络流量分析、redis-cli --hotkeys(需合适淘汰策略支持)。解决方式包括本地缓存、key 副本拆分、读写分离、热点预热、请求合并、限流降级。

17.19 Redis Cluster 为什么是 16384 个 slot?

Redis Cluster 通过 16384 个 hash slot 做分片。slot 数量在路由表大小、迁移粒度和管理成本之间折中。客户端只需维护 slot 到节点的映射。key 通过 CRC16 对 16384 取模定位 slot。

17.20 Cluster 中多 key 操作有什么限制?

多 key 操作要求所有 key 位于同一个 slot,否则会报 CROSSSLOT。可以使用 hash tag,如 order:{1001}:detailorder:{1001}:items,让 {1001} 参与 hash,从而落到同一 slot。

17.21 主从复制会不会丢数据?

可能。Redis 主从复制默认异步,主节点写成功后还没同步给从节点就宕机,故障转移后这部分数据可能丢失。可以通过 min-replicas-to-write 等配置降低风险,但会牺牲可用性和写入成功率。

17.22 哨兵如何判断主节点下线?

先由单个 Sentinel 判断主观下线;当足够数量 Sentinel 都认为主节点不可用,达到 quorum,形成客观下线。随后 Sentinel 选举 Leader,由 Leader 执行故障转移,选择一个从节点晋升为主节点。

17.23 为什么从库读可能读到旧数据?

主从复制是异步的,从库存在复制延迟。写入主库后立即读从库,可能读不到最新数据。解决方式:关键链路读主库、写后短时间读主、使用版本号判断、监控复制延迟并动态切换。

17.24 Pipeline 和事务有什么区别?

Pipeline 是客户端批量发送命令,减少网络往返,不保证原子性。事务是 Redis 服务端 MULTI/EXEC 命令队列,EXEC 时连续执行,具备一定原子执行边界。Pipeline 可以包含事务命令,但二者概念不同。

17.25 Lua 和事务有什么区别?

Lua 可以在服务端执行复杂逻辑,并且整个脚本原子执行,还能根据中间结果决定后续命令。事务只是命令排队,不能在 EXEC 前基于前一条命令结果动态分支。复杂原子校验通常优先用 Lua。

17.26 ZSet 为什么同时用 dict 和 skiplist?

dict 用于 O(1) 根据 member 查询 score;skiplist 用于按 score 排序、范围查询和排名。两者组合同时满足快速查分和有序范围访问。如果只用跳表,按 member 查找不够快;如果只用 dict,无法高效范围排序。

17.27 跳表相比红黑树有什么优势?

跳表实现简单,范围遍历自然,插入删除不需要复杂旋转,平均 O(logN)。Redis 作者选择跳表主要是工程实现和范围查询便利性的折中,并不是红黑树性能不行。

17.28 Redis Hash 什么时候比 String JSON 更合适?

当对象字段需要频繁局部更新、局部读取、计数字段自增时,Hash 更合适。String JSON 适合整体读写、结构灵活的对象缓存。若对象很大却只改一个字段,String JSON 会带来反序列化和全量写回成本。

17.29 Redis 可以做消息队列吗?

可以,但要看可靠性要求。List 可做简单队列但可靠性弱;Pub/Sub 不持久化,消费者离线会丢消息;Stream 支持消费者组、ACK 和 Pending,更接近消息队列。但复杂消息治理、大规模堆积和事务消息仍建议专业 MQ。

17.30 Stream 的 Pending List 有什么作用?

消费者组读取消息后,未 ACK 的消息会进入 PEL。通过 XPENDING 可查看待确认消息,通过 XCLAIM 可将超时未确认消息转移给其他消费者处理。这使 Redis Stream 具备一定消息可靠性和故障恢复能力。

17.31 Redis 为什么不能执行很慢的 Lua?

Lua 脚本执行期间 Redis 主线程被占用,其他命令无法执行。长脚本会造成全局阻塞,引发超时。脚本必须控制复杂度,避免大 key 遍历和不确定循环。

17.32 如何设计一个高并发商品详情缓存?

方案:商品详情使用 Cache Aside;热点商品预热;TTL 加随机扰动;空值缓存防穿透;互斥锁或逻辑过期防击穿;本地缓存承接极热读;更新商品时先更新 DB 再删缓存;删除失败进入重试;监控命中率、重建耗时和 DB 压力。

17.33 如何设计秒杀扣库存?

预热库存到 Redis;使用 Lua 原子判断库存、用户是否重复购买、扣减库存;成功后写入 MQ 异步创建订单;数据库用唯一索引防重复下单,用乐观锁或条件扣减保证库存不超卖;失败消息要补偿;Redis 库存和 DB 最终对账。

17.34 如果 Redis 宕机,系统如何降级?

按业务重要性分级:可读 DB 兜底但限流;热点页面返回本地缓存或静态降级;写链路绕过缓存直接写 DB;关闭非核心功能;启用熔断防止 DB 被打爆;恢复后预热缓存。不能让所有请求无保护地穿透到 DB。

17.35 如何避免缓存 key 被恶意打爆?

参数校验、认证鉴权、限流、布隆过滤器、空值缓存、业务 ID 范围约束、异常访问监控。缓存层不应成为非法请求放大器。

17.36 Redis 和本地缓存如何配合?

本地缓存如 Caffeine 延迟更低,适合极热点和变化不频繁的数据;Redis 作为分布式共享缓存。组合时要解决本地缓存失效通知,可用消息广播、短 TTL、版本号。风险是多实例本地缓存不一致。

17.37 什么是缓存预热?

系统启动或活动开始前,把热点数据提前加载进缓存,避免冷启动时大量请求打到 DB。预热数据来源可以是历史访问排行、运营配置、活动商品列表。预热后还要设置合理 TTL 和刷新策略。

17.38 什么是缓存污染?

大量低价值、只访问一次的数据进入缓存,占用空间并挤出热点数据。解决:只缓存高价值数据、设置较短 TTL、使用 LFU、按业务分实例或分命名空间、控制批量扫描类请求。

17.39 Redis 连接池耗尽怎么排查?

看应用线程是否大量阻塞在 Redis 调用;检查连接是否泄漏;查看 Redis CLIENT LIST;检查慢命令导致连接长期占用;核对连接池最大连接、等待时间、命令超时;确认网络是否抖动。修复时要设置合理超时并优化慢命令。

17.40 如何处理 Redis 中的无 TTL 缓存 key?

先区分业务语义:确实需要长期存在的数据和遗漏 TTL 的缓存。对缓存 key 应补充 TTL;对核心持久状态应考虑是否应该放 DB。可以通过 SCAN 抽样、命名规范和监控发现无 TTL key。

DEL 同步删除并释放内存,大 key 可能阻塞。UNLINK 先从 keyspace 解除关联,后台异步释放内存,更适合删除大 key。但它不是所有场景都能替代 DEL,核心区别在释放内存是否异步。

17.42 SCAN 能完全替代 KEYS 吗?

线上遍历应使用 SCAN,因为它渐进式返回,不会一次阻塞全库。但 SCAN 不保证一次遍历期间结果是强一致快照,可能出现重复或遗漏,需要客户端去重或容忍。运维离线场景才考虑 KEYS。

17.43 Redis 为什么会出现内存碎片?

频繁分配和释放不同大小对象会造成 allocator 碎片;大 key 更新、过期删除、AOF/RDB 期间 COW 也会影响内存表现。可通过 mem_fragmentation_ratio 观察,必要时开启主动碎片整理或重启迁移。

17.44 Redis 如何做附近的人/附近商家?

使用 GEO。写入经纬度和 member;查询时用 GEOSEARCH 按半径或矩形范围查找,并返回距离。底层是将经纬度编码为 geohash 后存入 ZSet。注意国内地图坐标系转换和距离排序精度。

17.45 Redis 如何实现排行榜同分按时间排序?

可将 score 设计为复合分数,例如主分数放大后叠加时间因子;或者 ZSet score 存主排序值,member 中带时间并在业务层二次排序。要注意浮点精度和分数范围。严谨场景可用两个结构辅助排序。

17.46 Redis 中存对象应该注意什么?

避免过大对象;明确序列化格式;key 和字段命名稳定;设置 TTL;处理版本兼容;避免把敏感信息无加密放入缓存;更新时考虑一致性;不要依赖缓存作为唯一事实来源。

17.47 什么情况下 Redis 不应该做缓存?

数据访问频率低、命中率低、强一致要求极高、数据体积巨大、查询结果高度个性化且难复用、维护缓存成本超过收益时,不适合缓存。缓存不是越多越好。

17.48 Redis Cluster 扩容时会发生什么?

新增节点后需要迁移一部分 slot 到新节点。迁移期间客户端可能收到 MOVED/ASK 重定向。大 key 会拖慢迁移,迁移过程中要关注延迟、失败重试和客户端兼容性。

17.49 如何保证 Redis 操作和数据库操作的一致提交?

Redis 和数据库之间没有天然分布式事务。通常通过最终一致性解决:数据库本地事务提交后发送消息/记录 outbox,再异步删除或更新缓存;失败可重试。强一致要求应把核心状态放数据库,用 Redis 做派生缓存。

17.50 面试中如何回答“Redis 用过哪些场景”?

不要只罗列“缓存、锁、队列”。应按场景讲清楚:业务背景、key 设计、数据结构选择、TTL、并发一致性、异常处理、监控指标。例如“商品详情缓存使用 String JSON + 随机 TTL + 空值缓存 + 互斥重建 + DB 更新后删缓存 + 删除失败重试”。


18. 速查表

18.1 数据类型选型

需求推荐类型
普通对象缓存String / Hash
局部字段更新Hash
计数器String INCR
去重集合Set
排行榜ZSet
延迟队列ZSet / Stream
签到Bitmap
UV 近似统计HyperLogLog
附近位置GEO
轻量可靠消息Stream

18.2 缓存问题速记

问题核心原因常用方案
穿透查不存在数据空值缓存、布隆过滤器
击穿热点 key 过期互斥锁、逻辑过期
雪崩大量 key 失效/Redis 故障TTL 随机、高可用、降级
不一致DB 与缓存双写先更新 DB 再删缓存 + 补偿
大 keyvalue/集合过大拆分、分页、UNLINK
热 key单 key 访问过高本地缓存、副本拆分、限流

18.3 生产禁忌

  • 不在线上使用 KEYS * 扫全库。
  • 不对大集合使用 HGETALL/SMEMBERS/LRANGE 0 -1
  • 不使用无 TTL 的缓存 key。
  • 不直接 DEL 释放别人的锁。
  • 不把 Redis 当强一致主数据库。
  • 不在 Lua 中写不可控循环。
  • 不把 Redis 暴露到公网。
  • 不让缓存故障无保护地打穿数据库。

18.4 高分面试回答公式

回答 Redis 问题时可按以下结构:

text
概念定义 → 底层原理 → 典型场景 → 风险边界 → 生产方案 → 监控/排障

例如回答分布式锁:

text
Redis 锁用于跨 JVM 互斥;底层依赖 SET NX EX 原子加锁;释放用 Lua 比较 value 再删除;长任务用 Redisson watchdog 续期;但锁不等于幂等,最终要用数据库唯一约束和状态机兜底;监控锁等待时间、失败率和业务超时。

19. 推荐学习路线

  1. 先掌握 String、Hash、List、Set、ZSet 的命令和场景。
  2. 理解 SDS、dict、skiplist、quicklist 等底层结构。
  3. 掌握过期删除、内存淘汰、RDB/AOF。
  4. 掌握缓存穿透、击穿、雪崩和一致性。
  5. 学会 Java 中 RedisTemplate、Lettuce、Redisson 的使用边界。
  6. 理解主从、哨兵、Cluster 和 slot。
  7. 通过真实项目练习缓存设计、分布式锁、限流、排行榜、延迟队列。
  8. 最后训练面试表达:不要背八股,要能讲清楚取舍、风险和兜底。

20. 面试前自检清单

  • [ ] 能解释 Redis 为什么快,并说出单线程边界。
  • [ ] 能区分 RDB、AOF、混合持久化。
  • [ ] 能说清楚过期删除和内存淘汰。
  • [ ] 能设计安全的分布式锁。
  • [ ] 能处理缓存穿透、击穿、雪崩。
  • [ ] 能解释缓存与 DB 一致性方案。
  • [ ] 能分析大 key、热 key、慢查询。
  • [ ] 能说明 ZSet 底层为什么用跳表。
  • [ ] 能描述主从复制、哨兵、Cluster。
  • [ ] 能结合 Java 项目讲 RedisTemplate/Redisson 实战。

Knowledge4J — Java 知识库