Redis--基础知识点--33--String Hash List Set ZSet

类型 底层数据结构 编码切换条件
String int / embstr / raw 按值大小和类型切换
Hash listpack / hashtable 元素少用 listpack,多了用 hashtable
List quicklist(7.0+) 历史用过 ziplist + linkedlist
Set intset / hashtable 全整数用 intset,否则 hashtable
ZSet listpack / skiplist+hashtable 元素少用 listpack,多了用 skiplist

String

复制代码
值类型判断:
├── 整数 → int 编码
├── 字符串 ≤ 44 字节 → embstr 编码
└── 字符串 > 44 字节 → raw 编码
编码 底层结构 条件 说明
int long 整数 值为整数且 ≤ long 范围 直接存 SDS header + 数字
embstr SDS + RedisObject 连续内存 字符串 ≤ 44 字节 一次内存分配,缓存友好
raw SDS + RedisObject 分离内存 字符串 > 44 字节 两次内存分配
复制代码
embstr:  [RedisObject | SDS header | data]  ← 一块连续内存
raw:     [RedisObject] → 指针 → [SDS header | data]  ← 两块内存

44 字节阈值原因:64 字节 jemalloc 分配,减去 RedisObject(16B) + SDS header(3B) + 结尾 \0(1B) + 预留(4B) ≈ 44B


Hash

编码 底层结构 切换条件
listpack 紧凑连续存储 元素数 < 128 且 每个 entry < 64 字节
hashtable 哈希表 超过任一阈值则切换
复制代码
listpack 编码:
┌─────────────────────────────────┐
│ field1 | value1 | field2 | value2 │  ← 连续内存,省空间
└─────────────────────────────────┘

hashtable 编码:
┌──────────┐
│ dictEntry │ → field → value
│ dictEntry │ → field → value
│ ...       │
└──────────┘
特性 listpack hashtable
内存占用 极小 有额外指针开销
查找复杂度 O(n) O(1)
适用场景 少量元素 大量元素

List

版本 底层结构 说明
Redis 7.0+ quicklist 双向链表,每个节点是一个 listpack
Redis 3.2~6.x quicklist 双向链表 + ziplist 节点
Redis < 3.2 ziplist / linkedlist 按条件切换
复制代码
quicklist 结构:

  head ←→ [listpack node] ←→ [listpack node] ←→ [listpack node] ←→ tail
            ┌──────────┐     ┌──────────┐
            │ item1    │     │ item4    │
            │ item2    │     │ item5    │
            │ item3    │     │ item6    │
            └──────────┘     └──────────┘
           (连续内存块)      (连续内存块)
特性 说明
兼顾内存和性能 小数据用 listpack 压缩省内存,大数据用链表保证操作效率
单节点 listpack 大小 默认 8KB,可配置 list-max-listpack-size
支持 LPOP/RPOP O(1)
支持 LRANGE O(n)

Set

编码 底层结构 切换条件
intset 有序整数数组 所有元素都是整数 且 元素数 ≤ 512
hashtable 哈希表 非整数元素 或 元素数 > 512
复制代码
intset 编码:
┌───────────────────────────────────────────┐
│ header | 1 | 5 | 10 | 20  │  ← 有序整数数组,支持二分查找
└───────────────────────────────────────────┘
  升级: int16 → int32 → int64(按最大元素决定编码)

hashtable 编码:
┌──────────┐
│ dictEntry │ → "apple" (value=NULL)
│ dictEntry │ → "banana"
│ ...       │
└──────────┘
特性 intset hashtable
内存 极省(无指针开销) 有指针开销
查找 O(log n) 二分查找 O(1)
元素类型 仅整数 任意
编码升级 int16→int32→int64 ---

ZSet(Sorted Set)

