cswork / 系统设计

第 29 章 / 共 57 章 · 约 4 分钟

关键技术:Redis

本页目录
  1. 1. 核心模型
  2. 2. 什么时候用
  3. 3. 什么时候不适合
  4. 4. 关键限制和数量级
  5. 5. 面试中常被追问的点
  6. 6. 和其他技术的取舍
  7. 7. 本站案例
  8. 章节测验

Redis 是一个把数据全部放在内存里的数据结构服务器。它不只是键值缓存:字符串、哈希、列表、集合、有序集合、流、地理位置都是内置类型,每条命令在服务端原子执行。系统设计里,缓存、计数器、排行榜、限流、分布式锁、在线状态,几乎都会想到它。

1. 核心模型

  • 单线程执行命令:命令在一个主线程里依次执行(网络 I/O 可以多线程),所以单条命令天然原子,INCR 不会有并发丢更新。需要“读-判断-写”这种多步原子操作时,用 Lua 脚本或 MULTI/EXEC。
  • 数据结构即接口:选对结构,很多功能一条命令就能完成。
结构典型用途代表命令
String缓存值、计数器、锁GET/SET、INCR、SET key val NX PX 30000
Hash对象的多个字段HSET、HINCRBY
Sorted Set排行榜、按时间排序的时间线、延迟队列ZADD、ZREVRANGE、ZRANGEBYSCORE
Set / HyperLogLog去重、基数估算SADD、PFADD(约 12 KB,标准误差约 0.81%)
Stream带消费者组的持久消息流XADD、XREADGROUP
Geo附近的人 / 车GEOADD、GEOSEARCH
  • 持久化:RDB 定期快照,AOF 追加写命令日志(常用每秒 fsync,最多丢约 1 秒数据)。即便开了持久化,Redis 一般也不当作唯一的数据来源。

2. 什么时候用

  • 缓存:cache-aside 读路径,详见基本构件里的缓存一节。
  • 计数和限流:INCR 加过期时间做固定窗口;滑动窗口、令牌桶用 Lua 脚本保证原子性,见限流器案例。
  • 排行榜和 Top-K:有序集合按分数排序,取前 100 名是 O(log N + 100)。
  • 短期状态:会话、在线状态、验证码、幂等键,靠 TTL 自动过期。
  • 轻量锁和预占:抢座的临时预占、司机派单时的锁,带 TTL 防止持有者崩溃后锁永不释放。

3. 什么时候不适合

  • 数据量远大于内存预算:所有数据都要放内存,几 TB 的冷数据放 Redis 成本很高。
  • 需要复杂查询或事务:没有 JOIN、没有二级索引(要自己维护),也没有跨 key 的回滚事务。
  • 不能丢的业务数据:主从复制是异步的,主节点在复制前宕机,已经确认的写入可能丢失。
  • 严格正确的分布式锁:单节点锁在故障切换时可能被两个客户端同时持有。正确性要求高时,要么配合 fencing token,要么用 ZooKeeper / etcd。

4. 关键限制和数量级

维度要记住的事
吞吐单实例每秒约 10 万量级的简单操作,延迟在亚毫秒级
容量单节点通常几 GB 到几十 GB;超过就要分片
集群Redis Cluster 有 16384 个哈希槽,按 key 分到各个分片;多 key 命令要求这些 key 在同一个槽,可以用哈希标签 {user42} 强制放在一起
复制异步复制;Sentinel 或 Cluster 负责故障切换,切换时可能丢掉最近的写入
淘汰内存满了按策略淘汰:allkeys-lru、allkeys-lfu、volatile-ttl 等;默认 noeviction 直接拒绝写入
慢命令KEYS *、对超大集合的 SMEMBERS、删除超大 key 都会阻塞主线程;用 SCAN、UNLINK 代替

5. 面试中常被追问的点

  • 缓存和数据库怎么保持一致? 先写库再删缓存,配合 TTL 兜底;残留的竞态窗口可以用延迟双删或者 CDC 驱动的失效来收窄。
  • 热点 key 怎么办? 单个 key 的流量超过一个分片的能力时,在应用进程里加一层本地缓存,或者把 key 复制成 key#1..key#N 分散到多个分片上读。
  • 缓存击穿和雪崩? 热 key 过期时用互斥锁或 single-flight 合并回源请求;TTL 加随机抖动,避免大量 key 同时过期。
  • 分布式锁靠谱吗? SET key token NX PX ttl 加锁,释放时用 Lua 先比较 token 再删除,防止误删别人的锁。持锁进程可能因为 GC 暂停超过 TTL,所以写共享资源时要带单调递增的 fencing token,由存储端拒绝旧 token。
  • Pub/Sub 能当消息队列吗? 不能可靠地用:订阅者不在线时消息直接丢。需要持久化和消费确认就用 Stream 或 Kafka。

6. 和其他技术的取舍

选项适合代价
Redis丰富数据结构、原子操作、低延迟内存成本;异步复制可能丢写
Memcached纯缓存,多线程,简单只有字符串,没有持久化和复制
进程内缓存最热的数据,零网络开销各实例之间不一致,容量小
DynamoDB / Cassandra大数据量、要持久的键值访问延迟是毫秒级,不是亚毫秒

7. 本站案例

章节测验

5 道选择题
  1. 限流器需要“读当前计数,没超限就加一”。在 Redis 里怎样做才不会有并发问题?
  2. 想做一个实时排行榜,按分数取前 100 名,并能查任意用户的名次,用哪种 Redis 结构?
  3. 为什么 Redis 分布式锁释放时,要先比较锁里的 token 再删除?
  4. 哪种场景最不适合把 Redis 当作唯一的存储?
  5. 在 Redis Cluster 上,一个 Lua 脚本要同时操作 user:42:cart 和 user:42:orders 两个 key,怎样保证能执行?

章节讨论

正在加载讨论…