主题
界面
主题
《Redis 分布式锁》解决并发互斥;本节解决**"Redis 挂了会怎样"**:
持久化:解决「重启后数据还在吗」 → RDB / AOF
高可用:解决「它挂了服务还能用吗」 → 主从 / 哨兵 / 集群★ 两者的取舍是同一个主题:安全 vs 性能 ✓✓
min-replicas-to-write ★★;先看一个常见误解 ✗:
"Redis 是缓存,丢了就丢了,不用持久化"
→ 但如果它同时承担"分布式锁""延时队列""计数"([S4-010](./S4-010))
→ 重启后锁没了、计数归零、延时任务全丢 ✗✗★ 所以先问自己:Redis 里存的东西"丢了能重建吗" ✓✓
| 存的东西 | 丢了能重建吗 |
|---|---|
| 商品详情缓存 ✓ | 能(回源重建即可)→ 持久化要求低 |
| 分布式锁 / 计数 / 延时队列 ✗ | 不能 → 必须持久化,且要评估丢多少可接受 |
| 维度 | RDB(快照) | AOF(追加日志) |
|---|---|---|
| 记什么 | 某一时刻的全量数据 | 每一条写命令 |
| 文件大小 | 紧凑 ✓✓ | 大(实测大 1.7 倍)✗ |
| 恢复速度 | 快(直接读进内存)✓✓ | 慢(要重放命令)✗ |
| 丢数据 | 多(两次快照之间全丢)✗✗ | 少(取决于 fsync)✓✓ |
| 对性能影响 | fork 子进程时有影响 ⚠️ | 持续写盘 ⚠️ |
★ 本节的实测 ✓:
RDB:写入 10 万条数据后做一次快照
快照文件:3.32 MB,生成耗时 82 ms
AOF:记录 10 万条写命令
→ **AOF 比 RDB 大 1.7 倍** ✗
恢复:
从 RDB 恢复 100000 条: 92 ms ✓✓
从 AOF 恢复 100000 条: 180 ms
→ AOF 恢复慢约 2 倍 ✗★★ 两条结论 ✓✓:
| # | 结论 | 依据 |
|---|---|---|
| ① | RDB 恢复快、文件小 → 适合备份与全量同步 ✓✓ | |
| ② | AOF 丢数据少 → 适合要求不丢数据的场景 ✓✓ |
| 策略 | 每次写入的代价 | 崩溃时丢失 | 适用 |
|---|---|---|---|
always | 每次都等落盘(最慢) ✗ | 几乎不丢 ✓✓ | 金融级 |
everysec ★★ | 每秒 fsync 一次 | 最多 1 秒 ✓ | 默认,推荐 ✓✓ |
no | 交给操作系统 | 可能丢几十秒 ✗ | 纯缓存 |
★★ 同样的取舍在别处也出现过(可以对照理解) ✓✓:
| 中间件 | 参数 | 三档含义 |
|---|---|---|
| MySQL ★★ | innodb_flush_log_at_trx_commit | 1 / 2 / 0 ——完全一样的三档 |
| Kafka ★★ | acks | all / 1 / 0 |
★ 由此得到一条通用方法 ✓✓:
看到一个中间件,先问它"什么时候落盘",基本就能判断它的可靠性边界 ✓
Redis 4.0+ 的做法 ✓✓:
AOF 文件的前半部分 = RDB 格式的全量快照
AOF 文件的后半部分 = 增量写命令★ 恢复时 ✓:先加载快照(快),再重放少量增量(不丢) ✓✓
★ 还有"RDB 做备份、AOF 做恢复源"的常见组合 ✓:
| 用途 | 用什么 |
|---|---|
| 定时备份、异地容灾 | RDB ✓(文件小、传输快) |
| 实例重启恢复 | AOF(或混合) ✓ |
结构:一个主库(写)+ N 个从库(读)✓
★★ 关键认识:主从同步是"异步"的 ✓✓:
主库写入成功 → 立刻返回客户端 ✓
→ 后台异步把命令发给从库 ⚠️
所以:**主库刚写的值,从库可能读不到** ✗✗★ 这带来两个真实问题 ✓:
| 问题 | 表现 |
|---|---|
| 读写分离读到旧值 ★★★ | 用户刚下单,刷新页面却看不到(因为读到了从库)✗✗ |
| 主库宕机可能丢最后几条写 ★★ | 从库还没收到就切主了 ✓ |
★ 两个应对 ✓:
| 手段 | 说明 |
|---|---|
| 写后短期读主库 ★★ | 如"下单后 1 秒内的查询走主库" ✓ |
WAIT 命令 ⚠️ | 等待 N 个从库确认(会牺牲性能) |
★ 与本模块已有内容的呼应 ✓✓: 这正是 《缓存一致性与更新策略》 一致性问题的一部分 —— "缓存与 DB 不一致"和"主从读到旧值"是两个独立但同源的问题 ✓✓
| 方案 | 解决什么 | 局限 |
|---|---|---|
| 主从复制 ★★ | 读扩展 + 数据备份 | 主机挂了要手动切 ✗ |
| 哨兵(Sentinel) ★★★ | 自动故障转移 ✓ | 数据量受单机内存限制 ✗ |
| 集群(Cluster) ★★★ | 分片 + 高可用 ✓✓ | 运维复杂、跨槽操作受限 ⚠️ |
★ 哨兵的三个职责 ✓:
| 职责 | 说明 |
|---|---|
| 监控 | 每秒 ping 所有节点 ✓ |
| 判定客观下线 ★★ | 多个哨兵都认为主库挂了才判定(避免误判)✓ |
| 选举 + 故障转移 | 从从库中选一个提升为主,并通知客户端 ✓ |
★★ 脑裂问题(必须知道) ✓✓:
网络分区时:
旧主库还在接受写入(它以为自己是主)
哨兵已经选出新主库
→ **两个主库同时写** ✗✗
→ 网络恢复后,旧主库的数据被丢弃 ✗✗★ 缓解手段 ✓:
min-replicas-to-write 1 # 至少 1 个从库确认才允许写入
min-replicas-max-lag 10 # 从库延迟不超过 10 秒
→ 写不出去就拒绝写入,宁可少写也不产生"双主" ✗→✓★ 一条更深的认知 ✓✓:
任何"自动故障转移"都伴随着"可能丢最后几条写"的风险 ✓
这就是 CAP:高可用(A)与强一致(C)天然冲突 ✓✓ (《CAP 与 BASE 理论》 会专门展开)
★ 选型一句话 ✓✓:
数据量小 + 要自动切换 → 哨兵; 数据量大 + 要水平扩展 → 集群 ✓
★ 一条硬建议 ✓✓:
对"绝对不能丢"的数据,Redis 单独作为存储是不可靠的 ✗
即使开了 AOF everysec,也可能丢 1 秒 ✓
正确做法:Redis 做加速层,MySQL 做真相来源 ✓✓
★ 三种"Redis 不能单独承担"的数据 ✓:
| 数据 | 为什么 |
|---|---|
| 交易流水 / 订单 ✗ | 必须强持久化 ✓ |
| 账户余额 ✗ | 丢 1 秒就是资金差错 ✓ |
| 审计日志 ✗ | 合规要求 ✓ |
★ 反过来,这三类很适合放 Redis ✓:
| 数据 | 为什么 |
|---|---|
| 缓存 ✓ | 丢了能重建 |
| 限流计数 ✓ | 丢一点影响不大 |
| 排行榜 / 会话 ✓ | 可重算或重新登录 |
# RDB:900 秒内至少 1 次修改就快照
save 900 1
save 300 10
# AOF:开启且每秒 fsync
appendonly yes
appendfsync everysec
# 混合持久化(4.0+)
aof-use-rdb-preamble yes// 刚下单的用户,1 秒内的查询走主库(避免读到从库旧值)
if (System.currentTimeMillis() - lastWriteTime < 1000) {
return masterDao.query(id);
}
return replicaDao.query(id);重启 → 计数归零 → **生成重复订单号** ✗✗代码:labs/redis-lab/src/main/java/cn/kb/learn/redis/PersistenceLesson.java
运行:
cd labs
.\mvnw.cmd -pl redis-lab compile
java "-Dstdout.encoding=UTF-8" -cp redis-lab/target/classes cn.kb.learn.redis.PersistenceLesson实测输出(关键部分):
===== 1. RDB(快照)=====
写入 100000 条数据后做一次快照
快照文件:3.32 MB,生成耗时 82 ms
===== 2. AOF(追加日志)=====
→ **AOF 比 RDB 大 1.7 倍** ✗
===== 4. 恢复实测:RDB vs AOF =====
从 RDB 恢复 100000 条: 92 ms ✓✓
从 AOF 恢复 100000 条: 180 ms
→ AOF 恢复慢约 2 倍 ✗★ 三条可落地的结论 ✓:
| # | 结论 | 依据 |
|---|---|---|
| ① | RDB 恢复快、文件小 → 用于备份与快速恢复 | 92 ms vs 180 ms ✓ |
| ② | AOF 丢数据少 → 用于"不能丢"的场景 | 最多丢 1 秒(everysec)✓ |
| ③ | 生产建议开混合持久化 | 兼得"恢复快"与"丢得少" ✓✓ |
min-replicas-to-write 并模拟网络分区:思考脑裂的后果 ✓。| # | 错误 | 说明 |
|---|---|---|
| 1 | 以为"缓存不用持久化" ✗✗ | 锁、计数、队列丢了影响很大 ✓ |
| 2 | 把 Redis 当唯一存储存订单/余额 ✗✗ | 最多丢 1 秒 → 资金差错 ✓✓ |
| 3 | 读写分离后立刻读从库 ✗✗ | 读到旧值(5.4)✓ |
| 4 | 以为故障转移是"无损"的 ✗✗ | 可能丢最后几条写 ✓ |
| 5 | 不配 min-replicas-to-write ⚠️ | 脑裂时产生双主 ✓ |
| 6 | RDB 频率设得太高 ⚠️ | 频繁 fork 影响性能 ✓ |
| 7 | 不做备份演练 ✗✗ | 真出事时才发现备份恢复不了 ✓✓ |
目标:亲手量出两种持久化的取舍,并想清楚"丢多少可接受"。
步骤:
PersistenceLesson,记录文件大小与恢复耗时 ✓;验收方式:能说出"你的 Redis 里哪些数据绝对不能丢、目前的配置能不能保证"。
扩展任务:设计一个"Redis 故障降级方案":
某系统用 Redis 生成订单号:
long seq = redis.incr("order:seq");
String orderNo = "ORD-" + seq;如果 Redis 重启且没开持久化,会发生什么? 5. (选型)一个系统的 Redis 数据量 8 GB,要求自动故障转移。该用哨兵还是集群?为什么?
min-replicas-to-write ★★;Redis 五节到此完成。接下来进入 S4 的中段:消息队列、Nginx、Netty 与微服务。
暂无