第 4 章:键管理与过期策略

jerry北京市2026年4月30日Go 4 次阅读 约 13 分钟
第 4 章:键管理与过期策略

掌握键的生命周期管理、过期删除策略、内存淘汰机制和键空间通知。


4.1 键的基本操作

# 查看键
KEYS *                         # 查看所有键(生产禁用!会阻塞)
KEYS user:*                    # 模式匹配
SCAN 0 MATCH user:* COUNT 100 # 渐进式扫描(生产推荐)
DBSIZE                         # 当前数据库的键数量

# 键信息
EXISTS key                     # 是否存在(1/0)
TYPE key                       # 数据类型
OBJECT ENCODING key            # 底层编码
OBJECT REFCOUNT key            # 引用计数
OBJECT IDLETIME key            # 空闲时间(秒)
OBJECT FREQ key                # 访问频率(LFU 模式下)
DEBUG OBJECT key               # 详细信息(调试用)

# 键操作
RENAME key newkey              # 重命名(newkey 存在则覆盖)
RENAMENX key newkey            # 重命名(newkey 存在则失败)
COPY source dest               # 复制键(Redis 6.2+)
DUMP key                       # 序列化
RESTORE key 0 serialized       # 反序列化恢复

# 排序
SORT mylist                    # 对列表/集合排序
SORT mylist DESC LIMIT 0 10   # 降序,取前 10

SCAN 系列命令

生产环境中,KEYS 命令会阻塞 Redis,必须用 SCAN 替代:

# SCAN:遍历所有键
SCAN 0 MATCH user:* COUNT 100
# 返回:下一个游标 + 匹配的键列表
# 游标为 0 时表示遍历完成

# HSCAN:遍历 Hash 的字段
HSCAN myhash 0 MATCH field* COUNT 100

# SSCAN:遍历 Set 的成员
SSCAN myset 0 MATCH pattern* COUNT 100

# ZSCAN:遍历 Sorted Set 的成员
ZSCAN myzset 0 MATCH pattern* COUNT 100

SCAN 的特点:

  • 渐进式遍历,不会阻塞
  • 可能返回重复元素,客户端需要去重
  • COUNT 是建议值,不是精确值
  • 遍历过程中新增/删除的键可能被遗漏或重复

4.2 过期时间

# 设置过期时间
EXPIRE key 60                  # 60 秒后过期
PEXPIRE key 60000              # 60000 毫秒后过期
EXPIREAT key 1704067200        # 在指定 Unix 时间戳过期
PEXPIREAT key 1704067200000    # 毫秒级时间戳

# 查看剩余时间
TTL key                        # 剩余秒数(-1 永不过期,-2 不存在)
PTTL key                       # 剩余毫秒数

# 移除过期时间
PERSIST key                    # 变为永不过期

# 设置值的同时设置过期
SET key value EX 60            # 秒
SET key value PX 60000         # 毫秒
SETEX key 60 value             # 等价于 SET + EXPIRE

注意事项:

  • 对已有过期时间的 key 执行 SET 会清除过期时间
  • RENAME 不会影响过期时间
  • EXPIRE 的时间精度是秒级,PEXPIRE 是毫秒级

4.3 过期删除策略

Redis 使用两种策略配合删除过期键:

惰性删除(Lazy Expiration)

  • 访问 key 时检查是否过期,过期则删除
  • 优点:对 CPU 友好,只在访问时检查
  • 缺点:大量过期 key 不被访问时会占用内存

定期删除(Active Expiration)

  • Redis 每秒执行 10 次(由 hz 配置控制):
    1. 随机抽取 20 个设置了过期时间的 key
    2. 删除其中已过期的 key
    3. 如果过期比例 > 25%,重复步骤 1
    4. 每次执行不超过 25ms,避免阻塞
┌─────────────────────────────────────┐
│         过期删除策略                  │
├─────────────────────────────────────┤
│                                     │
│  惰性删除:访问时检查                 │
│  ┌─────┐    ┌──────┐    ┌──────┐   │
│  │ GET │ →  │过期? │ →  │ 删除 │   │
│  └─────┘    └──────┘    └──────┘   │
│                                     │
│  定期删除:后台定时扫描               │
│  ┌──────────┐    ┌──────┐          │
│  │随机抽样20│ →  │删除过期│          │
│  │个key     │    │的key  │          │
│  └──────────┘    └──────┘          │
│  过期比例>25% → 继续抽样             │
│                                     │
└─────────────────────────────────────┘

