3. Redis 基础回顾:核心数据结构与缓存过期策略

在 Kafka 堆积场景下,Redis 作为缓存层承担着抗住瞬时读流量、保护下游数据库的关键职责。理解 Redis 的核心数据结构与过期策略,是设计缓存击穿防护方案的前提。本节将系统梳理五种常用数据结构的特性、适用场景,以及 Redis 的缓存过期机制。

3.1 核心数据结构概览

Redis 提供了丰富的数据结构,每种结构在内存布局、操作复杂度与适用场景上各有侧重。下表对比了五种核心结构的核心差异:

数据结构 底层编码 读写复杂度 典型场景 内存效率
String SDS(动态字符串) O(1) 读写 缓存击穿热点 Key、计数器、分布式锁 高(直接存储)
Hash ziplist / hashtable O(1) 字段级操作 对象缓存(用户信息、商品详情) 中(字段压缩)
List quicklist O(1) 头尾操作 消息队列、时间轴、最新列表 中(分段压缩)
Set intset / hashtable O(1) 增删查 标签系统、去重、共同关注 中(整数压缩)
Sorted Set ziplist / skiplist + dict O(log N) 范围查询 排行榜、延迟队列、限流滑动窗口 低(双结构)
性能调优提示: 在缓存击穿场景中,String 是最常用的结构,因其 O(1) 读写与极低的内存开销。但若缓存对象字段较多(如用户信息含 10+ 字段),Hash 可减少序列化开销,且支持单独更新某个字段,避免全量重写。

3.2 各数据结构详解与选型

3.2.1 String — 缓存击穿的主战场

String 是 Redis 最基础的结构,最大存储 512 MB。在缓存击穿场景中,热点 Key 通常使用 String 存储序列化后的 JSON 数据。其核心命令包括 GET、SET、SETNX(分布式锁)、INCR(计数器)。

# 缓存击穿防护:使用 SETNX 实现互斥锁
SETNX lock:hotkey 1
EXPIRE lock:hotkey 5

# 缓存预热:设置过期时间
SET hotkey:data "{...}" EX 60

3.2.2 Hash — 对象级缓存的优选

Hash 适合存储结构化对象,支持对单个字段进行 HGET/HSET,避免全量序列化。在 Kafka 堆积导致数据更新频繁时,Hash 可只更新变更字段,减少网络传输与内存重写。

# 用户信息缓存
HSET user:1001 name "张三" age 30 city "北京"
HGET user:1001 name
# 输出: "张三"

3.2.3 List — 消息队列与时间轴

List 基于双向链表实现,支持 LPUSH/RPOP 实现队列,LRANGE 实现分页。在 Kafka 消费端,List 可作为本地缓冲队列,缓解瞬时写入压力。

# 生产者
LPUSH task:queue "task1" "task2"
# 消费者
RPOP task:queue

3.2.4 Set — 去重与关系运算

Set 支持集合运算(交集、并集、差集),适用于标签系统、共同好友等场景。在缓存击穿中,Set 可用于记录已过期的 Key 黑名单,辅助布隆过滤器。

# 添加已过期 Key 到黑名单
SADD expired:keys "hotkey:123"
# 检查是否在黑名单中
SISMEMBER expired:keys "hotkey:123"

3.2.5 Sorted Set — 排行榜与延迟队列

Sorted Set 通过 score 排序,支持 ZRANGEBYSCORE 范围查询。在 Kafka 堆积场景中,可用于实现延迟队列:将消息的到期时间戳作为 score,消费者轮询 ZRANGEBYSCORE 获取到期任务。

# 添加延迟任务,score 为到期时间戳
ZADD delay:queue 1700000000 "task1"
# 获取已到期的任务
ZRANGEBYSCORE delay:queue -inf 1700000000
选型建议: 在缓存击穿防护中,优先使用 String + SETNX 实现互斥锁;若缓存对象字段多且更新频繁,改用 Hash;若需要排序或范围查询,使用 Sorted Set。

3.3 缓存过期策略

