【WebWiki】10 · Redis
04 Redis
WebWiki · 缓存篇
1. 数据类型与典型场景
| 类型 | 底层结构 | 典型场景 |
|---|---|---|
| String | SDS | 缓存 JSON、计数器(INCR)、分布式锁(SET NX EX) |
| Hash | ziplist / hashtable | 对象缓存(按字段读写)、购物车 HINCRBY |
| List | quicklist | 消息队列(轻量)、最新列表 LPUSH+LRANGE |
| Set | intset / hashtable | 标签、共同关注 SINTER、抽奖 SRANDMEMBER |
| ZSet | 跳表+dict | 排行榜 ZRANGE、延时队列(score=执行时间) |
| Bitmap | String 位图 | 签到 SETBIT、日活统计 |
| HyperLogLog | 概率结构 | 亿级 UV 去重计数(0.81% 误差) |
| Stream | Radix 树 | 消息队列(消费组、ACK,比 List 专业) |
# 排行榜三连
ZINCRBY leaderboard:2026-09 1 "user:1001"
ZREVRANGE leaderboard:2026-09 0 9 WITHSCORES # Top10
ZREVRANK leaderboard:2026-09 "user:1001" # 我的排名
为什么 Redis 这么快(复习):纯内存操作、单线程避免锁/上下文切换、基于 epoll 的 I/O 多路复用、高效数据结构(下面第 2 节细讲)。6.0 引入多线程 I/O(仅网络读写多线程,命令执行仍单线程),进一步榨干多核网卡。
2. 五大数据类型的底层编码(面试核心)
Redis 的"类型"是给用户看的,底层由 encoding(编码) 决定真正怎么存。一条
TYPE显示hash,底层可能是ziplist(小数据)也可能是hashtable(大数据)。用OBJECT ENCODING key查看。
2.1、SDS(Simple Dynamic String,字符串底层)
Redis 没用 C 原生字符串(以 \0 结尾的 char*),而是自研 SDS:
struct sdshdr {
int len; // 已用长度,O(1) 取长度
int alloc; // 分配容量
int flags; // 头部类型
char buf[]; // 字节数组,二进制安全
};
相比 C 字符串的优势:
- O(1) 求长度:C 串要遍历到
\0,SDS 直接读len; - 二进制安全:能存图片/Protocol Buffer 等含
\0的数据,不靠\0判结尾; - 杜绝缓冲区溢出:
sdscat先检查空间,不够自动扩容; - 预分配 + 惰性释放:扩容翻倍(<1MB)或 +1MB(≥1MB),缩容不立即回收,减少内存重分配。
注意:Redis 的
String不仅能存字符串,还存整数(共享REDIS_SHARED_INTEGERS小整数对象池)、字节数组。当值是可以用long表示的整数时,底层用int编码,INCR直接改整数无需解析。
2.2、ziplist 与 listpack(紧凑列表)
- ziplist:一段连续内存压缩存储,省内存,适合小数据。每个 entry 记录"前一节点长度"(用于反向遍历),但级联更新是痛点:中间插入一个大节点导致"前一节点长度"从 1 字节变 5 字节,可能触发后续一连串重编码。
- listpack(7.0 起逐步替代 ziplist):改进设计,每个 entry 只记录自身长度,避免级联更新;
Hash/ZSet/List的小数据底层编码从 ziplist 迁移到 listpack。
当 Hash/List/ZSet 元素少且体积小(阈值可配:hash-max-listpack-entries、hash-max-listpack-value 等)时,用 listpack;超过阈值自动转成 hashtable / skiplist + dict。
# 查看底层编码
127.0.0.1:6379> HSET cart:u1 sku1 2
127.0.0.1:6379> OBJECT ENCODING cart:u1
"listpack" # 元素少→listpack
# 塞入大量/大字段后再次查看会变成 "hashtable"
2.3、quicklist(List 底层)
Redis 3.2+ 的 List 底层是 quicklist = 链表 + ziplist/listpack:宏观是双向链表,每个节点是一个小的 ziplist(控制每块大小 list-max-listpack-size),兼顾"节点插入删除 O(1)"和"内存紧凑"。老版本 List 早期用 ziplist+linkedlist,后统一为 quicklist。
2.4、intset(Set 小整数集合)
当 Set 全是整数且元素数不多(set-max-intset-entries)时,底层是 intset(有序整数数组,查找二分 O(log n));出现非整数或大元素则升级为 hashtable。
2.5、skiplist + dict(ZSet 底层,最重要)
ZSet(有序集合)底层用跳表(skiplist)+ 字典(dict)两张结构,各取所长:
- dict:
member → score映射,O(1) 查某成员分数(ZSCORE)。 - skiplist:按 score 排序的多层链表,支持范围查询、排名(
ZRANGE/ZRANK)O(log n)。
为什么用两个而不是只用一个?只靠 dict 无法按 score 排序取范围;只靠跳表查单点分数要 O(log n) 且占内存多(跳表每个节点要存 member+score+多层指针)。两个结构共享 member/score 对象,不重复存值,省内存又全能。
跳表原理:每个节点随机层数(幂次概率,越高层越稀),形成"高速公路"。查找时从最高层往右、往下,平均 O(log n),实现比平衡树简单、范围遍历友好。
层 3: head ──────────────────────────► 节点10
层 2: head ───► 节点3 ─────────────► 节点10
层 1: head ─► 节点1 ─► 节点3 ─► 节点6 ─► 节点10
(按 score 有序,每层是 next 指针)
2.6、dict 与渐进式 rehash(核心中的核心)
Hash、Set(hashtable)、ZSet(dict 部分) 都用哈希表 dict:
dict
├ ht[0] # 当前哈希表
├ ht[1] # rehash 时的目标表
├ rehashidx # rehash 进度(-1 表示不在进行)
└ 两个 ht 各是:数组 + 链地址法解决冲突
扩容/缩容时机:
- 负载因子
used / size≥ 1 且无BGSAVE/BGREWRITEAOF(或 ≥ 5 强制)则扩容,size 取 ≥ used*2 的最小 2^n; - 负载因子 < 0.1 缩容。
为什么"渐进式 rehash"而不是一次性搬? 如果一次性把 ht[0] 几百万 key 搬到 ht[1],会长时间阻塞(Redis 单线程!)。所以 Redis 采用渐进式:
- 分配 ht[1],置
rehashidx = 0; - 此后每次增删改查 dict 时,顺手把
ht[0]第rehashidx个桶的全部 key 搬到 ht[1],rehashidx++; - 还靠
serverCron定时任务分批搬(每毫秒搬若干桶),防止长时间无访问导致搬迁卡住; - 搬迁完成,
ht[0]释放,指针互换,rehashidx = -1。
搬迁期间查找"两个表都查"(先 ht[0] 再 ht[1]),新增只写 ht[1],保证 ht[0] 只减不增、最终搬完。
127.0.0.1:6379> DEBUG OBJECT myhash # 观察内部(含编码、长度等,调试用)
127.0.0.1:6379> OBJECT ENCODING myhash
"hashtable"
编码小结:小数据省内存(listpack/intset/ziplist),大数据上性能结构(hashtable/skiplist),阈值可配、自动转换。理解编码能解释"为什么同样命令内存占用差很多""大 key 为什么慢"。
3. 持久化
| 方式 | 原理 | 优缺点 |
|---|---|---|
| RDB | 定时 fork 子进程全量快照 | 文件小恢复快;可能丢最后几分钟 |
| AOF | 追加写命令(everysec 折中) | 丢数据少;文件大、重写有开销 |
| 混合持久化(4.0+) | RDB 头 + AOF 增量 | 生产推荐 aof-use-rdb-preamble yes |
3.1、RDB 机制细节
- 触发:
save(同步,阻塞,别用)、bgsave(异步 fork 子进程)、配置save 900 1(900 秒内 1 次改动)、主从全量同步时、执行flushall/shutdown(带 save) 时。 - fork + 写时复制(COW):
bgsave时父进程 fork 出子进程共享内存页;子进程把内存数据序列化进dump.rdb;期间父进程写某页,该页被 COW 复制一份,子进程读旧页——所以快照是 fork 那一刻的一致性视图,且内存占用峰值可能是 2 倍(大量写入时)。 - 优点:二进制压缩、恢复极快、适合备份/灾难恢复、fork 后主进程几乎无感。
- 缺点:两次快照间宕机丢数据(取决于
save间隔);fork 大内存实例有停顿(页表复制,几十 GB 内存 fork 可能百毫秒级)。
3.2、AOF 机制细节
- 原理:把每条写命令以 Redis 协议文本追加到
appendonly.aof。重启用redis-check-aof把文件读回来重放。 - 刷盘策略
appendfsync:always:每条命令 fsync,最安全、最慢;everysec:默认,每秒 fsync 一次,最多丢 1 秒,性能与安全折中(推荐);no:交给 OS 刷,最快、可能丢更多。
- AOF 重写(Rewrite):AOF 只追加会越来越大(如
INCR100 次存 100 行)。BGREWRITEAOFfork 子进程,根据内存现状生成"最小命令集"(如SET cnt 100),新 AOF 体积小。重写期间新命令同时写"旧 AOF"和"重写缓冲",重写完合并。 - 混合持久化(4.0+):
aof-use-rdb-preamble yes时,AOF 重写文件以 RDB 格式开头 + 增量 AOF 命令,重启先快速加载 RDB 再重放少量 AOF,兼顾速度与少丢数据。生产首选。
# 配置示例(redis.conf)
save 900 1
save 300 100
save 60 10000
appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes # 混合持久化
auto-aof-rewrite-percentage 100 # 体积翻倍触发重写
auto-aof-rewrite-min-size 64mb
选型:纯缓存(可丢)可只开 RDB;需持久化(如会话、排行榜)开 AOF + 混合。金融级再加主从/集群多副本。
4. 过期与淘汰
- 过期策略:惰性删除(访问时检查)+ 定期删除(抽样清理);
- 内存满时按
maxmemory-policy淘汰:
| 策略 | 说明 |
|---|---|
| noeviction(默认) | 写报错 |
| allkeys-lru | 全部键 LRU 淘汰(纯缓存推荐) |
| volatile-lru / volatile-ttl | 只淘汰带过期时间的键(有持久数据时用) |
| allkeys-lfu(4.0+) | 访问频率优先 |
深入:过期删除为什么是"惰性 + 定期"而非定时?
- 定时删除(每 key 一个 timer)太耗 CPU,key 多时不可行。
- 惰性删除:读/写某 key 时先
expireIfNeeded,过期则删。缺点:永不访问的过期 key 占内存(内存泄漏隐患)。- 定期删除:
serverCron每秒跑 10 次(默认),每次随机抽ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP(默认20) 个带过期时间的 key 清理,超时(默认 25ms)即停,循环到比例 < 25% 才收手。平衡 CPU 与内存。LRU vs LFU(4.0+ 推荐 LFU):
- LRU(最近最少用):近似 LRU(Redis 不维护完整链表,用
24bit 时间戳 + 随机采样 N 个淘汰最旧),仅看"多久没访问",易被一次性全扫的冷数据污染。- LFU(最不经常用):记访问频率(
lfu_log_factor控制计数增长、lfu_decay_time控制衰减),能识别"长期高频"vs"偶尔爆发",缓存命中率更高。生产纯缓存建议allkeys-lfu。
# 内存与淘汰配置
maxmemory 4gb
maxmemory-policy allkeys-lfu
# 采样精度(LRU/LFU 近似度)
maxmemory-samples 10
5. 缓存读写模式与三大问题

