Redis 经典用例全解:从数据结构到系统设计
Redis 最容易被误解成“更快的数据库”。这个理解只对了一小半。Redis 更适合放在系统的热路径上,处理短生命周期状态、派生索引、原子协调、近实时统计和少量高频列表;完整事实仍然应该由数据库、日志或对象存储承载。
系统设计面试里,Redis 的价值也不在命令背诵。更重要的是把业务需求翻译成几个稳定的问题模型:
- 这份数据是否可以过期
- 这份数据丢了能否重建
- 读路径是否远热于写路径
- 是否需要排序、范围查询、集合运算或原子判断
- Redis 故障时,系统还能不能保持核心正确性
答案如果把 Redis 当成事实库,通常会在持久性、审核追溯、深页查询或跨 key 一致性上掉坑。更可靠的边界是:数据库保存事实,Redis 保存热路径和派生状态。
Redis 的系统设计位置
flowchart TD
Req["业务需求"] --> Sem["抽象操作语义"]
Sem --> DS["选择 Redis 数据结构"]
DS --> Key["设计 Key 和分片边界"]
Key --> Cons["定义一致性边界"]
Cons --> Fail["故障与降级"]
Sem --> S1["单值状态"]
Sem --> S2["结构化对象"]
Sem --> S3["排序列表"]
Sem --> S4["集合关系"]
Sem --> S5["概率统计"]
Sem --> S6["消息流"]
S1 --> R1["String"]
S2 --> R2["Hash"]
S3 --> R3["Sorted Set"]
S4 --> R4["Set / Bitmap"]
S5 --> R5["HyperLogLog / Bloom"]
S6 --> R6["Stream / List"]
Redis 方案设计可以按四步落地:
| 步骤 | 要回答的问题 | 常见错误 |
|---|---|---|
| 抽象操作 | 业务本质是读写单值、排序、集合、位图、消息流,还是原子判断 | 上来先套命令 |
| 设计 Key | Key 的粒度、生命周期、hash slot、热点边界是什么 | 一个业务对象塞进一个大 key |
| 明确事实源 | 哪些数据必须落数据库,哪些只是 Redis 派生状态 | 把 Redis 当不可丢事实库 |
| 设计降级 | Redis 慢、丢、不可用时,读写路径如何退回 | 没有回源与修复路径 |
数据结构速查
| 数据结构 | 本质 | 适合的问题 | 典型命令 |
|---|---|---|---|
| String | 二进制安全字节串 | 缓存、计数、锁、Session、幂等 token | GET、SET、INCR、SET NX EX |
| Hash | 一个 key 下的字段表 | 对象缓存、购物车、计数聚合 | HSET、HGET、HINCRBY |
| List | 双端队列 | 简单任务队列、最新日志 | LPUSH、RPOP、BRPOP |
| Set | 无序唯一集合 | 去重、关注关系、交并差 | SADD、SISMEMBER、SINTER |
| Sorted Set | 带 score 的唯一集合 | 排行榜、时间线、延迟队列、限流窗口 | ZADD、ZRANGE、ZRANGEBYSCORE |
| Bitmap | 位数组 | 签到、在线状态、大规模布尔状态 | SETBIT、GETBIT、BITCOUNT |
| HyperLogLog | 概率基数统计 | UV、独立访客、粗粒度去重计数 | PFADD、PFCOUNT |
| GEO | 经纬度索引,底层是 ZSet | 附近的人、门店搜索 | GEOADD、GEOSEARCH |
| Stream | 追加式消息日志 | 可靠消息队列、事件流、消费者组 | XADD、XREADGROUP、XACK |
八个可迁移模式
| 模式 | 核心公式 | 覆盖场景 | 边界 |
|---|---|---|---|
| KV + TTL | SET {key} {value} EX {ttl} |
缓存、Session、验证码、短链接 | 适合可过期状态 |
| 原子占位 | SET {key} {owner} NX EX {ttl} |
分布式锁、幂等、击穿保护 | 不能替代数据库事务 |
| Score 即数轴 | ZADD {key} {score} {member} |
排行榜、延迟队列、时间线、限流 | 大 ZSet 和热 key 要治理 |
| 双向关系 | SADD A B 与 SADD B A |
关注、粉丝、共同好友、权限交集 | 大 V 关系不适合全量 Set |
| 实体 + 索引分离 | HSET entity + ZADD index |
评论、商品列表、搜索结果缓存 | Redis 索引是派生状态 |
| 概率换空间 | PFADD / BF.ADD |
UV、穿透防护、大规模去重 | 允许误差或误判 |
| 位图压缩 | SETBIT {key} {offset} 1 |
签到、在线、开关状态 | offset 需要可控 |
| Lua 原子胶水 | 多命令封进脚本 | 限流、锁释放、延迟队列消费 | Cluster 下跨 slot 受限 |
用例一:缓存系统
问题
数据库中的热点数据被反复读取,查询成本高,响应慢。缓存系统要降低数据库压力,同时避免脏读、击穿、穿透和雪崩。
设计
最常见的模式是 Cache Aside。应用先读 Redis,未命中再读数据库,并把结果写回 Redis。
sequenceDiagram
participant App
participant Redis
participant DB
App->>Redis: GET cache:user:123
alt 命中
Redis-->>App: 用户缓存
else 未命中
Redis-->>App: nil
App->>DB: SELECT user
DB-->>App: 用户事实
App->>Redis: SET cache:user:123 {json} EX 3600
end
1 | |
写路径通常采用“先更新数据库,再删除缓存”。删除比更新缓存更稳,因为缓存值可能由多张表、多段业务逻辑组合而来,写路径未必拿得到完整新值。
三类缓存问题
| 问题 | 现象 | 处理 |
|---|---|---|
| 缓存穿透 | 查询不存在的数据,每次都打到数据库 | 空值缓存短 TTL,或 Bloom Filter 预判 |
| 缓存击穿 | 热点 key 过期,大量请求同时回源 | 单飞互斥、逻辑过期、旧值兜底 |
| 缓存雪崩 | 大量 key 同时过期或 Redis 故障 | TTL 加随机偏移,多级缓存,限流降级 |
1 | |
取舍
Redis 缓存适合动态热点数据,不适合替代数据库保存核心事实。缓存一致性要控制的是脏数据窗口:业务能接受多长时间的不一致,就按这个窗口设计主动失效、被动回源和后台修复路径。
模式提炼:KV + TTL
任何“有生命周期、可重建、读多写少”的状态,都可以先按 KV + TTL 建模。
| 场景 | Key | Value | TTL |
|---|---|---|---|
| 用户缓存 | cache:user:{id} |
用户 JSON | 10 分钟 |
| 商品详情 | cache:product:{id} |
商品 JSON | 5 分钟 |
| 空值缓存 | cache:null:user:{id} |
空字符串 | 30 秒 |
| 短链解析 | short:{code} |
原始 URL | 7 天 |
用例二:Session、验证码与临时令牌
问题
多实例服务需要共享登录态、验证码、临时授权码和幂等 token。这些状态有天然过期时间,访问频繁,数据量可控。
设计
1 | |
Session 的事实源通常来自用户登录事件和账户系统。Redis 保存的是“当前有效状态”。Redis 故障时,可以让用户重新登录;这影响体验,但不破坏核心账务事实。
取舍
| 方案 | 优势 | 代价 |
|---|---|---|
| 本地 Session | 延迟最低 | 多实例扩展差 |
| Redis Session | 多实例共享,过期简单 | Redis 可用性影响登录态 |
| JWT | 服务端无状态 | 撤销、续期、权限变更更复杂 |
模式提炼:短生命周期状态
验证码、登录态、授权码、幂等 token 的共同点是“状态本身会自然死亡”。TTL 在这里不是附加配置,它就是数据模型的一部分。
用例三:分布式锁与幂等控制
问题
多个服务实例同时处理同一资源时,需要保证某段临界区同一时间只有一个执行者。典型场景包括缓存重建、定时任务抢占、重复请求防重。
设计
加锁使用 SET NX EX,释放锁必须校验 owner 后删除。
1 | |
释放锁用 Lua:
1 | |
value 不能省略。没有 owner 校验时,一个慢请求可能删掉另一个请求刚拿到的锁。
Redlock 与看门狗
Redlock 试图在多个独立 Redis 节点上获取多数派锁,降低单点故障风险。它适合“重复执行代价可控”的互斥场景,不适合金融账务、库存最终扣减这类强一致临界区。强一致需求应该回到数据库事务、乐观锁、唯一约束或专门的协调系统。
看门狗续期适合执行时间不确定的任务,但会引入新的风险:业务线程卡死时,锁可能被错误续期。续期机制必须绑定持有者健康状态,并设置最大持有时间。
取舍
| 适合 Redis 锁 | 不适合 Redis 锁 |
|---|---|
| 缓存重建互斥 | 资金扣减 |
| 定时任务抢占 | 唯一订单创建 |
| 幂等处理中间状态 | 强一致库存扣减 |
| 重复请求短期防重 | 长事务资源锁定 |
模式提炼:原子占位
SET NX 表达的是“谁先到谁占坑”。分布式锁只是其中一种用法。
| 场景 | Key | Value | TTL |
|---|---|---|---|
| 缓存击穿保护 | lock:rebuild:{key} |
请求 ID | 10 秒 |
| 接口幂等 | idem:pay:{token} |
处理结果 | 24 小时 |
| 任务抢占 | job:owner:{jobId} |
worker ID | 60 秒 |
用例四:计数器与限流器
问题
阅读量、点赞数、库存预扣、接口访问频率都需要高并发更新。数据库每次加一会产生热点行,应用内计数又无法跨实例共享。
设计:计数器
1 | |
计数分两类。余额、库存、账务属于事实,不能只放 Redis。阅读量、点赞展示数、评论展示数通常是派生指标,可以 Redis 聚合后异步落库,并由后台定期校准。
设计:滑动窗口限流
1 | |
这组命令要封进 Lua,避免并发请求在 ZCARD 和 ZADD 之间穿过限制。
设计:令牌桶
令牌桶适合“允许瞬时突发,但限制长期平均速率”的接口。Redis Hash 可以保存当前令牌数和上次补充时间。
1 | |
令牌桶的计算必须在 Lua 中完成:读状态、补令牌、判断、扣令牌、写回状态是一组原子动作。
取舍
| 方案 | 精度 | 成本 | 适合场景 |
|---|---|---|---|
| 固定窗口 | 低 | 低 | 粗限流 |
| 滑动窗口 | 高 | 中 | 登录、评论、发帖 |
| 令牌桶 | 中 | 中 | API 网关、突发流量 |
模式提炼:Score 即时间轴
滑动窗口的本质是把请求时间放进 ZSet 的 score。凡是“按时间范围取一段数据”的问题,都可以先尝试 ZSet。
用例五:排行榜
问题
排行榜需要实时更新分数、查询 Top N、查询某个用户名次、查询用户附近的排名。数据库可以做排序,但高频更新和实时 Top N 会很昂贵。
设计
1 | |
多周期榜单通常按时间拆 key:
1 | |
设计要点
| 问题 | 处理 |
|---|---|
| 同分排序 | score 编码进次级排序,或应用层按 ID 二次排序 |
| 榜单过大 | 只保留 Top N,长尾落数据库 |
| 多维排序 | Redis 不适合复杂组合排序,交给搜索引擎或 OLAP |
| 作弊分数回滚 | 分数事件化,保留可重算依据 |
模式提炼:分数数轴
ZSet 的 score 不一定是“分数”,也可以是时间戳、过期时间、热度分、距离编码。业务可以映射到一条数轴时,就能使用 ZSet 做范围和排名。
用例六:延迟队列
问题
订单超时关闭、30 分钟后发送提醒、延迟重试等需求都需要“到时间再执行”。专业 MQ 往往有延迟消息能力;没有 MQ 或规模较小时,Redis ZSet 可以实现轻量延迟队列。
设计
1 | |
查询到期消息和删除消息必须原子化,否则多个消费者会重复处理同一条消息。
1 | |
取舍
Redis 延迟队列适合低到中等规模的延迟任务,不适合长时间海量堆积、严格投递保证、复杂重试策略。任务结果必须幂等,消费者失败后要能补偿或重入。
用例七:社交关系
问题
关注、粉丝、共同好友、是否互相关注等需求,本质上是集合成员关系和集合运算。
设计
1 | |
互相关注可以在关注写入时顺手判断:
1 | |
若成立,再写入 friends:userA 和 friends:userB。
取舍
普通用户的关注列表适合 Set。大 V 粉丝列表可能达到千万级,完整放进单个 Set 会带来大 key、迁移慢、集合运算阻塞等问题。大 V 粉丝可以只存计数,或按分片 key 存储,推荐列表交给专门的图计算或推荐系统。
模式提炼:双向关系
一个关系通常有两个查询方向。关注关系要查“用户 A 关注了谁”,也要查“谁关注了用户 A”。写两份 Set 是有意的冗余,用写放大换读简单。
用例八:SNS 二级评论系统
问题
评论系统表面是 CRUD,实际考察产品语义、分页稳定性、热点治理、审核删除和缓存边界。一个 SNS 评论系统通常要支持:
| 维度 | 设计假设 |
|---|---|
| 层级 | 帖子下有一级评论,一级评论下有回复 |
| 排序 | 默认按时间倒序,可扩展热度排序 |
| 展示 | 首屏展示一级评论,每条带少量回复预览 |
| 删除 | 作者删除、平台隐藏、审核中状态 |
| 一致性 | 评论事实不能丢,计数和排序可以短暂延迟 |
| 风控 | 限流、敏感词、重复提交、恶意刷屏 |
假设 1 亿 DAU,10% 用户每天发表评论,活跃评论用户日均 2 条:
| 指标 | 粗估 |
|---|---|
| 写入量 | 100M * 10% * 2 = 20M/day |
| 平均写 QPS | 约 230/s |
| 峰值写 QPS | 约 2K/s |
| 读写比 | 约 20:1 |
| 峰值读 QPS | 热点帖子可到 50K/s+ |
架构
flowchart TD
Client["客户端"] --> API["评论服务"]
API --> Guard["幂等与限流"]
Guard --> Redis[("Redis 热路径")]
API --> DB[("评论数据库")]
API --> Outbox[("Outbox 事件表")]
Outbox --> Worker["异步 Worker"]
Worker --> Redis
Worker --> Audit["审核服务"]
Worker --> Notify["通知服务"]
Worker --> Search["搜索索引"]
数据库保存事实,Redis 保存热路径。Outbox 让数据库提交后的事件可靠进入缓存更新、通知、审核和搜索索引链路。
数据模型
1 | |
一级评论满足 parent_id = 0,root_comment_id = comment_id。回复评论的 root_comment_id 指向一级评论,parent_id 指向直接回复对象。这样既能表达“回复某人”,又避免读取时递归展开无限树。
Redis Key
| 用途 | Key | 类型 |
|---|---|---|
| 帖子一级评论热段 | cmt:post:{postId}:top:new |
ZSet |
| 一级评论回复热段 | cmt:root:{rootCommentId}:reply:new |
ZSet |
| 评论体短缓存 | cmt:body:{commentId} |
String / Hash |
| 帖子评论计数 | cmt:cnt:post:{postId} |
String / Hash |
| 回复计数 | cmt:cnt:root:{rootCommentId} |
String / Hash |
| 用户限流 | rl:cmt:{userId} |
ZSet |
| 幂等 token | idem:cmt:{userId}:{token} |
String |
1 | |
Redis 只缓存前 500 或 1000 条热评论 ID。深页走数据库索引。缓存全量评论只会把数据库压力搬成 Redis 大 key。
写路径
- 鉴权和权限检查。
- Redis 用户维度限流。
Idempotency-Key防重复提交。- 数据库事务写入
comments,同时写入 Outbox。 - 事务提交后返回评论 ID。
- Worker 更新 Redis 热段、计数、通知、审核和搜索索引。
Redis 更新不放进数据库事务。数据库提交成功但 Redis 更新失败,可以靠 Outbox 重试;先写 Redis 再写数据库则可能产生不存在的脏评论。
读路径
首屏读取:
1 | |
深页读取用游标,不用 offset:
1 | |
评论列表边读边变,offset 容易重复和漏读。(created_at, comment_id) 是稳定游标,同一毫秒内也能打破并列。
为什么通常是二级评论
二级评论的动机和数据库是否能存树关系不大,它更像产品和工程之间的折中。
| 无限树的问题 | 二级评论的处理 |
|---|---|
| 移动端无限缩进不可读 | 一级观点 + 局部回复 |
| 递归读取和分页复杂 | 两个列表:帖子评论、一级评论回复 |
| 删除子树成本高 | 隐藏一级评论即可折叠一组回复 |
| 缓存 key 不可控 | post_id 和 root_comment_id 两类 key |
| 分片困难 | 一级评论天然成为回复分片锚点 |
| 审核范围不清 | 按一级评论聚合治理 |
parent_id 仍然保留直接回复语义,读路径只按 root_comment_id 拉回复列表。用户看到“回复某人”,系统处理的是“某个一级评论下的一条回复”。
难点
| 难点 | 处理 |
|---|---|
| 热帖首屏过热 | Redis 热段缓存,本地 1-3 秒短缓存,深页回源 |
| 新评论刷屏 | 合并刷新,不强制每条立即推到在线客户端 |
| 删除审核 | 状态机 + Outbox 缓存失效 + tombstone 惰性清理 |
| 计数不一致 | 展示计数允许秒级延迟,后台校准 |
| Redis Cluster | 单 key Lua,跨 key 一致性交给 Outbox 重试 |
模式提炼:语义保留,结构压平
评论系统逻辑上有树,工程上按两个有序列表处理。root_comment_id 聚合讨论,parent_id 保留直接回复语义,Redis ZSet 只维护热段索引。
用例九:消息队列
问题
服务之间需要异步通信、削峰填谷、事件驱动和后台任务处理。Redis 能提供三种队列形态:List、Pub/Sub、Stream。
三种方案
| 方案 | 命令 | 能力 | 适合场景 |
|---|---|---|---|
| List | LPUSH + BRPOP |
简单阻塞队列 | 轻量任务 |
| Pub/Sub | PUBLISH + SUBSCRIBE |
实时广播,无积压 | 在线通知、广播 |
| Stream | XADD + XREADGROUP |
持久化、消费者组、ACK | 可靠事件流 |
List 示例:
1 | |
Stream 示例:
1 | |
取舍
Pub/Sub 不持久化,消费者离线就丢消息。List 简单,但缺少消费者组和 ACK。Stream 更接近专业 MQ,但堆积仍受 Redis 内存约束,不适合高吞吐、长周期、大规模可靠消息场景。重要业务事件优先 Kafka、RocketMQ 或数据库 Outbox。
用例十:地理位置服务
问题
附近的人、附近门店、3 公里内网点都需要按经纬度做距离和范围查询。
设计
Redis GEO 底层把经纬度编码到 ZSet score 中。
1 | |
取舍
Redis GEO 适合圆形范围和距离排序,不适合复杂多边形围栏、路径规划、空间 JOIN。复杂地理查询应该使用 PostGIS 或搜索引擎的地理索引。
用例十一:UV 统计
问题
UV 统计需要按页面、天、活动统计独立访客数。精确 Set 会随用户规模线性增长。
设计
HyperLogLog 用固定小内存估算基数,误差可控。
1 | |
取舍
UV、独立 IP、活动覆盖人数通常可以接受小误差。抽奖去重、付费权益、风控名单不能用 HyperLogLog,因为它不能告诉某个用户是否存在,也不能删除单个成员。
用例十二:Bloom Filter 防穿透
问题
大量请求查询不存在的商品、用户或文章 ID,会绕过缓存直击数据库。空值缓存能缓解,但攻击面很大时仍然浪费存储。
设计
Bloom Filter 判断“某个元素是否可能存在”。返回不存在时一定不存在;返回存在时只是可能存在。
1 | |
读路径:
1 | |
取舍
Bloom Filter 有误判,不能删除普通元素。商品上架、下架、ID 迁移等场景要考虑重建过滤器,或使用支持删除的 Cuckoo Filter。
模式提炼:概率换空间
HyperLogLog 和 Bloom Filter 都是在可接受误差内换取数量级的空间节省。业务问题不要求精确成员集合时,可以考虑概率结构。
用例十三:分布式 ID
问题
多实例服务需要生成唯一 ID。数据库自增 ID 简单,但高并发下会集中到单点,也不利于多机房扩展。
设计
Redis INCR 能生成全局递增序号:
1 | |
按业务和日期分段可以降低单 key 压力:
1 | |
应用层可组合成:
1 | |
取舍
Redis ID 简单,但强依赖 Redis 可用性,跨机房和极高吞吐场景不如 Snowflake、号段服务或数据库序列。ID 一旦写入数据库,就不能因为 Redis 回滚而重复。
用例十四:签到、在线状态与二值状态
问题
签到、在线、功能开关、每日活跃等状态只有 0/1 两种取值。用 Set 保存用户 ID 可以做,但空间成本较高。
设计
Bitmap 用一个 bit 表示一个用户或某一天。
1 | |
全站日活可以按天建 Bitmap,offset 使用用户 ID:
1 | |
取舍
Bitmap 的前提是 offset 可控。用户 ID 如果极度稀疏,用最大用户 ID 作为 offset 会浪费空间。连续内部 ID、映射表或按用户 ID 分片都可以缓解。
Redis 选型边界
Redis 的边界比用例本身更重要。
| 需求 | Redis 合适的条件 | 替代方案 |
|---|---|---|
| 缓存 | 数据可重建,读多写少 | 本地缓存、CDN |
| 分布式锁 | 临界区短,重复执行代价可控 | 数据库事务、etcd、ZooKeeper |
| 消息队列 | 轻量任务、短期积压 | Kafka、RocketMQ、RabbitMQ |
| 评论系统 | 热帖首屏、计数、限流、幂等 | 数据库 + Outbox + 搜索引擎 |
| 排行榜 | 单维实时排序 | 搜索引擎、OLAP |
| 社交关系 | 普通用户集合运算 | 图数据库、推荐系统 |
| GEO | 圆形范围、距离排序 | PostGIS、Elasticsearch |
| UV 统计 | 可接受误差 | 精确 Set、离线数仓 |
| 资金账务 | 不适合单独使用 Redis | 关系型数据库、账务系统 |
生产设计原则
Redis 不是事实源
评论正文、订单、支付、库存扣减、审核状态、用户资产都应该有持久事实源。Redis 可以缓存、计数、排序、限流,但必须能从事实源或事件日志重建。
大 key 比慢查询更隐蔽
HGETALL、SMEMBERS、大范围 ZRANGE 都可能阻塞 Redis。生产环境要限制单 key 成员数和 value 大小,扫描 hot key、big key,并对大集合做分片。
TTL 是数据模型的一部分
缓存、验证码、Session、限流窗口、幂等 token 都应有 TTL。没有 TTL 的缓存类 key 最终会变成内存泄漏。
Pipeline 减少往返,Lua 收紧原子边界
Pipeline 只减少网络往返,不提供事务原子性。Lua 可以保证单节点内多命令原子执行,但 Redis Cluster 下跨 slot 脚本不可作为默认方案。
一致性靠事件修复
数据库和 Redis 双写很难做到强一致。更常见的做法是数据库事务内写 Outbox,异步 worker 更新 Redis。读路径发现缓存缺失或脏状态时,可以回源修复。
Key 命名
Key 命名应表达业务域、实体、标识和子资源:
1 | |
命名规则:
- 使用冒号分隔层级
- 避免无意义缩写
- 把高基数字段放到后面,便于按前缀观察
- Cluster 下需要同 slot 的 key 使用 hash tag,但不要为了原子性把热点全集中到一个 slot
面试速查表
| 需求关键词 | 模式 | Redis 方案 | 必讲边界 |
|---|---|---|---|
| 缓存、热点、加速 | KV + TTL | SET key value EX ttl |
穿透、击穿、雪崩 |
| Session、验证码 | 短生命周期状态 | String + TTL | Redis 故障要能重新登录或重发 |
| 幂等、互斥、只能一个 | 原子占位 | SET NX EX |
value 校验释放,不能替代事务 |
| 排行榜、Top N | 分数数轴 | ZSet | 大榜单、同分、作弊回滚 |
| 延迟、定时、超时关闭 | Score 即时间 | ZSet + Lua | 消费幂等,堆积受内存限制 |
| 限流、频率控制 | 滑动窗口 | ZSet + Lua | Cluster 单 key 原子边界 |
| 关注、共同好友 | 双向关系 | Set + 交并差 | 大 V 大 key |
| 评论列表 | 结构压平 | DB + Redis ZSet 热段 | 二级评论、游标分页、审核 |
| 消息、异步、事件 | 消息流 | Stream | 高可靠高吞吐用专业 MQ |
| 附近的人 | 地理索引 | GEO | 复杂空间查询用 PostGIS |
| UV、独立访客 | 概率换空间 | HyperLogLog | 不能做精确成员判断 |
| 缓存穿透 | 概率预判 | Bloom Filter | 误判和重建 |
| 签到、在线 | 位图压缩 | Bitmap | offset 稀疏问题 |
| 全局序号 | 原子递增 | INCR / INCRBY |
高可用和跨机房 |
组合题怎么答
复杂系统往往由多个 Redis 模式组合而成:
| 系统 | Redis 模式组合 | 事实源 |
|---|---|---|
| 秒杀 | 原子占位 + Lua 扣减 + Stream/队列削峰 + 幂等 token | 订单库、库存库 |
| Feed 流 | ZSet 时间线 + 热用户缓存 + 计数器 + 去重 Set | 帖子库、关注关系库 |
| 评论系统 | ZSet 热段 + 短 TTL 评论体 + 限流 + 幂等 + Outbox 修复 | 评论库 |
| 点赞系统 | Set 去重 + 计数器 + 异步落库 | 点赞事实表 |
| API 网关 | 滑动窗口 + 令牌桶 + 黑名单 TTL | 配置中心、风控系统 |
Redis 在这些系统中的角色很一致:负责快、热、短、可重建;数据库和日志系统负责慢、全、准、可追溯。面试答案把这条边界讲清楚,Redis 就不会被讲成数据库替代品。



