2. Redis 核心数据结构回顾:String、Hash、List、Set、Sorted Set 在缓存场景中的典型用法
在构建搜索缓存闭环时,Redis 的五种核心数据结构是承载缓存数据的基石。每种结构都有其独特的适用场景,选错结构会导致性能下降或代码复杂度飙升。本节从缓存实战角度,逐一回顾它们的特性与典型用法。
2.1 数据结构选型速查表
| 数据结构 | 底层编码 | 缓存场景核心能力 | 典型 TTL 策略 |
|---|---|---|---|
| String | int / embstr / raw | 单值缓存、计数器、分布式锁 | 固定过期或滑动过期 |
| Hash | ziplist / hashtable | 对象字段级缓存、部分更新 | 整体过期或逐 field 管理 |
| List | quicklist | 消息队列、时间线、分页缓存 | 通常不设 TTL,用 LTRIM 控制长度 |
| Set | intset / hashtable | 标签系统、去重、集合运算 | 整体过期或定期清理 |
| Sorted Set | ziplist / skiplist | 排行榜、范围查询、滑动窗口限流 | 按 score 时间戳实现自动淘汰 |
2.2 String —— 最基础的缓存单元
String 是 Redis 最直观的结构,value 最大 512MB。在缓存场景中,它承担了 键值对缓存 和 计数器 两大职责。
实战要点: 使用
SET key value EX NX 实现分布式锁;使用 INCR/DECR 做访问计数;使用 GETSET 实现原子性更新。
// 缓存搜索结果摘要(JSON 序列化)
SET search:hotel:1001 '{"id":1001,"name":"希尔顿","price":899}' EX 300
// 原子计数器:记录搜索频次
INCR search:count:2025-03-20
// 分布式锁:防止缓存击穿
SET lock:rebuild:hotel NX EX 10
2.3 Hash —— 对象级缓存的利器
Hash 允许对对象的多个字段进行独立读写,适合缓存 结构化数据。相比 String 序列化整个对象,Hash 在 部分字段更新 时节省大量网络带宽。
| 操作 | 命令示例 | 缓存场景 |
|---|---|---|
| 写入对象 | HSET hotel:1001 name "希尔顿" price 899 |
缓存酒店基本信息 |
| 读取单字段 | HGET hotel:1001 price |
仅获取价格,避免传输整个对象 |
| 批量读取 | HMGET hotel:1001 name price |
获取部分字段组合 |
| 字段自增 | HINCRBY hotel:1001 views 1 |
对象级计数器 |
选型建议: 当缓存对象字段超过 10 个且频繁更新其中 1-2 个字段时,Hash 比 String 节省约 60% 的序列化开销。
2.4 List —— 有序队列与分页缓存
List 基于双向链表实现,支持 左进右出 和 范围查询。在搜索缓存中,常用于存储 搜索结果 ID 列表 或 消息队列。
// 缓存搜索结果 ID 列表(分页友好)
LPUSH search:result:query "1001" "1002" "1003"
LTRIM search:result:query 0 99 // 只保留前 100 条
// 分页读取
LRANGE search:result:query 0 9 // 第 1 页
LRANGE search:result:query 10 19 // 第 2 页
避坑提示: List 的
LINDEX 和 LSET 操作时间复杂度为 O(N),大数据量下避免频繁随机访问。分页场景优先使用 LRANGE。
2.5 Set —— 标签与去重利器
Set 提供 自动去重 和 集合运算(交集、并集、差集)。在搜索缓存中,适合存储 标签索引 和 用户行为去重。
// 为酒店打标签
SADD tag:泳池 1001 1002 1005
SADD tag:健身房 1001 1003 1004
// 查询同时有泳池和健身房的酒店
SINTER tag:泳池 tag:健身房
// 结果:1001
// 用户已浏览去重
SADD user:123:viewed 1001 1002 1003
SCARD user:123:viewed // 统计已浏览数量
2.6 Sorted Set —— 排序与范围查询之王
Sorted Set 每个元素关联一个 score,按 score 排序。在搜索缓存中,它是 排行榜、范围查询 和 滑动窗口限流 的核心结构。
| 场景 | 命令示例 | score 含义 |
|---|---|---|
| 搜索热词排行 | ZINCRBY hot:search:day 1 "Redis" |
搜索次数 |
| 按价格范围查询 | ZRANGEBYSCORE hotel:price 500 1000 |
酒店价格 |
| 滑动窗口限流 | ZREMRANGEBYSCORE user:req:123 0 (now-60s |
请求时间戳 |
| 分页获取排名 | ZREVRANGE hot:search:day 0 9 WITHSCORES |
按次数降序 |
// 滑动窗口限流:1 分钟内最多 10 次请求
ZREMRANGEBYSCORE user:req:123 0 (1710800000
ZCARD user:req:123
// 如果 < 10,则允许请求并添加当前时间戳
ZADD user:req:123 1710800123 "req:1710800123"
2.7 数据结构选择决策流程
2.8 缓存场景中的内存优化建议
- String 压缩: 对 JSON 字符串使用
gzip压缩后再存入,可减少 60-80% 内存占用。 - Hash 编码控制: 当 field 数量小于 512 且每个 value 小于 64 字节时,Redis 自动使用
ziplist编码,内存效率极高。 - Set 整数优化: 当所有元素为整数且数量小于 512 时,使用
intset编码,内存仅为 hashtable 的 1/5。 - Sorted Set 压缩: 合理设置
zset-max-ziplist-entries和zset-max-ziplist-value,避免 skiplist 的额外指针开销。 - 过期策略: 为每个缓存键设置合理的 TTL,避免冷数据长期占用内存。
核心原则: 数据结构的选择应基于 访问模式 而非数据本身。同一个对象,如果频繁读取全部字段,用 String;如果频繁更新部分字段,用 Hash。没有银弹,只有匹配。