缓存01:Redis

从数据结构、持久化策略、集群模式、哨兵机制到缓存设计模式,拆解 Redis 在分布式缓存场景下的核心机制和常见坑点。

字数 3743 阅读时长 ≈ 11 分钟 2026-7-13 2026-7-13
缓存01:Redis

Redis 不是”简单缓存”,而是”数据结构服务器”。它支持五种基础数据结构(String、List、Set、Hash、ZSet)和四种高级结构(Bitmap、HyperLogLog、Geo、Stream),能够承载计数器、排行榜、消息队列、分布式锁、位运算等复杂场景。

这篇文章围绕五个核心机制:数据结构、持久化、集群模式、哨兵机制、缓存设计模式,把 Redis 的设计逻辑和常见坑点讲清楚。

为什么选 Redis,而不是别的

先定位清楚:Redis 的核心场景是高性能缓存和复杂数据结构操作,支持内存级读写、多种数据结构和持久化机制,适合计数器、排行榜、分布式锁、会话存储、消息队列等场景。

与 Memcached、Caffeine(本地缓存)的差异:

维度RedisMemcachedCaffeine(本地缓存)
数据结构5 种基础 + 4 种高级只支持 String只支持 Key-Value
持久化RDB + AOF,支持数据恢复不支持,重启丢数据不支持,进程重启丢数据
集群主从、哨兵、Cluster 三种模式不原生支持集群不支持,单机
分布式支持分布式锁、分布式计数不支持不支持,只单机
吞吐量单机十万级 QPS单机十万级 QPS单机百万级 QPS(内存访问)
适用场景分布式缓存、计数器、排行榜、分布式锁、消息队列简单 KV 缓存、Session 共享本地热点缓存、高频读

简单判断:分布式场景、需要复杂数据结构、需要持久化 → Redis 合适。简单 KV 缓存、跨语言 Session → Memcached 合适。单机热点数据、高频读 → Caffeine 合适。

数据结构:五种基础 + 四种高级

Redis 的核心优势是丰富的数据结构,不同结构对应不同场景。

五种基础数据结构

数据结构说明适用场景
String最基础类型,支持数值操作(INCR/DECR)计数器、分布式锁、缓存对象(JSON 序列化)
List双向链表,支持 LPUSH/RPUSH/LPOP/RPOP消息队列、最新列表、时间线
Set无序集合,支持交集、并集、差集标签系统、共同好友、去重
Hash键值对集合,适合存储对象用户信息、商品属性、计数器(多字段)
ZSet(Sorted Set)有序集合,按分数排序排行榜、延时队列、按时间排序的列表

String:不只是字符串

String 是 Redis 最基础的数据结构,但它的能力不止存储字符串:

  • 数值操作INCR keyINCRBY key 10DECR key,适合计数器(如文章阅读数、点赞数)
  • 分布式锁SET key value NX PX 30000,NX 保证不存在才设置,PX 设置过期时间
  • 缓存对象:对象序列化为 JSON/String 存储,适合缓存用户信息、商品详情

常见坑:大 Value 问题。String 存储的对象过大(超过 10KB),读写性能下降,建议拆成 Hash 多字段存储。

List:消息队列的替代

List 是双向链表,支持 LPUSH/RPUSH(头尾插入)和 LPOP/RPOP(头尾弹出):

  • LPUSH + RPOP:生产者从左侧插入,消费者从右侧弹出,先进先出(FIFO)
  • RPUSH + LPOP:生产者从右侧插入,消费者从左侧弹出,先进先出(FIFO)
  • BRPOP key timeout:阻塞弹出,timeout 秒内没有元素则阻塞等待,类似长轮询

适用场景:简单消息队列、最新文章列表、用户时间线。

常见坑:List 不支持消费确认和重试,消息弹出后立即删除,消费失败无法恢复。适合轻量队列,不适合业务消息。业务消息用 Kafka/RocketMQ/RabbitMQ。

Set:去重和集合运算

