Redis 缓存技术文档
Redis 缓存技术文档
1. 定位与适用场景
Redis 是基于内存的键值存储,单实例读性能约 10 万 QPS 量级,常用于:
- 缓存:挡住热点读流量,降低数据库压力。
- 会话/令牌存储:登录态、验证码、限流计数。
- 轻量队列与延迟任务:List / Stream / ZSet。
- 排行榜与去重:ZSet、HyperLogLog、Bitmap。
不适合做唯一真源(source of truth):内存存储成本高、持久化有窗口、单线程模型下慢命令会阻塞全局。
2. 缓存模式
2.1 Cache-Aside(旁路缓存,最常用)
读:先查缓存,未命中查库并回写缓存(带 TTL)。 写:先更新数据库,再删除缓存,不直接更新缓存。
先删缓存再写库,或先写库再删缓存,都有并发窗口。工程上默认选「先写库、后删缓存」,并把删除失败放入重试/消息队列补偿。
2.2 Read-Through / Write-Through
由缓存层封装读写逻辑,应用只与缓存交互。读未命中由缓存组件回源(如 Spring Cache CacheLoader、RedisJSON + 自定义代理)。适合读多写少、希望统一收口回源逻辑的场景。
2.3 Write-Behind(回写)
写只落缓存,异步批量刷库。吞吐最高,但存在数据丢失风险,仅适用于可容忍丢失的计数类数据。
2.4 本地缓存 + Redis 二级缓存
- L1:Caffeine / 进程内 Map,纳秒级,容量小、多实例不一致。
- L2:Redis,跨实例共享。
一致性通过变更时广播失效消息(Redis Pub/Sub、或 Kafka)解决,L1 只设很短 TTL(秒级)兜底。
3. 键设计规范
业务:资源:标识[:维度] 示例
user:profile:1001:zh
order:detail:20260101:8899
rate:login:ip:10.0.0.7
约定:
- 冒号分层,前缀可被
SCAN/ 监控按业务维度聚合;禁止无前缀裸键。 - 用业务主键,不用自增数据库 ID 之外的可变字段;避免键名包含时间戳导致数量爆炸。
- 单键 value 控制在 10 KB 以内,超过 100 KB 需拆分(大 key 会阻塞、拖慢迁移)。
- 明确类型:Hash 存对象字段、ZSet 存排行、String 存序列化对象,不滥用 String 存整个 JSON。
- 不存未压缩的大 JSON;必要时开启
list-compress-depth等结构级压缩。
4. 过期与淘汰
- TTL 策略:所有缓存键必须设过期时间,禁止永久键(除显式配置字典/元数据)。
- 过期精度:Redis 惰性删除 + 定期采样,实际释放可能滞后;不要依赖「到点立刻消失」。
- 淘汰策略:
allkeys-lru:纯缓存场景首选。volatile-lru:混布实例(部分键必须保留)时用。noeviction:写入会报错,仅用于禁止丢数据的实例。
- TTL 抖动:批量预热时为 TTL 加 ±10% 随机值,避免同一时刻集中失效。
5. 缓存三大问题与防护
| 问题 | 成因 | 防护 |
|---|---|---|
| 穿透 | 查询不存在的数据,缓存永不命中直压 DB | 缓存空值(短 TTL,如 60s);布隆过滤器前置拦截;参数合法性校验 |
| 击穿 | 热键过期瞬间大量并发回源 | 单飞(singleflight / 互斥锁重建);逻辑过期 + 异步续期 |
| 雪崩 | 大量键同时失效,或 Redis 整体不可用 | TTL 随机抖动;多级缓存;熔断降级限流;集群多副本 |
单飞示例(Go 语义):
// 同一 key 只允许一个 goroutine 回源,其余等待结果
group.Do(key, func() (any, error) {
v, err := db.Query(key)
if err != nil { return nil, err }
cache.Set(ctx, key, v, jitter(10*time.Minute))
return v, nil
})
6. 一致性
- Redis 与 DB 之间是最终一致,不存在零延迟强一致方案(除非加分布式锁串行化写路径,代价高)。
- 延迟双删:写库 → 删缓存 → 延迟几百毫秒再删一次,覆盖「读旧值回写」的窗口。
- 用 binlog / CDC(Canal、Debezium)订阅数据库变更投递失效消息,比业务代码里手写删除更可靠。
- 缓存值内可带版本号或更新时间,读取时与库对比,便于发现脏数据。
7. 持久化与高可用
- RDB:周期性快照,恢复快,可能丢最后一次快照后的数据。
- AOF:追加日志,
appendfsync everysec是性能与安全的平衡点,最多丢 1 秒。 - 生产建议 RDB + AOF 同开,
aof-use-rdb-preamble yes。 - 主从复制:异步复制,故障切换可能丢数据;缓存场景通常可接受。
- Redis Cluster:16384 个哈希槽,key 用
{}指定 hash tag 保证同槽(如user:{1001}:profile),跨槽 MGET / 事务不可用。 - Sentinel:主从 + 自动故障转移,适合容量不需要分片、只要高可用的场景。
8. 常见陷阱
KEYS *阻塞主线程 → 用SCAN游标遍历。- 单条命令操作大集合(
SMEMBERS、HGETALL于百万字段)→ 分批SSCAN/HSCAN。 - 事务(MULTI/EXEC)无回滚,Lua 脚本才是原子执行单元;脚本要限时长(
lua-time-limit)。 - Pipeline 减少 RTT,但不保证原子性。
- 连接不复用导致 TIME_WAIT 堆积 → 使用连接池并设置合理上限。
- 客户端超时设置过长会拖垮上游线程池,建议连接/读写超时 50–200 ms 级别并按业务调整。
- 大量 key 同时过期造成的
expired_keys尖峰,可通过--bigkeys/--hotkeys提前发现。
9. 监控与容量
关键指标:
used_memory/maxmemory、evicted_keys(淘汰速率是容量不足的第一信号)。hit_rate:keyspace_hits / (keyspace_hits + keyspace_misses),低于 80% 需检查键设计与 TTL。instantaneous_ops_per_sec、connected_clients、blocked_clients。latency与slowlog:slowlog-log-slower-than 10000(10 ms)。- 主从
master_link_status与复制延迟master_repl_offset差值。
容量估算:总内存 ≈ 键数据 + 每键开销(约 50–100 B)+ 副本/碎片余量(预留 30%)。设置 maxmemory 为实例内存的 60%–75%,留出 fork 与碎片空间。
10. 参考配置片段
maxmemory 4gb
maxmemory-policy allkeys-lru
timeout 300
tcp-keepalive 300
appendonly yes
appendfsync everysec
save 900 1
save 300 10
slowlog-log-slower-than 10000
slowlog-max-len 256