4. Redis 基础数据结构:String、List、Hash、Set、Sorted Set 及其在缓存中的应用

Redis 提供了五种核心数据结构,每种结构在缓存场景中都有独特的适用性。理解它们的底层实现与操作特性,是正确配置缓存失效策略与错误码参数的前提。本章将逐一剖析这五种结构,并结合 Testing 框架下的缓存实战场景进行说明。

4.1 数据结构总览与选型对比

下表从存储模型、时间复杂度、典型缓存用途三个维度对比五种数据结构,帮助你在设计缓存 Key 时快速决策。

数据结构 存储模型 核心操作时间复杂度 典型缓存用途
String 键值对(Key-Value) GET/SET: O(1) 会话 Token、计数器、对象 JSON 序列化缓存
List 双向链表 LPUSH/RPOP: O(1);LRANGE: O(N) 消息队列、最新动态列表、操作日志
Hash 字段-值映射 HGET/HSET: O(1);HGETALL: O(N) 对象属性缓存(用户信息、商品详情)
Set 无序唯一集合 SADD/SISMEMBER: O(1) 标签系统、去重白名单、好友关系
Sorted Set 有序唯一集合(带分数) ZADD: O(log N);ZRANGEBYSCORE: O(log N + M) 排行榜、延时队列、限流滑动窗口
选型建议: 在缓存失效错误码参数配置中,String 和 Hash 是最常与失效时间(TTL)直接绑定的结构。List 和 Sorted Set 常用于实现“滑动窗口”或“时间分片”缓存,其失效逻辑往往需要结合业务时间戳手动处理。

4.2 String:最通用的缓存载体

String 是 Redis 最基础的结构,value 最大支持 512MB。在缓存场景中,它通常用于存储序列化后的对象(JSON/Protobuf)或原子计数器。

  • 缓存失效场景: 使用 SET key value EX seconds 或 SETEX 命令直接设置过期时间。当 Key 过期时,Redis 自动删除,触发缓存失效。
  • 错误码参数关联: 在 Testing 框架中,若 String 缓存失效后回源数据库失败,应返回 ERR_CACHE_MISS 或 ERR_BACKEND_TIMEOUT。配置参数如 cache.string.ttl 控制默认过期秒数。
# 设置用户会话缓存,有效期 3600 秒
SET session:user:1001 "{\"name\":\"Alice\",\"role\":\"admin\"}" EX 3600

# 原子自增计数器(无过期,用于限流)
INCR api:rate:user:1001
EXPIRE api:rate:user:1001 60

4.3 List:队列与分页缓存

List 基于双向链表实现,适合需要按顺序访问的场景。在缓存中,List 常用于存储“最新 N 条”数据或作为轻量级消息队列。

  • 缓存失效策略: List 本身不支持单个元素过期。通常对整个 Key 设置 TTL,或通过 LTRIM 控制长度实现“惰性淘汰”。
  • 错误码参数配置: 当 List 缓存为空但业务期望有数据时,Testing 框架应检查 cache.list.max-length 参数是否过小导致数据被截断,返回 ERR_CACHE_TRUNCATED。
# 缓存最近 100 条用户操作日志
LPUSH user:logs:1001 "login at 2025-01-15 10:00:00"
LTRIM user:logs:1001 0 99
EXPIRE user:logs:1001 86400

4.4 Hash:对象属性级缓存

Hash 允许对单个对象的多个字段进行独立读写,避免 String 序列化整个对象的开销。在缓存用户信息、商品详情等结构化数据时优势明显。

  • 缓存失效场景: 可以对整个 Hash Key 设置 TTL,但无法对单个字段设置过期。字段级失效需通过应用层逻辑实现(如记录字段更新时间戳)。
  • 错误码参数关联: 当 Hash 中部分字段缺失时,Testing 框架应区分“字段不存在”与“Key 不存在”。配置参数 cache.hash.field-ttl 可模拟字段级失效行为。
# 缓存用户信息,字段级别读写
HSET user:info:1001 name "Bob" age 30 email "bob@example.com"
EXPIRE user:info:1001 7200