编码 底层结构 切换条件
listpack 紧凑连续存储 元素数 < 128 且 每个 entry < 64 字节
skiplist + hashtable 跳表 + 哈希表 超过任一阈值
复制代码
skiplist 编码(双结构组合):

  ┌─────────────────────────────────────────────────┐
  │ SkipList(跳表)                                 │
  │                                                  │
  │  L3:  head ──────────────────→ 30               │
  │  L2:  head ─────→ 10 ───────→ 30               │
  │  L1:  head → 5 → 10 → 20 ──→ 30                │
  │  L0:  head → 5 → 10 → 20 → 30 → 40             │
  │                                                  │
  │  每个 node: [member, score, forward[], backward] │
  └─────────────────────────────────────────────────┘

  ┌─────────────────────────────────────────────────┐
  │ Dict(哈希表)                                   │
  │                                                  │
  │  "apple"  → score=10                            │
  │  "banana" → score=20                            │
  │  "cherry" → score=5                             │
  │                                                  │
  │  作用: O(1) 查 score                            │
  └─────────────────────────────────────────────────┘
操作 用哪个结构 复杂度
ZADD 跳表插入 + 哈希表更新 O(log n)
ZSCORE 哈希表 O(1)
ZRANK 跳表回溯 O(log n)
ZRANGE 跳表遍历 O(log n + m)

为什么用两个结构? 跳表支持范围查询,哈希表支持 O(1) 查分数。两者配合覆盖所有操作。


为什么用跳表而不是红黑树?

对比 跳表 (SkipList) 红黑树 (RBTree)
范围查询 ✅ 天然支持,链表顺序遍历 ❌ 需要中序遍历,复杂
实现复杂度 简单(概率性平衡) 复杂(旋转+变色)
并发友好 ✅ 局部加锁 ❌ 旋转影响范围大
内存 额外多级指针 每节点额外颜色位
缓存友好 较好 一般

Redis 作者 Antirez 的选择理由:实现简单 + 范围查询天然支持 + 并发友好。


总结速查

复制代码
String:  int / embstr / raw
Hash:    listpack / hashtable
List:    quicklist
Set:     intset / hashtable
ZSet:    listpack / skiplist + hashtable
设计原则 体现
小数据省内存 listpack / intset 紧凑存储
大数据保性能 hashtable / skiplist 保证 O(1) 或 O(log n)
编码自动切换 根据元素数量和大小自动升级,对用户透明
空间换时间 ZSet 同时维护跳表+哈希表
相关推荐
我不会起名字3227 小时前
Redis 从 0 到 1(三十三):机器断电 Redis 起不来——AOF/RDB 损坏的 4 步抢救流程
redis·数据恢复·aof·rdb·排查
csdn_aspnet9 小时前
Redis 入门:Windows、Mac安装指南
数据库·windows·redis·缓存·mac
IT规划师10 小时前
Redis 数据类型全解 - HyperLogLog:12 KB 估出亿级 UV(八)
redis·uv·hyperloglog·.net10
aiks_yuan1 天前
Token 放 Redis 还是用 JWT:Java 实测验签和查 Redis 耗时,退出登录、改密码踢下线怎么做(jjwt 0.12 + Redis 7.2)
redis
我不会起名字3221 天前
Redis 从 0 到 1(二十九):CPU 突然 100%——commandstats 定位法与 6 步排查链路
redis·cpu·慢查询·排查·commandstats
我不会起名字3221 天前
Redis 从 0 到 1(二十八):Redis 抖一下,重试把它打挂了——超时/重试/熔断的 Go 封装
redis·go·超时·熔断·重试
我不会起名字3221 天前
Redis 从 0 到 1(三十):删了一半数据 RSS 不降——内存碎片与 activedefrag 观测
redis·内存碎片·rss·排查·activedefrag
csdn_aspnet2 天前
使用 Docker 和 RedisInsight 设置 Redis 以进行本地开发
redis·docker·容器·redisinsight
用户094248568032 天前
第22章:Redis 持久化 RDB 与 AOF:数据不丢的底线
redis·架构·源码