Redis 数据结构与持久化
数据结构总览
Redis 提供了 8 种核心数据结构,每种都有独特的使用场景。
| 数据结构 | 底层编码 | 最大容量 | 核心特性 |
|---|---|---|---|
| String | int / embstr / raw | 512 MB | 最基础,支持整数自增 |
| List | quicklist | 2³²-1 元素 | 双向链表,支持 LPUSH/RPOP |
| Set | intset / hashtable | 2³²-1 元素 | 无序唯一,支持交并差 |
| ZSet | ziplist / skiplist | 2³²-1 元素 | 有序唯一,按 score 排序 |
| Hash | ziplist / hashtable | 2³²-1 键值对 | 对象属性存储 |
| HyperLogLog | 稀疏/密集编码 | 12 KB → 2⁶⁴ 基数 | 基数统计,有误差 |
| Bitmap | SDS 字符串 | 512 MB(约 42 亿位) | 位操作 |
| Geo | ZSet 编码 | 2³²-1 元素 | 地理位置计算 |
| Stream | listpack / rax | 海量 | 消息队列(5.0+) |
String — 字符串
底层编码(根据值和长度自动选择):
text
int → 整数(< 2⁶³),用 long 存储 例: SET key 100
embstr → 短字符串(≤ 44 字节),SDS 和对象连续内存 例: SET key "hello"
raw → 长字符串(> 44 字节),SDS 和对象分离 例: SET key "a very long string..."SDS(Simple Dynamic String) 结构:
c
struct sdshdr {
int len; // 已用长度
int free; // 可用空间
char buf[]; // 数据
};优势:O(1) 获取长度、预分配避免频繁扩容、二进制安全。
使用场景:
- 缓存(JSON 序列化对象)
- 分布式锁
SET key value NX EX 30 - 计数器
INCR article:read:1001 - 限流
INCR + EXPIRE
List — 列表
底层编码:quicklist(Redis 3.2+),将 ziplist 分段连接成双向链表。
text
quicklist 结构:
ziplist → ziplist → ziplist → ...
| | |
[元素...] [元素...] [元素...]使用场景:
- 消息队列:
LPUSH queue msg+BRPOP queue 0 - 最新列表:
LPUSH news:list item→LTRIM news:list 0 99 - 时间线(Timeline):用户发帖推入 List
Set — 集合
底层编码(自动转换条件):
- intset:所有元素都是整数且数量 < 512(可配置
set-max-intset-entries) - hashtable:元素不是整数或数量超过阈值
text
intset: 有序整数数组,二分查找 — 内存密集
hashtable: 字典,O(1) 查找 — 灵活但占内存使用场景:
- 标签系统
SADD article:1:tags java redis - 共同好友
SINTER user:1:friends user:2:friends - 独立用户统计
SADD uv:2026-07-14 user:1001 - 抽奖去重
SRANDMEMBER/SPOP
ZSet — 有序集合
底层编码(自动转换条件):
- ziplist:元素数量 < 128 且所有元素长度 < 64 字节
- skiplist:超出上述条件
text
skiplist 结构:
head → level3: ───────────────→ [Node] → NULL
head → level2: ─────→ [Node] ─→ [Node] → NULL
head → level1: → [Node] → [Node] → [Node] → NULL跳表通过多层索引实现 O(log N) 的查找性能,实现比平衡树简单。
使用场景:
- 排行榜
ZINCRBY leaderboard 100 user:1 - 延迟队列
ZRANGEBYSCORE delay:queue 0 now - 滑动窗口限流
ZREMRANGEBYSCORE + ZCARD
Hash — 哈希
底层编码(自动转换条件):
- ziplist:键值对数量 < 512 且所有键值长度 < 64 字节
- hashtable:超出上述条件
ziplist 编码时,key 和 value 连续存储在列表中:
text
[attr1][val1][attr2][val2]...使用场景:
- 对象属性
HSET user:1001 name "Alice" age 25 - 购物车
HINCRBY cart:1001 item:2001 1 - 短链映射 `HSET url:hash long_url "..."
HyperLogLog — 基数统计
原理:通过伯努利试验的估算,用固定 12 KB 内存统计高达 2⁶⁴ 个元素的基数。
text
PFADD uv:page:1 user:1001 user:1002
PFCOUNT uv:page:1 → 约 2
PFMERGE uv:total uv:page:1 uv:page:2特点:
- 固定内存(12 KB),无论数据量多大
- 标准误差约 0.81%
- 不可反推具体元素(隐私友好)
使用场景:UV 统计、日活月活、搜索词去重
Bitmap — 位图
本质是 String 的位操作,将字符串视为 bit 数组。
text
SETBIT sign:202607 0 1 // 7月1日签到
SETBIT sign:202607 1 1 // 7月2日签到
BITCOUNT sign:202607 // 签到天数 → 2使用场景:
- 签到系统(每个用户 365 天 = 46 字节)
- 在线用户状态(位图标记百万用户 = 125 KB)
- 布隆过滤器实现
Geo — 地理位置
底层:使用 ZSet 编码,score 为 GeoHash 编码后的 52 位整数。
text
GEOADD cities 116.40 39.90 "Beijing"
GEOADD cities 121.47 31.23 "Shanghai"
GEODIST cities Beijing Shanghai km → 约 1068 km
GEORADIUS cities 116 40 100 km → 半径搜索GeoHash 编码将二维经纬度转换为唯一字符串,相邻地理位置的编码前缀相同。
使用场景:附近的人、门店搜索、路线规划
Stream — 消息队列
Redis 5.0 引入,专为消息队列设计的日志结构。
text
XADD mystream * sensor-id 1234 temp 19.8
→ "1712345678000-0" (时间戳-序号)
XREAD COUNT 10 BLOCK 0 STREAMS mystream $
→ 等待新消息(阻塞读取)
XGROUP CREATE mystream mygroup $
XREADGROUP GROUP mygroup consumer1 COUNT 1 STREAMS mystream >
→ 消费者组模式特性:
- 消息持久化(写入 RDB/AOF)
- 消费者组(类似 Kafka 的分组消费)
- ACK 机制保证消息不丢失
- Pending List 支持消息重试
对比:
| 特性 | List 作队列 | Stream |
|---|---|---|
| 消息持久 | ✅ | ✅ |
| 阻塞读取 | ✅ | ✅ |
| 消费者组 | ❌ | ✅ |
| 消息确认 | ❌ | ✅ |
| 多播 | ❌ | ✅ |
持久化
RDB(快照)
原理:将内存数据定时保存为二进制 dump.rdb 文件。
触发方式:
SAVE— 同步阻塞(不推荐)BGSAVE— fork 子进程异步写入- 自动触发:
save 900 1(900 秒内 1 次修改) - 主从复制首次全量同步
优点:文件紧凑,恢复快,适合备份 缺点:可能丢失两次快照间的数据,fork 可能阻塞
AOF(Append-Only File)
原理:将写操作以 Redis 协议格式追加到 .aof 文件末尾。
三种刷盘策略:
| appendfsync | 说明 | 安全性 | 性能 |
|---|---|---|---|
| always | 每条命令都 fsync | 最高 | 最慢 |
| everysec(默认) | 每秒 fsync | 最多丢 1s 数据 | 均衡 |
| no | 由 OS 决定刷盘时机 | 最多丢多秒数据 | 最快 |
AOF 重写(BGREWRITEAOF):合并冗余命令,缩小文件体积。
混合持久化(Redis 4.0+)
text
aof-use-rdb-preamble yesAOF 重写时,将全量数据保存为 RDB 格式,增量数据保存为 AOF 格式:
text
[RDB 格式的全量数据][AOF 格式的增量数据]选型建议
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 缓存(允许丢失) | RDB 或关闭持久化 | 性能优先 |
| 数据不丢失 | RDB + AOF 混合 | 兼顾恢复速度和数据安全 |
| 消息队列 | AOF everysec | 数据可靠性要求高 |
| 主从复制 | 从节点开 AOF | 主节点专注写入 |
内存优化建议
- 使用 ziplist 编码的 Hash/ZSet 代替 String 存储对象
- 短结构优先选用 embstr 编码
- 淘汰策略
maxmemory-policy:allkeys-lru / volatile-ttl / noeviction - 大 Key 拆分(单个 Key 超过 10 KB 需注意)
- 用
MEMORY USAGE key分析内存占用