Redis
Redis 的后端工程定位:内存优先的数据结构服务,重点理解数据类型、TTL、缓存、持久化、复制、队列/Stream、原子操作与分布式协调边界。
[!info] related notes
- 所属 MOC:Backend 常用中间件与基础设施 MOC、Caching MOC
- 课程入口:BodySense Redis 从零到一课程 MOC
- 核心关系:缓存策略与一致性、Redis Expiration 与 Eviction、Redis 原子性、事务与 Lua、Redis Session Authority、分布式锁
- 持久化与运维:Redis RDB、AOF 与恢复、Redis 运维、观测与故障语义
- 消息边界:Redis Pub/Sub、Redis Streams、Messaging & Queues MOC、Kafka 与 RabbitMQ 的选型边界
- 实践:Redis 在 Go 后端的应用、Go 缓存策略
Redis
[!abstract] 一句话定位 Redis 是一个以内存为主要工作介质的数据结构服务器。它提供 String、Hash、Set、Sorted Set、Stream 等数据结构,以及 TTL、原子操作等能力,因此既可以作为缓存层,也可以直接承担 Session、限流、Token 状态、临时任务状态等高速运行状态。它通常用于加速或协调,不应默认取代承载复杂业务约束的关系型数据库。
核心心智模型
Client Command
→ Redis data structure operation
→ optional persistence / replication
→ response
Redis 的价值不只是“内存快”,而是几类能力的组合:
内存访问
+ 数据结构
+ TTL
+ 原子操作
+ 并发协调
其中:
- 数据主要在内存中访问,延迟低;
- 提供可原子操作的丰富数据结构,而不只是简单
key → value; - TTL 可以直接表达临时业务状态的生命周期;
- 某些运行时状态可以由 Redis 本身作为 authoritative source,而不是数据库缓存的副本。
学习时优先理解语义、复杂度和故障边界,不把某一版本的内部编码细节当长期契约。
Cache 与 Fast State Plane
不要把 Redis 简化成“数据库缓存”。两种用法的关键区别在于:Redis 中的数据是不是别处事实的可重建副本。
Cache
PostgreSQL / 其他事实源
↓ copy
Redis
↓
Redis 丢失后可以从事实源重新生成
例如用户资料缓存:Redis 中的 user:42 消失后,可以重新查询 PostgreSQL,因此 Redis 只是高速副本。
而 Session、验证码、限流计数、Refresh Token 使用状态等更接近高速状态层:
Fast State Plane
session:abc123 = active
login_attempt:1.2.x.x = 4
verification:xyz = valid
refresh:<digest> = active / consumed
这些状态可能根本没有一份 PostgreSQL 副本。Redis 中“存在 / 不存在、当前值、TTL”本身就是业务语义:
DEL session:abc123
→ Session 真正失效
verification:xyz TTL 到期
→ 验证码真正失效
因此可以这样区分:
Cache 是已有事实的高速副本;Fast State Plane 保存系统当前正在运行的状态,这个状态本身可以只存在 Redis 中。
Redis 也可以成为某些短期运行状态的 authoritative source,但这不意味着它应该默认承载用户档案、Assessment、诊断结果等长期业务事实。长期业务事实通常仍由 PostgreSQL 等持久数据库承担。
常用数据类型
| 类型 | 典型用途 | 关键边界 |
|---|---|---|
| String | Cache value、Counter、Flag、Token | Value 过大形成 Big Key |
| Hash | 小对象字段、局部更新 | 过大 Hash 仍会带来延迟和迁移成本 |
| List | 简单队列、栈 | 缺少完整 Consumer Group / ACK 语义 |
| Set | 去重、成员关系、集合运算 | 大集合操作可能昂贵 |
| Sorted Set | 排行榜、优先级、时间索引 | Score 设计决定排序语义 |
| Stream | 事件记录、Consumer Group、Pending | 需要自行设计重试、Trim 与幂等 |
| Bitmap / HLL / GEO | 位标记、近似去重、地理查询 | 精度与适用模型各不相同 |
[!tip] 选结构的原则 从需要的原子操作和查询模式出发,而不是“都序列化成 JSON String”。
Expiration 与 Eviction 不是一回事
Expiration
应用显式给 Key 设置 TTL,表达业务生命周期:
SET session:abc value EX 1800
TTL 到期后,Key 在业务语义上失效。Redis 通过访问时检查和后台过期处理清理它。
Eviction
内存达到 maxmemory 上限后,Redis 根据 eviction policy 选择 Key 淘汰。
Expiration → 时间到了,业务认为它过期
Eviction → 内存不足,系统提前移除某些 Key
因此 expired_keys 与 evicted_keys 是两个不同的观测信号。
Redis 作为缓存
进入 缓存策略与一致性 理解完整模型。最常见的是 Cache-aside:
Read
cache hit → return
cache miss → DB → fill cache → return
Write
update DB
→ after commit, invalidate cache
三类高频故障
| 问题 | 现象 | 常见治理 |
|---|---|---|
| 穿透 | 不存在的 Key 反复打 DB | 参数校验、空值短 TTL、Bloom Filter |
| 击穿 | 热点 Key 失效瞬间并发回源 | Singleflight / Mutex、逻辑过期、预热 |
| 雪崩 | 大批 Key 或节点同时失效 | TTL Jitter、多级缓存、限流与降级 |
缓存不可用时还要保护事实层,不能简单让所有流量穿透到数据库。
持久化:RDB 与 AOF
| 方案 | 模型 | 优势 | 主要代价 |
|---|---|---|---|
| RDB | 周期性时间点快照 | 文件紧凑、备份与恢复较直接 | 两次快照之间可能丢写入 |
| AOF | 记录写命令并重放 | 可提供更细的持久性窗口 | 文件、fsync、rewrite 与恢复成本 |
| RDB + AOF | 组合使用 | 兼顾备份与较细恢复 | 运维和 I/O 更复杂 |
| No Persistence | 纯临时缓存 | 最简单、无落盘成本 | 重启后数据消失 |
选择前先问:
Redis 中的数据能否重建?
最多能接受丢多少?
恢复时间要求是什么?
持久化造成的延迟和 fork / I/O 成本能否接受?
Replication、Sentinel 与 Cluster
Replication
Leader 将数据变化复制给 Replica。Replica 可提供冗余或读扩展,但异步复制可能产生 lag 和 stale read。
Sentinel
Sentinel 负责监控、故障判断、自动 failover 和客户端发现新的主节点,适合非 Cluster 的高可用拓扑。
Cluster
Redis Cluster 同时引入:
- Keyspace 分片;
- 每个分片的主从复制;
- Failover;
- Slot / Redirect;
- Multi-key locality 限制。
Replication → 同一份数据多副本
Cluster → 数据分片 + 每片高可用
Pub/Sub、Stream 与专用消息系统
Pub/Sub
适合在线广播。Subscriber 不在线时通常不会获得历史消息,不是 durable log。
Stream
提供 Entry ID、历史、Consumer Group、Pending Entries 和 Claim 等能力,适合已有 Redis 的中小规模消息场景。
Redis Stream → 轻量事件/任务
RabbitMQ → Work Queue、路由、ACK、Retry / DLQ
Kafka → 高吞吐事件日志、Retention、Replay、CDC
“用了 Stream”不等于自动获得 exactly-once 业务效果,仍要处理 ACK、Pending、重试和幂等。
原子操作与事务边界
Redis 单条命令具备明确原子语义;多步决策可考虑:
- 原子命令;
MULTI / EXEC;- Lua / Redis Functions;
- 乐观条件和版本检查。
MULTI / EXEC 不是关系型数据库那种任意多步失败后自动回滚的事务。复杂业务不变量仍应由事实数据库维护。
Refresh Token Rotation 是一个典型的原子状态转换问题:旧 Refresh Token 从 active 变为 consumed,同时生成新的 active Token;如果旧 Token 再次出现,就可以识别 replay。这个“检查旧状态 + 消费旧 Token + 建立新状态”的边界不能被并发请求拆开。
分布式锁的正确边界
基础 Lease 风格锁至少需要:
atomic acquire
+ unique owner
+ expiry
+ owner-checked release
但高风险外部副作用还要考虑:
- Client pause 后 Lease 已过期;
- 网络分区和 failover;
- 锁续期失败;
- 旧 Owner 仍在运行;
- Fencing Token / Version Check。
完整模型见 分布式锁。Redis 只是实现工具,不能替代正确性证明。
BodySense 实战锚点
BodySense 当前真正使用 Redis 的核心位置在 Go API,而不只是把它当作“未来可选缓存”:
Go API
├─ Session authority
│ ├─ session:<sid>
│ ├─ user_sessions:<userID>
│ └─ user_auth_revoked:<userID>
├─ Refresh Token rotation / replay detection
├─ Auth rate limiting
├─ Privacy erasure / global revocation
└─ Health check / managed Redis TLS
对应源码锚点:
apps/api/internal/database/redis.go:go-redis/v9连接、Ping、可选REDIS_TLS;apps/api/internal/cache/user_session.go:Session TTL、用户 Session Set、撤销 tombstone,并用 Lua 保证创建/撤销原子边界;apps/api/internal/service/auth_service.go:Opaque Refresh Token 的 digest key、rotation、replay tombstone、refresh family revoke;apps/api/internal/auth/rate_limit.go:INCR + EXPIRE的 Redis Lua 固定窗口限流;apps/api/internal/middleware/auth.go:Redis 是在线 session revocation authority,Redis 故障时鉴权 fail closed 返回 503,而不是让已撤销 Access Token 穿透;- dev / staging / production 都部署 Redis 7;当前 Compose 开启 AOF。
这意味着 BodySense 是很好的 Redis 学习实验场,但当前重点是:
TTL / String / Set
+ Lua atomicity
+ session / refresh lifecycle
+ rate limiting
+ failure policy
+ persistence / health
而不是先从 Redis Cluster、Pub/Sub 或 Stream 开始。AI Service 虽然声明了 Python redis 依赖,但当前源码没有实际 import/use,因此不能把它算成 AI Service 的现役 Redis 用法。
高频生产问题
Big Key
单次命令、网络传输、删除、复制和迁移都可能变慢。阈值应结合延迟 SLO、命令复杂度和实例容量,而不是死记固定 MB 数。
Hot Key
少量 Key 集中流量,可能压垮单个 shard。治理方向包括本地缓存、请求合并、读扩展、拆 Key 和容量保护。
Blocking Operation
复杂集合操作、过大 Lua、同步删除大对象或慢命令会阻塞命令处理路径。使用 SLOWLOG、latency monitoring 和命令级指标定位。
Pipeline
Pipeline 减少网络往返,但批次过大也会增加缓冲、单批延迟和错误处理复杂度。
观测与排障
至少关注:
latency / slow commands
ops and network throughput
memory / fragmentation
hit rate / misses
expired_keys / evicted_keys
connected and blocked clients
replication lag / failover
key distribution / hot key / big key
persistence and fork status
排障顺序:先确认用户延迟和错误,再检查慢命令、CPU/内存、网络、Key 分布、持久化、复制和下游连接。
什么时候不应优先使用 Redis
- 复杂关系查询和多表约束;
- 强事务业务事实;
- 大量长期冷数据;
- 全文搜索和复杂分析;
- 需要强持久事件回放的大规模平台。
官方资料
- Data types: https://redis.io/docs/latest/develop/data-types/
- Streams: https://redis.io/docs/latest/develop/data-types/streams/
- Persistence: https://redis.io/docs/latest/operate/oss_and_stack/management/persistence/
- Replication: https://redis.io/docs/latest/operate/oss_and_stack/management/replication/
- Distributed locks: https://redis.io/docs/latest/develop/clients/patterns/distributed-locks/
学习检查
不看正文回答:
- Redis 为什么不能只理解成“内存数据库”或“数据库缓存”?
- Cache 与 Fast State Plane 的权威性边界有什么不同?
- Expiration 与 Eviction 分别由什么触发,为什么不能混为一谈?
- Cache-aside 中穿透、击穿、雪崩分别是什么类型的问题?
- RDB 与 AOF 主要在什么维度上取舍?
- Replication、Sentinel、Cluster 分别解决什么问题?
- Pub/Sub 与 Stream 的可靠性语义有什么根本不同?
- 为什么单条 Redis 命令原子不等于多步骤业务流程天然原子?
- 为什么
SET NX EX还不是一个完整的分布式锁方案? - 给一个 Session、缓存、队列、复杂业务事实四类需求,判断 Redis 在其中应该是 authority、projection 还是不应优先使用。
掌握验收
- 能解释常用数据结构及典型原子操作;
- 能区分 TTL / Expiration 与 Eviction;
- 能解释 Cache-aside 以及穿透、击穿、雪崩的差异;
- 能比较 RDB / AOF,以及 Replication / Sentinel / Cluster 的职责;
- 能定位 Big Key、Hot Key、慢命令与阻塞操作;
- 能区分 Pub/Sub、Stream、RabbitMQ、Kafka 的消息语义边界;
- 能解释为什么
SET NX EX不是完整分布式锁方案; - 能区分缓存副本与 authoritative fast state;
- 能根据业务事实、恢复要求与一致性边界判断是否应该使用 Redis。