// Cache-Aside 读路径
public ProductVO getProduct(Long id) {
String key = "product:" + id;
String json = redis.get(key);
if (json != null) return JSON.parseObject(json, ProductVO.class);
// miss:加互斥锁防击穿(只放一个线程回源)
String lockKey = "lock:" + key;
if (redis.setIfAbsent(lockKey, "1", Duration.ofSeconds(3))) {
try {
Product p = productMapper.selectById(id);
if (p == null) {
redis.set(key, "", Duration.ofSeconds(60)); // 空值缓存防穿透
return null;
}
redis.set(key, JSON.toJSONString(p),
Duration.ofMinutes(30).plusSeconds(ThreadLocalRandom.current().nextLong(60))); // TTL 加随机防雪崩
return ProductVO.from(p);
} finally {
redis.delete(lockKey);
}
}
Thread.sleep(50);
return getProduct(id); // 自旋重试
}
| 问题 | 现象 | 对策 |
|---|---|---|
| 缓存穿透 | 查询不存在的数据,全打到 DB | 空值缓存、布隆过滤器、参数校验 |
| 缓存击穿 | 热点 key 过期瞬间大量回源 | 互斥锁重建、热点 key 永不过期+后台刷新 |
| 缓存雪崩 | 大量 key 同时过期 / Redis 宕机 | TTL 加随机、多级缓存、集群高可用、限流降级 |
写路径:先更新数据库,再删除缓存(Cache-Aside 标准写法);要求更高时用 Canal 订阅 binlog 异步删,实现最终一致。
三大问题深入(第 11 篇有落地命令,这里讲原理)
5.1、缓存穿透(查不存在的数据)
- 成因:恶意/异常请求查 DB 和缓存都没有的 key(如
id=-1、id=超长随机),缓存不命中 → 每次打到 DB,缓存形同虚设。 - 对策 1 空值缓存:DB 也没查到,缓存一个短 TTL 的空对象(如
""/NULL标记),拦住重复穿透。代价:占用一点内存、与 DB 有短暂不一致(空值过期前若数据补上,需主动删/等 TTL)。 - 对策 2 布隆过滤器(见 5.4):在缓存前加一层,所有合法 key 预先放进 BF,查前先问 BF;BF 说"不存在"直接拒绝,绝不透传到 DB。BF 说"可能存在"才放行(有误判但可接受)。
- 对策 3 参数校验/限流:id 范围、格式、频率校验,配合熔断降级。
5.2、缓存击穿(热点 key 突失效)
- 成因:某个极热点 key(如首页爆款、热搜)在过期瞬间,海量并发同时 miss,瞬间全部回源 DB,DB 被打垮。
- 对策 1 互斥锁重建:如上面代码,只放一个线程去 DB 加载,其他线程等/重试(本地锁或 Redis
SET NX分布式锁)。 - 对策 2 热点永不过期 + 后台刷新:逻辑过期(值里带
expireTime,读时不依赖 Redis TTL,另起线程/定时刷新),或物理不过期、由后台任务主动更新。代价:实现复杂、需保证最终一致。 - 对策 3 提前预热:大促前把热点数据主动灌缓存。
5.3、缓存雪崩(大量 key 同时失效 / Redis 挂)
- 成因:大量 key 设了相同 TTL(如都 1 小时),到点集体失效,瞬时全打 DB;或 Redis 集群整体宕机。
- 对策:
- TTL 加随机抖动:
base + random(0, base*0.3),错开失效时间(代码已体现); - 多级缓存:本地缓存(Caffeine)→ Redis → DB,Redis 挂还有本地兜底;
- 高可用:主从 + 哨兵 / Cluster,避免单点(见第 6 节);
- 限流降级:熔断、排队、返回默认,保护 DB;
- Redis 持久化 + 快速恢复:宕机后尽快拉起并预热带。
- TTL 加随机抖动:
5.4、布隆过滤器(Bloom Filter)原理与落地
布隆过滤器是"空间极小、查询极快、可能误判(false positive)、绝不漏判(false negative)"的概率型集合。
- 原理:一个 m 位 bit 数组 + k 个独立哈希函数。
- 加入元素 x:k 个哈希算 k 个位置,全部置 1;
- 查询元素 x:k 个位置全为 1 → "可能存在"(若有任一位 0 → "一定不存在");
- 误判来源:不同元素哈希位重叠,导致"查一个没加过的,结果位全 1"→ 误判存在,但绝不会把加过的判成不存在。
- 落地(Redis + Redisson 的 RBloomFilter):
// 初始化布隆过滤器(预计元素 1 亿,误判率 1%)
RBloomFilter<String> bf = redisson.getBloomFilter("user:id:bf");
bf.tryInit(100_000_000L, 0.01);
// 数据初始化/新增时把合法 id 加入
bf.add(String.valueOf(userId));
// 查询前先问布隆过滤器
public User getById(Long id) {
if (!bf.contains(String.valueOf(id))) {
return null; // 一定不存在,直接返回,不查 DB
}
// 可能存在 → 走缓存/DB(此处仍可能误判,再查 DB 兜底)
return userService.queryFromDb(id);
}
- 注意:布隆过滤器不支持删除(清位会影响别的元素);需要删除用 Counting Bloom Filter / Cuckoo Filter。误判率与
m/k相关:元素越多、误判率越高,初始化要估准容量。
6. 分布式锁
// Redisson:看门狗自动续期,可重入
RLock lock = redisson.getLock("order:pay:" + orderId);
try {
if (lock.tryLock(3, 30, TimeUnit.SECONDS)) { // 最多等 3s,持锁 30s
// 业务处理
}
} finally {
if (lock.isHeldByCurrentThread()) lock.unlock();
}
要点:加锁必须带唯一 value + 过期时间(防死锁与误删);释放用 Lua 保证"判断+删除"原子性;Redisson 已封装好上述一切。
手写分布式锁的正确姿势(理解原理,别只会调 Redisson)
6.1、SET NX EX 加锁
# 原子加锁:不存在才设、同时给过期时间(单条命令,避免 SETNX + EXPIRE 之间宕机导致死锁)
SET lock:order:1001 <唯一token> NX EX 30
# 唯一 token 用 UUID,保证"只能删自己加的锁"
- 为什么必须 NX + EX 一条命令:分开
SETNX再EXPIRE,若中间进程挂了,锁永不过期 → 死锁。合到一条命令才原子。 - 为什么 value 要唯一:避免 A 线程锁过期、B 拿到锁后,A 执行完误删 B 的锁。释放前先校验 value 是自己的。
6.2、Lua 脚本释放锁(保证原子)
-- 释放锁:只有 value 匹配才删(GET + DEL 原子执行)
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
# 调用(EVAL script numkeys key value)
redis-cli --eval unlock.lua lock:order:1001 , <唯一token>
不用 Lua 的话,GET 判等再 DEL 两步之间有竞态(判完过期、被别人拿到、你却删了别人的)。Lua 在 Redis 中单线程原子执行,无此问题。
6.3、锁续期(看门狗)
业务执行可能超过锁 TTL(如 30s 没跑完),锁过期被别人抢走 → 并发。Redisson 的"看门狗":加锁后起守护线程,每 TTL/3 续期一次(重置 EX 为 30s),直到 unlock。手写版可用"定时任务续期"或"Redisson 直接用"。
6.4、RedLock(红锁)与争议
Redis 作者提出多节点 RedLock(多数节点加锁成功才算拿到锁),应对单实例故障。但分布式时钟、GC 停顿等仍可能出问题,Martin Kleppmann 曾发文质疑。工程取舍:绝大多数场景"单 Redis 实例 + 过期 + 续期"足够;强一致需求考虑 ZooKeeper/etcd 的 CP 锁,或业务层幂等兜底(锁只是"尽量串行",最终靠幂等/版本号兜底)。
7. 高可用架构
主从复制:主写从读,异步复制(可能丢少量数据)
哨兵 Sentinel:监控 + 自动故障转移(至少 3 个哨兵节点),客户端走哨兵发现主
Cluster 集群:16384 个 slot 按 CRC16(key) 分片,数据分布到多主,横向扩容
| 方案 | 容量 | 适用 |
|---|---|---|
| 主从+哨兵 | 单机内存上限 | 中小规模、结构简单 |
| Cluster | 水平扩展 | 大数据量、高写入 |
Cluster 注意事项:多 key 命令要求同 slot(用 hash tag {user1001}.cart);批量操作用 mget 需分组。
7.1、主从复制原理
- 全量同步:从节点首次连主,发
PSYNC;主bgsave生成 RDB 发给从,从清空载入;期间主写命令进复制缓冲(repl_backlog),RDB 传完后再补发缓冲中的增量命令。 - 增量同步(命令传播):全量后,主每执行写命令都异步发给从;从断线重连若
repl_backlog还在窗口内,直接增量续传(PSYNC带offset),否则再全量。 - 异步复制:主不等从落盘即返回客户端,主从有复制延迟(从
info replication的master_repl_offsetvs 从slave_repl_offset差)。故障切换可能丢少量未同步数据。 - 读写分离:从只读(
replica-read-only yes),分担读;但从数据延迟——写后立即读从可能读不到(读写分离的一致性坑),重要读走主或加重试。
7.2、哨兵(Sentinel)原理
- 监控:哨兵定时
PING主从,超时(主观下线sdown)标记;多个哨兵确认主不可达达quorum→ 客观下线(odown)。 - 选举与故障转移:哨兵间用 Raft 类协商选"领头哨兵",领头从从节点里挑一个(优先级/复制偏移最新/runid 小)提升为主(
SLAVEOF NO ONE),其他从改认新主,通知客户端。 - 部署:至少 3 个哨兵节点(奇数,避免脑裂),且分散不同机器。客户端通过哨兵获取当前主节点地址,主切换自动感知。
- 缺点:仍受单机内存上限;在线扩容麻烦(Cluster 才解决)。
7.3、Cluster 槽位机制(分片核心)
- 16384 个 slot:Redis Cluster 把 key 空间分成 16384 个哈希槽。每个主节点负责一部分 slot(如 3 主各 ~5461 个)。
- 定位 key:
slot = CRC16(key) & 16383(哈希算法);客户端算 slot 直连对应节点。命令CLUSTER KEYSLOT key查看。 - 请求路由:客户端连任意节点,若 key 的 slot 不归该节点,返回
MOVED <slot> <node>重定向;迁移中返回ASK临时重定向。智能客户端( Lettuce/JedisCluster/Redisson)缓存 slot→node 映射,少走重定向。 - 扩容/缩容:用
redis-cli --cluster reshard把部分 slot 从旧节点迁到新节点(迁移中 key 用ASK引流),在线完成,无需停服。 - 多 key 约束:跨 slot 的命令(如
MGET、事务)默认报错。解决:- Hash Tag:用
{...}包裹决定 slot 的部分,{user1001}.cart和{user1001}.order同 slot,可一起操作; - 批量命令按 slot 分组后分别发。
- Hash Tag:用
- 高可用:每个主配若干从,主挂由 Cluster 自动故障转移(类似哨兵但内建)。
- 限制:不支持多 key 跨 slot 事务、Lua 脚本里 key 必须同 slot(用 hash tag);最大 1000 节点左右;DB 只有 0(Cluster 禁用
select)。
# 创建集群(3 主 3 从)
redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 \
127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 --cluster-replicas 1
# 查看 slot 分布
redis-cli -p 7000 cluster slots
# 迁移 slot(reshard 交互/参数)
redis-cli --cluster reshard 127.0.0.1:7000 --cluster-from <nodeA> \
--cluster-to <nodeB> --cluster-slots 1000 --cluster-yes
8. Spring Boot 整合
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory cf) {
RedisTemplate<String, Object> tpl = new RedisTemplate<>();
tpl.setConnectionFactory(cf);
tpl.setKeySerializer(new StringRedisSerializer());
tpl.setHashKeySerializer(new StringRedisSerializer());
tpl.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // JSON 存储
tpl.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return tpl;
}
}
建议再包一层 CacheService(封装 getOrLoad、TTL 随机、key 规范 业务:对象:id),避免业务代码直接拼 Redis 命令。
整合踩坑
- key 序列化:用
StringRedisSerializer让 key 可读;value 用 JSON(注意反序列化类型、循环引用、LocalDateTime 需自定义序列化器)。别用默认JdkSerializationRedisSerializer(二进制、人看不懂、跨语言不兼容)。- 连接池:生产用 Lettuce(
spring-boot-starter-data-redis默认)+ 配置lettuce.pool,高并发避免每次新建连接。- 事务/管道:
RedisTemplate.executePipelined(...)批处理;多命令合并减少 RTT(详见 11 篇 pipeline)。- 发布订阅 / Stream:
RedisMessageListenerContainer监听频道;StreamMessageListenerContainer消费 Stream 消费组。- 缓存注解:
@Cacheable/@CachePut/@CacheEvict配RedisCacheManager,注意 缓存穿透——@Cacheable不缓存 null 默认(可cacheNullValues=true)防穿透但会存空。
9. 小结
| 主题 | 要点 |
|---|---|
| 底层编码 | SDS、listpack、intset、quicklist、skiplist+dict、dict 渐进式 rehash |
| 持久化 | RDB 快照快、AOF 安全、混合持久化首选 |
| 过期淘汰 | 惰性+定期删除;LRU/LFU 按场景选 |
| 三大问题 | 穿透(空值/BF)、击穿(互斥锁)、雪崩(TTL随机+高可用) |
| 分布式锁 | SET NX EX + 唯一value + Lua 释放 + 看门狗续期 |
| 高可用 | 主从复制、哨兵选主、Cluster 16384 slot 分片 |
| 整合 | 序列化用 String+JSON、连接池、CacheService 封装 |
本篇讲原理与架构,第 11 篇《Redis 整理笔记》补充命令实战、应用场景、Pipeline/Lua、BigKey 排查。

浙公网安备 33010602011771号