2. Redis 基础回顾:核心数据结构与持久化机制
在构建搜索缓存闭环之前,必须牢固掌握 Redis 的两大基石:五种核心数据结构 与 两种持久化机制。数据结构决定了缓存能“存什么、怎么查”,持久化决定了缓存“断电后能否恢复”。本章从实战角度梳理这些基础,为后续的搜索缓存设计打下坚实的地基。
2.1 五种核心数据结构
Redis 不是简单的键值存储,它的值类型决定了操作能力。下表总结了五种数据结构的核心特征与典型应用场景:
| 数据结构 | 内部编码 | 核心命令 | 时间复杂度 | 典型场景 |
|---|---|---|---|---|
| String | int / embstr / raw | GET, SET, INCR, DECR | O(1) | 计数器、分布式锁、Session 缓存 |
| Hash | ziplist / hashtable | HGET, HSET, HGETALL | O(1) 单字段 | 对象缓存(用户信息、商品详情) |
| List | quicklist | LPUSH, RPUSH, LPOP, LRANGE | O(1) 头尾 / O(N) 范围 | 消息队列、时间线、最新动态 |
| Set | intset / hashtable | SADD, SREM, SISMEMBER, SUNION | O(1) 单元素 / O(N) 集合运算 | 标签系统、去重、共同关注 |
| Sorted Set | ziplist / skiplist | ZADD, ZRANGE, ZRANK, ZREVRANK | O(log N) 增删查 | 排行榜、延迟队列、范围查询 |
2.1.1 String:最基础的缓存单元
String 是 Redis 最基础的值类型,最大支持 512MB。除了简单的键值缓存,INCR 命令常用于实现搜索请求计数器、限流令牌桶。在搜索缓存中,String 适合存储序列化后的单条搜索结果或聚合统计值。
# 缓存搜索结果摘要(JSON 序列化)
SET search:cache:query:redis "{\"total\": 120, \"docs\": [...]}" EX 300
# 搜索请求计数器
INCR search:stats:requests:2025-04-01
2.1.2 Hash:结构化对象缓存
Hash 允许对对象的单个字段进行读写,避免序列化整个对象。在搜索缓存中,Hash 非常适合存储文档的元数据字段,支持部分更新而不影响其他字段。
# 缓存文档元数据
HSET doc:1001 title "Redis 实战" author "张三" score 9.5
HSET doc:1002 title "缓存设计" author "李四" score 8.7
# 只获取标题字段
HGET doc:1001 title
2.1.3 List:消息队列与分页
List 基于双向链表实现,头尾操作时间复杂度 O(1)。在搜索系统中,List 可用于构建异步索引写入队列,或缓存搜索结果的分页列表。
# 索引写入队列
LPUSH index:queue "{\"doc_id\": 1001, \"action\": \"update\"}"
# 缓存搜索结果分页(每页 20 条)
RPUSH search:result:page:1 "doc:1001" "doc:1002" ...
2.1.4 Set:标签与过滤
Set 提供高效的成员判断与集合运算。在搜索缓存中,Set 用于存储文档的标签集合,通过 SINTER 实现多标签过滤,通过 SDIFF 实现排除逻辑。
# 为文档打标签
SADD tag:redis doc:1001 doc:1002 doc:1003
SADD tag:cache doc:1001 doc:1004
# 同时包含 redis 和 cache 标签的文档
SINTER tag:redis tag:cache
2.1.5 Sorted Set:排序与范围查询
Sorted Set 是搜索缓存的核心数据结构。每个元素关联一个 double 类型的分数,Redis 通过跳跃表实现高效的按分数范围查询与排名计算。在搜索系统中,分数可以映射为相关性评分、发布时间戳或价格。
# 存储搜索结果排序索引
ZADD search:index:keyword:redis 9.5 doc:1001
ZADD search:index:keyword:redis 8.7 doc:1002
ZADD search:index:keyword:redis 7.2 doc:1003
# 获取 Top 10 结果
ZREVRANGE search:index:keyword:redis 0 9 WITHSCORES
# 按分数范围过滤(相关性 > 8.0)
ZRANGEBYSCORE search:index:keyword:redis 8.0 +inf
ZREVRANGE 与 ZRANGEBYSCORE 是搜索缓存闭环中“排序”与“过滤”两个核心操作的底层实现基础。合理设计分数映射规则,可以避免在应用层进行二次排序。
2.2 Redis 持久化:RDB 与 AOF
持久化是 Redis 从“缓存”走向“数据库”的关键能力。在搜索缓存闭环中,持久化决定了索引数据在重启后能否快速恢复,以及数据丢失的容忍度。Redis 提供两种持久化机制:RDB(快照) 与 AOF(追加文件)。
2.2.1 RDB(Redis Database Backup)
RDB 通过创建内存数据的二进制快照实现持久化。它适合大规模数据的定期备份,恢复速度极快,但可能丢失最后一次快照之后的数据。
| 特性 | 说明 |
|---|---|
| 触发方式 | 手动 SAVE(同步阻塞)/ BGSAVE(异步 fork);自动根据配置 save <seconds> <changes> |
| 文件格式 | 二进制压缩文件 dump.rdb,体积小 |
| 恢复速度 | 极快,直接加载到内存 |
| 数据丢失 | 可能丢失最后一次快照后的所有写入 |
| 适用场景 | 冷备份、灾难恢复、搜索索引的定期全量快照 |
# 手动触发异步快照
BGSAVE
# 自动触发配置(60 秒内至少 10000 次写入则生成快照)
save 60 10000
# 查看最后一次成功快照的时间
LASTSAVE
2.2.2 AOF(Append Only File)
AOF 通过记录每次写操作的命令日志实现持久化。它提供更高的数据安全性(可配置 fsync 策略),但文件体积较大,恢复速度相对较慢。
| 特性 | 说明 |
|---|---|
| 记录方式 | 追加写操作命令,文本协议格式 |
| fsync 策略 | always(每次写入同步,最安全)、everysec(每秒同步,推荐)、no(由 OS 决定) |
| 文件重写 | BGREWRITEAOF 压缩重复命令,减少文件体积 |
| 恢复速度 | 较慢,需逐条回放命令 |
| 适用场景 | 需要高数据安全性的搜索索引增量更新 |
# 开启 AOF(redis.conf)
appendonly yes
appendfsync everysec
# 手动触发 AOF 重写
BGREWRITEAOF
always,每次搜索结果的写入都会触发磁盘同步,会显著降低写入吞吐量。建议使用 everysec 策略,在安全性与性能之间取得平衡。对于搜索索引的全量快照,RDB 是更高效的选择。
2.2.3 混合持久化(Redis 4.0+)
Redis 4.0 引入了混合持久化模式,将 RDB 快照与 AOF 增量日志结合。在 AOF 重写时,将当前内存数据以 RDB 格式写入 AOF 文件头部,后续增量命令以 AOF 格式追加。这种模式兼顾了 RDB 的快速恢复与 AOF 的低数据丢失。
# 开启混合持久化(redis.conf)
aof-use-rdb-preamble yes
2.3 持久化策略选择建议
在搜索缓存闭环中,持久化策略的选择取决于对数据丢失容忍度与恢复速度的要求。以下为三种典型场景的推荐配置:
- 场景一:纯缓存,可重建 —— 关闭持久化(
save "",appendonly no),仅作为加速层,数据丢失后从数据库重建。 - 场景二:搜索索引缓存,允许少量丢失 —— RDB 每 5 分钟快照 + AOF everysec,兼顾恢复速度与数据安全。
- 场景三:搜索索引主存储,零容忍丢失 —— AOF always + 定期 RDB 快照 + 混合持久化,配合 Redis Sentinel 或 Cluster 实现高可用。