Redis¶
一句话:Redis 在高并发系统里同时扮演"缓存"与"协调器"(分布式锁)两个角色,是降低数据库压力、保证分布式一致性的核心中间件。
概念¶
Redis 是基于内存的、单线程(核心命令)的、基于 IO 多路复用的 KV 存储,单实例 QPS 可达十万级。它在后端系统承担两类职责:缓存(挡住读流量、降低 RT、保护 DB)与分布式协调(分布式锁、限流计数、排行榜)。
缓存引入后立刻带来新问题:缓存与数据库一致性、缓存击穿/穿透/雪崩、热点 key。分布式锁带来的是锁的正确性争议(看门狗续期、Redlock 的安全性证明)。这些问题没有银弹,需要根据业务对一致性与性能的要求做权衡,这正是资深工程师的价值所在。
原理¶
分布式锁:从 setnx 到 Redisson¶
最朴素的分布式锁用 SETNX(Set if Not eXists)实现,但存在一系列正确性问题:
| 问题 | 成因 | 解法 |
|---|---|---|
| 锁非原子 | SETNX + EXPIRE 两步,中间宕机导致锁永不释放 |
SET key value NX EX 30 原子设值+过期 |
| 误删他人锁 | A 持锁超时自动释放,B 抢到锁,A 执行完误删 B 的锁 | value 存唯一标识,删除前 Lua 脚本校验 |
| 锁过期仍执行 | 业务执行超时,锁已释放但业务还在跑 | 看门狗(watchdog)自动续期 |
| 主从故障丢锁 | master 加锁后未同步到 slave 就宕机,切换后锁丢失 | Redlock(多实例过半数) |
Redisson 看门狗机制:Redisson 的 RLock 加锁时若不显式指定 leaseTime,默认 30 秒并启动一个后台线程(看门狗),每隔 10 秒(lockWatchdogTimeout/3)检查持锁线程是否还活着,活着就把过期时间重置回 30 秒,从而避免"业务没执行完锁就过期"。看门狗本质是一个定时续期的定时任务,依赖持有锁的客户端进程存活。
Redlock 争议¶
Redlock 是 antirez 提出的多 Redis 实例算法:向 N(通常 5)个独立 master 同时申请锁,超过半数(N/2+1)成功且耗时小于锁有效期才算加锁成功。目的是解决单点主从切换丢锁的问题。
Martin Kleppmann 在著名文章中批评 Redlock:依赖时钟同步,在 GC pause、进程暂停、时钟漂移下,仍可能出问题,对"需要正确性保证"的场景(如金融扣款)应使用基于共识(Zookeeper/etcd)的锁,Redis 锁只适合"对偶尔出错容忍、性能优先"的场景。这一争议是面试深水区。
缓存一致性¶
缓存与 DB 双写如何保证一致?常见策略对比:
| 策略 | 做法 | 问题 |
|---|---|---|
| 先更新 DB 再删缓存 | 写 DB → DEL cache | 删除失败则不一致(可重试/MQ 兜底) |
| 先删缓存再更新 DB | DEL cache → 写 DB | 并发下:A 删缓存 → B 查 DB 旧值写回缓存 → A 更新 DB,长期不一致 |
| 延迟双删 | 删缓存 → 写 DB → 延迟 N 秒再删一次缓存 | 修补"先删后写"的并发窗口,延迟时间难精确 |
| Canal 监听 binlog | DB 变更 → Canal 解析 binlog → 异步删缓存 | 最终一致、解耦、一致性最稳,但引入组件与延迟 |
工程上推荐 "更新 DB + 删除缓存 + Canal 兜底":写时先删缓存再更新 DB 并配合短延迟双删,同时用 Canal 监听 binlog 异步删除缓存做最终一致性兜底。
多级缓存¶
为扛高并发读,构建多级缓存:
| 层级 | 介质 | 特点 |
|---|---|---|
| L1 本地缓存 | Caffeine/Guava | 进程内,纳秒级,无网络开销,但多机不一致 |
| L2 分布式缓存 | Redis | 跨机共享,毫秒级 |
| DB | MySQL | 真相源 |
L1+L2 配合:先查本地,未命中查 Redis,再未命中查 DB 并回写。本地缓存适合"读多写少、可容忍秒级不一致"的数据(配置、字典)。
缓存三大问题与热点 key¶
| 问题 | 成因 | 解法 |
|---|---|---|
| 缓存穿透 | 查询不存在的数据,每次打到 DB | 缓存空值、布隆过滤器 |
| 缓存击穿 | 热点 key 过期瞬间,大量请求打到 DB | 互斥锁重建、热点 key 永不过期+异步刷新 |
| 缓存雪崩 | 大量 key 同时过期或 Redis 宕机 | 过期时间加随机偏移、多级缓存、Redis 集群高可用 |
| 热点 key | 单 key 流量集中打爆单分片 | 本地缓存、key 分片打散(多副本)、读写分离到 slave |
实战要点¶
结合 Redis 在交易链路与 Agent 平台(Redis 分布式锁/缓存、99.99% 可用)的经验:
-
分布式锁务必用 Redisson 而非手撸 setnx:Redisson 封装了原子加锁、看门狗续期、Lua 释放、可重入,避免手写的各种坑。手写 setnx 在"误删他人锁"和"锁过期业务仍在跑"上几乎必然踩坑。
-
看门狗不是万能,业务必须有超时兜底:看门狗依赖客户端进程存活,若进程假死(GC、死循环)会一直续期造成死锁。关键资源加锁必须设最大持有时间与业务超时,配合死锁检测告警。
-
缓存一致性优先"删缓存"而非"更新缓存":删除是幂等的、不依赖计算;更新缓存有并发覆盖风险。结合 Canal binlog 兜底是高一致性场景的稳妥方案。
-
热点 key 在大促前预识别:用离线分析+实时统计找出 TopN 热点 key(如爆款商品),提前做本地缓存预热与 key 分片打散,避免单分片被打挂。
-
金融/资金类锁不上 Redis:分布式锁的正确性要求高的场景(扣款、库存扣减),Redlock 仍有争议,应走数据库行锁、TCC 或 Zookeeper/etcd 共识锁。Redis 锁用于"防重复提交、限流、秒杀资格预筛"等容忍偶尔出错的场景。
-
多级缓存要设容量上限与淘汰策略:Caffeine 设
maximumSize与expireAfterWrite,防止本地缓存无界膨胀导致 OOM。
本节相关题目¶
| 难度 | 题目 | 链接 |
|---|---|---|
| 基础 | Redis 分布式锁实现、setnx 问题 | → 题库 |
| 进阶 | Redis 看门狗原理、Redlock 争议 | → 题库 |
| 进阶 | 缓存一致性:延迟双删 vs Canal binlog | → 题库 |