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) 范围查询 | 排行榜、延迟队列、限流滑动窗口 | 低(双结构) |
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
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 错误 |
3.4 过期策略与数据结构的关系
下图展示了 Redis 过期策略在数据结构层面的执行流程,以及各数据结构在过期检查中的行为差异:
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 + 随机偏移),防止缓存雪崩。
0(永不过期),并通过消息队列异步更新缓存。同时,将 Redis 淘汰策略配置为 volatile-lru,确保只有设置了 TTL 的 Key 可被淘汰,保护热点数据不被误删。