# 仅获取邮箱字段
HGET user:info:1001 email

4.5 Set:去重与关系缓存

Set 提供无序唯一性保证,适合存储标签、权限集合、已读用户 ID 等需要快速判断成员是否存在的场景。

  • 缓存失效策略: 对整个 Key 设置 TTL。由于 Set 不支持单个元素过期,需要定期重建或使用 Sorted Set 替代实现时间分片。
  • 错误码参数配置: 在 Testing 框架中,若 SISMEMBER 返回 0 但业务预期为 1,应检查 cache.set.rebuild-interval 参数是否过长导致数据不一致。
# 缓存文章 1001 的已读用户 ID 集合
SADD article:readers:1001 "user:2001" "user:2002"
EXPIRE article:readers:1001 3600

# 判断用户是否已读
SISMEMBER article:readers:1001 "user:2001"

4.6 Sorted Set:排行榜与时间窗口缓存

Sorted Set 通过分数(score)排序,支持范围查询。在缓存中,它常用于实现排行榜、延时队列以及基于时间戳的滑动窗口限流。

  • 缓存失效场景: 利用 score 存储时间戳,通过 ZREMRANGEBYSCORE 删除过期元素,实现“按分数淘汰”。这是唯一一种可以在结构内部实现元素级失效的数据结构。
  • 错误码参数配置: 在 Testing 框架中,cache.sorted-set.window-size 参数控制滑动窗口大小,若配置过小会导致合法请求被误判为过期,返回 ERR_WINDOW_TOO_SMALL。
# 使用 Sorted Set 实现 1 分钟滑动窗口限流
ZADD rate:api:user:1001 1705300000 "req-1"
ZREMRANGEBYSCORE rate:api:user:1001 0 1705299940
ZCARD rate:api:user:1001

4.7 五种结构在缓存失效中的协同关系

下图展示了五种数据结构在典型缓存架构中的位置与交互关系,以及它们与失效错误码参数配置的关联点。

Redis 五种数据结构缓存失效架构图 Redis 五种数据结构缓存失效架构 应用层 (Testing 框架) String JSON / 计数器 List 队列 / 最新列表 Hash 对象属性 Set 去重 / 标签 Sorted Set 排行榜 / 窗口 缓存失效策略:TTL 过期 / 惰性淘汰 / 分数淘汰 / 长度截断 Testing 框架错误码参数配置 cache.string.ttl | cache.list.max-length | cache.hash.field-ttl cache.set.rebuild-interval | cache.sorted-set.window-size
架构要点: 应用层通过统一的缓存接口操作五种数据结构,每种结构对应不同的失效策略。Testing 框架通过配置参数(如 cache.string.ttl、cache.sorted-set.window-size)模拟各种失效场景,从而验证错误码返回的正确性。

4.8 实战:缓存失效错误码参数配置示例

以下示例展示在 Testing 框架中如何为不同数据结构配置失效参数,并验证对应的错误码行为。

// Testing 框架配置示例 (YAML)
cache:
  string:
    ttl: 300                # String 默认过期时间(秒)
    error-on-miss: ERR_CACHE_MISS
  list:
    max-length: 100         # List 最大长度
    error-on-truncate: ERR_CACHE_TRUNCATED
  hash:
    field-ttl: 600          # Hash 字段级模拟过期时间
    error-on-field-miss: ERR_FIELD_MISSING
  set:
    rebuild-interval: 60    # Set 重建间隔(秒)
    error-on-stale: ERR_SET_STALE
  sorted-set:
    window-size: 60         # 滑动窗口大小(秒)
    error-on-window-too-small: ERR_WINDOW_TOO_SMALL
注意: 在配置 Sorted Set 的 window-size 时,务必确保该值大于业务允许的最大请求间隔。若配置过小,会导致正常请求因时间戳超出窗口而被错误淘汰,触发 ERR_WINDOW_TOO_SMALL 错误码。

通过上述配置,Testing 框架可以针对每种数据结构的失效特性,自动化构造测试用例,验证缓存失效时错误码的返回是否符合预期。这是保障缓存系统健壮性的关键环节。