4.4 内存淘汰策略

当内存达到 maxmemory 限制时,Redis 根据淘汰策略决定删除哪些 key:

策略 说明
noeviction 不淘汰,写入报错(默认)
allkeys-lru 所有 key 中淘汰最近最少使用的(推荐)
allkeys-lfu 所有 key 中淘汰最不经常使用的(Redis 4.0+)
allkeys-random 所有 key 中随机淘汰
volatile-lru 有过期时间的 key 中淘汰 LRU
volatile-lfu 有过期时间的 key 中淘汰 LFU
volatile-random 有过期时间的 key 中随机淘汰
volatile-ttl 有过期时间的 key 中淘汰 TTL 最小的
# 查看当前策略
CONFIG GET maxmemory-policy

# 设置策略
CONFIG SET maxmemory-policy allkeys-lru

LRU vs LFU

  • LRU(Least Recently Used):淘汰最久没被访问的

    • 适合:访问模式有明显的时间局部性
    • Redis 使用近似 LRU(采样淘汰),不是精确 LRU
  • LFU(Least Frequently Used):淘汰访问频率最低的

    • 适合:有热点数据的场景
    • Redis 4.0+ 支持,使用 Morris 计数器

选择建议:

  • 通用场景:allkeys-lru
  • 有明显热点数据:allkeys-lfu
  • 缓存 + 持久数据混合:volatile-lru

4.5 键空间通知

Redis 可以在键发生变化时发送通知:

# 开启键空间通知
CONFIG SET notify-keyspace-events KEA

# K:键空间通知(按 key 名)
# E:键事件通知(按事件类型)
# A:所有事件
# g:通用命令(DEL, EXPIRE, RENAME...)
# $:String 命令
# l:List 命令
# s:Set 命令
# h:Hash 命令
# z:Sorted Set 命令
# x:过期事件
# e:淘汰事件

订阅通知:

# 终端 1:订阅所有键的过期事件
SUBSCRIBE __keyevent@0__:expired

# 终端 2:设置一个会过期的键
SET mykey "hello" EX 5

# 5 秒后终端 1 收到通知:
# "mykey"

应用场景:

  • 订单超时自动取消
  • 缓存过期后触发数据刷新
  • 会话过期通知

4.6 大 Key 扫描

大 Key 会导致阻塞、内存不均、网络拥塞等问题:

# 使用 redis-cli 扫描大 key
redis-cli --bigkeys

# 使用 MEMORY USAGE 查看单个 key 的内存
MEMORY USAGE mykey

# 使用 SCAN + 应用层判断
# 伪代码:
# for key in SCAN:
#     size = MEMORY USAGE key
#     if size > threshold:
#         report(key, size)

大 Key 的定义(经验值):

  • String > 10KB
  • Hash/Set/Sorted Set/List > 5000 个元素

大 Key 的删除:

# 小 key 直接删除
DEL key

# 大 key 异步删除(Redis 4.0+,不阻塞)
UNLINK key

4.7 面试要点

  1. Redis 的过期删除策略?

    • 惰性删除 + 定期删除配合使用
  2. 内存淘汰策略有哪些?推荐哪个?

    • 8 种策略,通用场景推荐 allkeys-lru
  3. LRU 和 LFU 的区别?

    • LRU 淘汰最久没访问的,LFU 淘汰访问频率最低的
  4. 为什么不能用 KEYS 命令?

    • KEYS 会遍历所有键,O(N) 复杂度,阻塞 Redis
    • 用 SCAN 渐进式遍历替代
  5. 大 Key 有什么危害?如何处理?

    • 阻塞、内存不均、网络拥塞
    • 用 UNLINK 异步删除,拆分为小 key

练习

  1. 使用 SCAN 遍历所有以 user: 开头的键
  2. 设置不同的过期时间,观察 TTL 变化
  3. 配置 maxmemory 和淘汰策略,测试内存满时的行为
  4. 开启键空间通知,监听键的过期事件

← 上一章:高级数据类型 | 下一章:持久化:RDB 与 AOF →

评论

登录 后发表评论

暂无评论