Set 是无序集合,元素唯一,支持交集、并集、差集:

  • SADD key member:添加元素
  • SINTER key1 key2:交集,适合共同好友、共同标签
  • SUNION key1 key2:并集,适合合并标签、合并关注列表
  • SDIFF key1 key2:差集,适合差集计算

适用场景:标签系统、共同好友、去重、社交关系。

Hash:对象的天然存储

Hash 是键值对集合,适合存储对象的多个字段:

  • HSET key field value:设置字段值
  • HGET key field:获取字段值
  • HMGET key field1 field2:批量获取字段
  • HINCRBY key field increment:字段数值递增,适合多字段计数器

适用场景:用户信息(userId、name、age、email)、商品属性(productId、price、stock、title)、多字段计数器(文章的阅读数、点赞数、评论数)。

Hash 与 String 存储对象的对比:

维度String(JSON 序列化)Hash(多字段)
读写方式整体读写,修改需全量更新单字段读写,修改只更新字段
适用场景整体缓存、对象完整读取对象部分字段、频繁修改部分字段
内存占用整体占用,较大按字段占用,灵活

ZSet:排行榜的天然实现

ZSet 是有序集合,每个元素关联一个分数(score),按分数排序:

  • ZADD key score member:添加元素和分数
  • ZRANGE key start stop WITHSCORES:按分数升序获取
  • ZREVRANGE key start stop WITHSCORES:按分数降序获取,适合排行榜
  • ZINCRBY key increment member:分数递增,适合动态排名

适用场景:排行榜(文章热度、用户积分)、延时队列(score = 延迟时间戳)、按时间排序的列表(score = 时间戳)。

四种高级数据结构

高级结构说明适用场景
Bitmap位图,按位操作(SETBIT/GETBIT)用户签到、用户活跃度统计、布隆过滤器
HyperLogLog基数估算,误差 0.81%UV 统计、去重计数(不精确)
Geo地理坐标,支持距离计算、范围查询附近的人、附近的门店、地理范围查询
Stream消息流,支持消费组、消息确认消息队列(Redis 5.0+)、日志流

持久化:RDB 和 AOF

Redis 的持久化决定数据是否能在重启后恢复。两种持久化方式各有取舍。

RDB(Redis Database)

RDB 是快照持久化,定期将内存数据保存到磁盘的二进制文件(dump.rdb):

  • 触发方式:配置规则(如 900 秒内至少 1 次修改触发)或手动执行 BGSAVE
  • 写入方式: fork 子进程,子进程将内存数据写入临时文件,完成后替换旧 RDB 文件
  • 恢复速度:快,直接加载 RDB 文件到内存
  • 数据完整性:差,最后一次快照后的修改会丢失

RDB 配置:

save 900 1      # 900 秒内至少 1 次修改触发
save 300 10     # 300 秒内至少 10 次修改触发
save 60 10000   # 60 秒内至少 10000 次修改触发

适用场景:允许分钟级数据丢失、恢复速度优先、数据量大的场景。

AOF(Append Only File)

AOF 是日志持久化,每次写入操作追加到 AOF 文件:

  • 写入方式:每次写入操作追加到 AOF 文件(appendonly.aof)
  • 刷盘策略appendfsync always(每次写入刷盘)、appendfsync everysec(每秒刷盘)、appendfsync no(由操作系统决定)
  • 恢复速度:慢,需要重放所有写入操作
  • 数据完整性:好,最多丢失 1 秒数据(everysec 策略)

AOF 配置:

appendonly yes
appendfsync everysec    # 每秒刷盘,推荐

AOF 重写:AOF 文件会越来越大,Redis 定期重写 AOF,压缩冗余命令(如多次 INCR 合并为一条 SET)。触发规则:auto-aof-rewrite-min-size 64mb(文件超过 64MB 触发重写)。

适用场景:数据完整性优先、允许秒级丢失、恢复速度可接受的场景。

RDB + AOF 混合持久化