Redis 的过期策略决定了 Key 何时被淘汰,直接影响缓存击穿的发生概率。Redis 采用 定期删除 + 惰性删除 的混合策略,并配合 内存淘汰机制 处理内存不足的情况。

3.3.1 过期策略对比

策略 触发时机 工作原理 优缺点
定期删除 每 100ms 随机抽查 随机抽取 20 个带过期时间的 Key,删除已过期的;若过期比例 > 25%,重复执行 优点:主动清理,避免内存堆积;缺点:无法保证所有过期 Key 立即删除
惰性删除 每次访问 Key 时 当客户端请求一个 Key 时,检查是否过期,若过期则删除并返回 nil 优点:CPU 友好;缺点:过期 Key 可能长时间占用内存
内存淘汰 内存达到 maxmemory 上限 根据淘汰策略(如 LRU、LFU、TTL)删除 Key 释放空间 优点:防止 OOM;缺点:可能误删未过期 Key,导致缓存击穿

3.3.2 内存淘汰策略详解

当 Redis 内存达到 maxmemory 限制时,会根据配置的淘汰策略删除 Key。以下是常用策略的对比:

策略 淘汰范围 适用场景 风险
allkeys-lru 所有 Key 缓存场景,淘汰最近最少使用的 Key 热点 Key 可能被误淘汰
volatile-lru 设置了 TTL 的 Key 缓存 + 持久化混合场景 未设置 TTL 的 Key 永不淘汰
allkeys-lfu 所有 Key 访问频率差异大的场景 新 Key 可能因访问次数少被淘汰
volatile-ttl 设置了 TTL 的 Key 优先淘汰即将过期的 Key TTL 设置不合理时效果差
noeviction 不淘汰 写少读多,内存充足 写操作直接返回 OOM 错误
缓存击穿风险: 在 Kafka 堆积导致大量数据写入 Redis 时,若内存不足触发 allkeys-lru 淘汰,可能将热点 Key 淘汰,导致后续请求直接穿透到数据库,引发缓存击穿。建议在缓存击穿防护方案中,将热点 Key 的过期时间设置为 永不淘汰(不设 TTL),或使用 volatile-lru 并确保热点 Key 设置了 TTL。

3.4 过期策略与数据结构的关系

下图展示了 Redis 过期策略在数据结构层面的执行流程,以及各数据结构在过期检查中的行为差异:

Redis 过期策略执行流程与数据结构关系 Redis 过期策略执行流程 客户端请求 Key 惰性删除:检查 Key 是否过期 过期? 删除 Key,返回 nil 返回数据 定期删除(每 100ms) 随机抽查 20 个 Key 删除过期 Key 数据结构在过期检查中的行为 • String / Hash / Set / Sorted Set:过期检查基于 Key 级别,与内部结构无关 • List:过期时整个 List 被删除,不支持元素级过期 • 所有结构在惰性删除和定期删除中行为一致,仅删除整个 Key

3.5 缓存过期策略在 Kafka 堆积场景下的调优要点

  • 热点 Key 不设 TTL 或设置极长 TTL:避免因过期导致缓存击穿,配合后台异步更新(Cache-Aside 模式)保持数据一致性。
  • 使用 volatile-lru 淘汰策略:确保未设置 TTL 的 Key(如热点数据)不会被淘汰,仅淘汰可过期的冷数据。
  • 合理设置 maxmemory:预留 20%-30% 的内存余量,避免因内存满触发淘汰导致热点 Key 被误删。
  • 监控 expired_keys 与 evicted_keys:通过 Redis INFO 命令监控过期与淘汰数量,若 evicted_keys 持续增长,说明内存不足,需扩容或优化数据结构。
  • 避免大量 Key 同时过期:在 Kafka 批量写入时,为 Key 设置随机 TTL(如基础 TTL + 随机偏移),防止缓存雪崩。
实践建议: 在 Kafka 堆积场景下,建议将热点缓存 Key 的 TTL 设置为 0(永不过期),并通过消息队列异步更新缓存。同时,将 Redis 淘汰策略配置为 volatile-lru,确保只有设置了 TTL 的 Key 可被淘汰,保护热点数据不被误删。