String、List、Set、Hash、Sorted Set详解
Redis不仅仅是简单的键值存储,它支持五种核心数据类型,每种类型都有其特定的应用场景和操作命令。理解这些数据类型是掌握Redis的关键。
| 数据类型 | 描述 | 典型应用场景 | 底层实现 | 时间复杂度 |
|---|---|---|---|---|
| String | 字符串、数字 | 缓存、计数器、分布式锁 | SDS(简单动态字符串) | O(1) |
| List | 有序列表 | 消息队列、时间线、最新动态 | 双向链表/压缩列表 | O(1)~O(N) |
| Set | 无序集合 | 标签、共同好友、去重 | 哈希表/整数集合 | O(1) |
| Hash | 键值对集合 | 对象存储、用户信息 | 哈希表/压缩列表 | O(1) |
| Sorted Set | 有序集合 | 排行榜、优先队列 | 跳表+哈希表 | O(log N) |
Redis使用统一的对象系统来管理所有数据类型,每个Redis对象(robj)包含:
String是Redis最基本的数据类型,一个键最大能存储512MB的数据。String类型是二进制安全的,可以存储任何数据,如文本、数字、序列化对象、图片等。
SDS是Redis自定义的字符串实现,相比C字符串有以下优势:
// SDS数据结构定义(简化版)
struct sdshdr {
int len; // 已使用的字节数
int free; // 未使用的字节数
char buf[]; // 字节数组
};
Redis的String类型支持多种编码方式,根据存储内容自动选择:
# 1. 缓存对象(JSON序列化)
127.0.0.1:6379> SET user:1001 '{"name":"张三","age":25,"city":"北京"}'
OK
# 2. 计数器(网站访问量)
127.0.0.1:6379> SET page:home:views 0
127.0.0.1:6379> INCR page:home:views
(integer) 1
# 3. 分布式锁
127.0.0.1:6379> SET lock:order:123 "uuid-abc" EX 30 NX
OK
# 4. 限流器(记录请求次数)
127.0.0.1:6379> SET rate:user:1001 0 EX 60
127.0.0.1:6379> INCR rate:user:1001
(integer) 1
# 5. Session存储
127.0.0.1:6379> SETEX session:token123 3600 "user_data"
OK
List是简单的字符串列表,按照插入顺序排序。可以在列表的头部或尾部添加元素,最多可以包含2^32-1个元素(超过40亿)。
Redis的List使用两种底层编码方式,根据元素数量和大小自动选择:
当列表元素较少且元素较小时,使用压缩列表存储:
当列表元素较多或较大时,使用双向链表存储:
// 双向链表节点结构(简化版)
typedef struct listNode {
struct listNode *prev; // 前驱节点
struct listNode *next; // 后继节点
void *value; // 节点值
} listNode;
// 链表结构
typedef struct list {
listNode *head; // 头节点
listNode *tail; // 尾节点
unsigned long len; // 节点数量
// ... 其他字段
} list;
# 1. 消息队列(生产者-消费者模式)
# 生产者推送消息
127.0.0.1:6379> LPUSH queue:tasks "task1"
(integer) 1
127.0.0.1:6379> LPUSH queue:tasks "task2"
(integer) 2
# 消费者获取消息
127.0.0.1:6379> RPOP queue:tasks
"task1"
# 2. 最新动态(微博时间线)
127.0.0.1:6379> LPUSH timeline:user:1001 "发布了新微博"
127.0.0.1:6379> LPUSH timeline:user:1001 "点赞了一条微博"
127.0.0.1:6379> LRANGE timeline:user:1001 0 9
# 获取最新10条动态
# 3. 文章列表(分页)
127.0.0.1:6379> RPUSH articles "article1" "article2" "article3"
127.0.0.1:6379> LRANGE articles 0 1
# 第一页,显示2条
# 4. 栈操作(后进先出)
127.0.0.1:6379> LPUSH stack "A"
127.0.0.1:6379> LPUSH stack "B"
127.0.0.1:6379> LPOP stack
"B"
Set是String类型的无序集合,集合成员是唯一的,不能重复。Redis的Set是通过哈希表实现的,添加、删除、查找的时间复杂度都是O(1)。
Redis的Set使用两种底层编码方式:
当集合元素都是整数且数量较少时,使用整数集合:
当集合元素包含非整数或数量较多时,使用哈希表:
// 哈希表节点结构
typedef struct dictEntry {
void *key; // 键
union {
void *val; // 值(对于Set,val为NULL)
uint64_t u64;
int64_t s64;
} v;
struct dictEntry *next; // 下一个节点(解决哈希冲突)
} dictEntry;
// 哈希表结构
typedef struct dictht {
dictEntry **table; // 哈希表数组
unsigned long size; // 表大小
unsigned long sizemask; // 大小掩码
unsigned long used; // 已使用节点数
} dictht;
# 1. 标签系统
127.0.0.1:6379> SADD article:100:tags "Python" "Redis" "数据库"
(integer) 3
127.0.0.1:6379> SMEMBERS article:100:tags
1) "Python"
2) "Redis"
3) "数据库"
# 2. 共同好友
127.0.0.1:6379> SADD user:1001:friends "user:1002" "user:1003" "user:1004"
127.0.0.1:6379> SADD user:1005:friends "user:1003" "user:1004" "user:1006"
127.0.0.1:6379> SINTER user:1001:friends user:1005:friends
1) "user:1003"
2) "user:1004"
# 3. 点赞用户列表(去重)
127.0.0.1:6379> SADD post:200:likes "user:1001"
127.0.0.1:6379> SADD post:200:likes "user:1002"
127.0.0.1:6379> SADD post:200:likes "user:1001" # 重复添加无效
(integer) 0
127.0.0.1:6379> SCARD post:200:likes
(integer) 2
# 4. 抽奖系统(随机抽取)
127.0.0.1:6379> SADD lottery:users "user1" "user2" "user3" "user4"
127.0.0.1:6379> SRANDMEMBER lottery:users 2
1) "user3"
2) "user1"
# 5. IP黑名单
127.0.0.1:6379> SADD blacklist:ip "192.168.1.100" "10.0.0.50"
127.0.0.1:6379> SISMEMBER blacklist:ip "192.168.1.100"
(integer) 1
Hash是一个键值对集合,特别适合存储对象。每个Hash可以存储2^32-1个键值对。相比String存储对象(JSON序列化),Hash可以只修改某个字段,更加灵活高效。
Redis的Hash使用两种底层编码方式:
当Hash字段较少且字段值较小时,使用压缩列表:
当Hash字段较多或字段值较大时,使用哈希表:
# 1. 用户信息存储
127.0.0.1:6379> HSET user:1001 name "张三" age 25 city "北京" job "工程师"
(integer) 4
127.0.0.1:6379> HGET user:1001 name
"张三"
127.0.0.1:6379> HGETALL user:1001
1) "name"
2) "张三"
3) "age"
4) "25"
5) "city"
6) "北京"
7) "job"
8) "工程师"
# 2. 商品信息
127.0.0.1:6379> HMSET product:2001 name "iPhone15" price 5999 stock 100 category "手机"
OK
127.0.0.1:6379> HINCRBY product:2001 stock -1
(integer) 99
# 3. 购物车
127.0.0.1:6379> HSET cart:user:1001 product:2001 2
127.0.0.1:6379> HSET cart:user:1001 product:2002 1
127.0.0.1:6379> HGETALL cart:user:1001
1) "product:2001"
2) "2"
3) "product:2002"
4) "1"
# 4. 网站配置
127.0.0.1:6379> HMSET config:site title "我的网站" domain "example.com" email "admin@example.com"
OK
# 5. 统计数据(多维度计数)
127.0.0.1:6379> HINCRBY stats:2024-01-01 page_views 100
127.0.0.1:6379> HINCRBY stats:2024-01-01 unique_visitors 50
127.0.0.1:6379> HGETALL stats:2024-01-01
1) "page_views"
2) "100"
3) "unique_visitors"
4) "50"
Sorted Set是Set的升级版,每个成员都关联一个分数(score),Redis通过分数为集合成员排序。成员唯一,但分数可以重复。
Redis的Sorted Set使用两种数据结构的组合:
用于维护有序的成员列表,支持快速的范围查询和排名操作:
用于快速查找成员对应的分数:
// 跳表节点结构(简化版)
typedef struct zskiplistNode {
robj *obj; // 成员对象
double score; // 分数
struct zskiplistNode *backward; // 后退指针
struct zskiplistLevel {
struct zskiplistNode *forward; // 前进指针
unsigned int span; // 跨度
} level[]; // 层级数组
} zskiplistNode;
// 跳表结构
typedef struct zskiplist {
struct zskiplistNode *header, *tail; // 头尾节点
unsigned long length; // 节点数量
int level; // 最大层数
} zskiplist;
# 1. 排行榜(游戏积分)
127.0.0.1:6379> ZADD leaderboard 1000 "player1" 1500 "player2" 1200 "player3"
(integer) 3
127.0.0.1:6379> ZREVRANGE leaderboard 0 2 WITHSCORES
1) "player2"
2) "1500"
3) "player3"
4) "1200"
5) "player1"
6) "1000"
# 2. 热搜榜(按热度排序)
127.0.0.1:6379> ZADD hot:topics 5000 "话题A" 8000 "话题B" 3000 "话题C"
127.0.0.1:6379> ZINCRBY hot:topics 1000 "话题A"
"6000"
127.0.0.1:6379> ZREVRANK hot:topics "话题A"
(integer) 1
# 3. 延迟队列(按时间戳排序)
127.0.0.1:6379> ZADD delay:queue 1675234567 "task1" 1675234600 "task2"
127.0.0.1:6379> ZRANGEBYSCORE delay:queue 0 1675234580
1) "task1"
# 4. 成绩排名
127.0.0.1:6379> ZADD exam:scores 95 "张三" 88 "李四" 92 "王五"
127.0.0.1:6379> ZRANK exam:scores "李四"
(integer) 0 # 排名第1(从0开始)
# 5. 商品销量排行
127.0.0.1:6379> ZADD sales:ranking 1000 "product:A" 1500 "product:B" 800 "product:C"
127.0.0.1:6379> ZINCRBY sales:ranking 100 "product:A"
"1100"
127.0.0.1:6379> ZREVRANGE sales:ranking 0 9 WITHSCORES
# 获取销量前10的商品
# 决策树:
1. 需要存储单个值?
→ 使用 String
2. 需要存储对象(多个字段)?
→ 使用 Hash
3. 需要存储列表(有序、可重复)?
→ 使用 List
4. 需要存储集合(无序、不重复)?
→ 不需要排序 → 使用 Set
→ 需要排序 → 使用 Sorted Set
5. 需要集合运算(交集、并集、差集)?
→ 使用 Set
6. 需要排行榜、优先队列?
→ 使用 Sorted Set
| 操作 | String | List | Set | Hash | Sorted Set |
|---|---|---|---|---|---|
| 添加元素 | O(1) | O(1) | O(1) | O(1) | O(log N) |
| 删除元素 | O(1) | O(N) | O(1) | O(1) | O(log N) |
| 查找元素 | O(1) | O(N) | O(1) | O(1) | O(log N) |
| 范围查询 | - | O(N) | - | - | O(log N + M) |
# 存储1000个用户信息的内存占用对比
# 方式1: String(JSON序列化)
SET user:1 '{"name":"张三","age":25}'
SET user:2 '{"name":"李四","age":30}'
...
# 内存占用: ~150KB
# 方式2: Hash(推荐)
HSET user:1 name "张三" age 25
HSET user:2 name "李四" age 30
...
# 内存占用: ~80KB(节省约47%)
# 结论: Hash在存储对象时更节省内存
本课程详细介绍了Redis的五大核心数据类型:
理解每种数据类型的特点和应用场景,是高效使用Redis的基础。在实际开发中,要根据业务需求选择最合适的数据类型,以达到最佳的性能和内存效率。