Redis 4.0+ 支持混合持久化:RDB 做基础快照,AOF 记录增量操作。恢复时先加载 RDB,再重放 AOF 增量。

配置:

aof-use-rdb-preamble yes

适用场景:兼顾恢复速度和数据完整性,推荐生产环境使用。

持久化对比

维度RDBAOFRDB + AOF 混合
恢复速度中等
数据完整性差(分钟级丢失)好(秒级丢失)好(秒级丢失)
文件大小小(压缩快照)大(日志追加)中等
性能影响低(定期快照)中(每次写入追加)
适用场景允许分钟级丢失、恢复快数据完整性优先兼顾两者,推荐生产

集群模式:主从、哨兵、Cluster

Redis 的集群模式决定高可用和水平扩展能力。三种模式各有取舍。

主从复制(Master-Slave)

主从复制是最基础的高可用方案:

  • Master:读写请求,数据写入 Master
  • Slave:只读请求,从 Master 同步数据

配置:Slave 执行 REPLICAOF master_ip master_port,建立复制关系。

主从复制的限制:

  • Master 故障需人工切换:Slave 只读,Master 故障后需手动提升 Slave 为 Master
  • 写入压力集中在 Master:Slave 只分担读压力,写入压力仍在 Master
  • 复制延迟:Slave 同步有延迟,Slave 读到的是旧数据

适用场景:读多写少、允许短暂读延迟、人工切换可接受的场景。

哨兵模式(Sentinel)

哨兵模式在主从基础上增加自动故障转移:

  • Sentinel:监控 Master 和 Slave,检测 Master 故障后自动切换
  • 选举机制:Sentinel 集群投票选举新 Master,从 Slave 中选择
  • 客户端感知:Sentinel 通知客户端新 Master 地址,客户端自动切换

配置:启动 Sentinel 进程,配置监控的 Master 地址和 Sentinel 集群。

哨兵模式的限制:

  • 写入压力仍在 Master:Slave 只读,哨兵解决故障转移,不解决写入压力
  • Slave 数量受限:Slave 过多时同步延迟增加
  • 选举延迟:Sentinel 检测故障和选举需要时间(秒级)

适用场景:读多写少、需要自动故障转移、写入压力不大的场景。

Cluster 模式

Cluster 模式是 Redis 的分布式方案,支持水平扩展:

  • 分片:数据按 Key 分片到多个 Master 节点,每个 Master 负责 16384 个槽位的一部分
  • 多 Master:多个 Master 同时写入,写入压力分散
  • 高可用:每个 Master 配备 Slave,Master 故障时 Slave 自动提升

分片规则:CRC16(key) % 16384,根据槽位路由到对应 Master。

Cluster 模式的限制:

  • 跨节点操作不支持:MSET、事务(MULTI/EXEC)跨节点不支持
  • Lua 脚本受限:Lua 脚本只能在单节点执行
  • Key 设计重要:相关 Key 需要路由到同一节点,使用 Hash Tag(如 {user:1001}:profile{user:1001}:score

适用场景:写入压力大、需要水平扩展、数据量大的场景。

集群模式对比

维度主从复制哨兵模式Cluster 模式
高可用需人工切换自动故障转移自动故障转移
写入扩展不支持不支持支持多 Master 写入
读扩展Slave 分担读压力Slave 分担读压力Slave 分担读压力
跨节点操作支持(单节点)支持(单节点)不支持(需 Hash Tag)
适用场景读多写少、人工切换可接受读多写少、自动故障转移写入压力大、水平扩展

缓存设计模式:三种常见模式

Redis 作为缓存时,有三种常见设计模式,各有取舍。

Cache-Aside Pattern(旁路缓存)

最常用的缓存模式:应用代码同时维护缓存和数据库:

  • 读流程:先读缓存,命中则返回;未命中则读数据库,写入缓存后返回
  • 写流程:先更新数据库,再删除缓存(不是更新缓存)

为什么要删除缓存而不是更新缓存:

操作说明
更新缓存每次写数据库都更新缓存,并发写时缓存数据不一致
删除缓存删除后下次读时重新加载,保证一致性

Cache-Aside 的常见坑:并发读写导致脏数据。线程 A 读数据库(旧数据),线程 B 更新数据库并删除缓存,线程 A 写入缓存(旧数据),缓存数据不一致。

解决方式:延迟双删,更新数据库后删除缓存,延迟一段时间(如 500ms)再次删除缓存。

Read-Through / Write-Through Pattern(穿透缓存)

缓存代理数据库访问:应用代码只访问缓存,缓存未命中时由缓存组件读数据库并写入缓存。

  • Read-Through:应用读缓存,缓存组件负责读数据库并写入缓存
  • Write-Through:应用写缓存,缓存组件负责同步写数据库

优点:应用代码不关心数据库,缓存组件统一维护一致性。缺点:缓存组件复杂,需要实现数据库访问逻辑。

适用场景:缓存组件成熟、应用代码简化、一致性统一维护的场景。

Write-Behind Pattern(异步写缓存)

写缓存时异步写数据库:应用写缓存后立即返回,缓存组件异步批量写数据库。

优点:写入性能高,应用不需要等待数据库写入。缺点:数据库写入延迟,可能丢数据(缓存组件故障)。

适用场景:写入性能优先、允许数据库写入延迟、数据丢失可接受的场景。

三种模式对比

模式读流程写流程一致性性能
Cache-Aside先读缓存,未命中读 DB 写缓存先写 DB,再删缓存中等(并发写可能不一致)
Read-Through读缓存,缓存组件读 DB 写缓存写缓存,缓存组件同步写 DB好(缓存组件统一维护)中等
Write-Behind读缓存,缓存组件读 DB 写缓存写缓存,缓存组件异步写 DB差(异步写入延迟)最高

生产环境常用 Cache-Aside,简单可控。Read-Through/Write-Through 需成熟缓存组件(如 Spring Cache + Redis),Write-Behind 适合高性能写入场景(如埋点数据)。

常见坑点

坑点说明解决方式
缓存穿透Key 不存在,请求穿透到数据库布隆过滤器拦截不存在 Key、空值缓存短 TTL
缓存击穿热点 Key 过期瞬间大量请求穿透到数据库永不过期、互斥锁(SETNX)、逻辑过期
缓存雪崩大量 Key 同时过期,数据库压力瞬间激增过期时间随机化、多级缓存、熔断限流
大 Key 问题单个 Key 的 Value 过大(>10KB)拆分 Key、使用 Hash 多字段存储
热点 Key 问题单个 Key 访问量过高,单节点压力过大本地缓存(Caffeine)+ Redis、Key 分片
缓存与数据库不一致并发读写、先删缓存再写 DB 导致不一致先写 DB 再删缓存、延迟双删、分布式锁

项目判断

引入 Redis 时,先回答三个问题:

  1. 场景:是简单缓存,还是需要复杂数据结构(排行榜、分布式锁、计数器)?
  2. 数据量:数据量是否超过单机内存(需要 Cluster)?
  3. 一致性要求:是否需要强一致性(缓存不适合,用数据库)?

如果是简单缓存、数据量不大、一致性要求不高 → 单机 Redis 或主从即可。如果需要复杂数据结构、高可用 → 哨兵模式。如果数据量大、写入压力大 → Cluster 模式。

线上排查 Redis 问题:

  • 慢查询SLOWLOG GET 10 查看慢命令,检查是否大 Key、复杂命令(如 KEYS *)
  • 内存使用INFO memory 查看内存占用,检查是否有大 Key、未设置过期时间
  • 阻塞命令:避免 KEYS *、HGETALL 大 Hash、ZRANGE 大 ZSet,用 SCAN 替代
  • 连接数INFO clients 查看连接数,检查是否有连接泄漏
  • 持久化:检查 RDB/AOF 配置,是否开启持久化,刷盘策略是否合理