玩转Redis五大核心数据结构:从计数器到排行榜
在上一篇中,我们知道了Redis为什么这么快,以及如何搭建环境。但Redis真正的威力,来自于它丰富的数据结构。如果说简单的键值对是Redis的"骨架",那么这些数据结构就是它的"肌肉",让Redis能够优雅地解决各种复杂的业务场景。
Redis最吸引人的地方在于,它不仅仅是一个简单的键值存储,而是一个数据结构服务器。今天,我们将深入探索Redis的五大核心数据结构:String(字符串)、Hash(哈希)、List(列表)、Set(集合) 和 Sorted Set(有序集合)。
一、String(字符串):不止是文本
String是Redis最基本的数据类型,一个Key对应一个Value。但别被它的名字骗了——它不仅可以存储文本,还可以存储数字(整数或浮点数)甚至是二进制数据(如图片或序列化对象)。
核心命令与特性
# 1. 基础设置与获取
127.0.0.1:6379> SET username "redis_learner"
OK
127.0.0.1:6379> GET username
"redis_learner"
# 2. 数字操作 - Redis知道你是数字时会允许数学运算
127.0.0.1:6379> SET page_views 100
OK
127.0.0.1:6379> INCR page_views # 增加1
(integer) 101
127.0.0.1:6379> INCRBY page_views 10 # 增加指定数值
(integer) 111
127.0.0.1:6379> DECR page_views # 减少1
(integer) 110
# 3. 批量操作 - 提升效率
127.0.0.1:6379> MSET user:1001:name "Alice" user:1001:age 25 user:1001:city "Beijing"
OK
127.0.0.1:6379> MGET user:1001:name user:1001:age user:1001:city
1) "Alice"
2) "25"
3) "Beijing"
# 4. 条件设置 - 实现分布式锁的基础
127.0.0.1:6379> SETNX lock:order_123 "client_1" # 只有当key不存在时才设置
(integer) 1 # 设置成功
127.0.0.1:6379> SETNX lock:order_123 "client_2"
(integer) 0 # 设置失败,因为key已存在
实战应用场景
- 缓存:存储序列化的用户信息、页面片段等
- 计数器:文章阅读量、用户点赞数、网站访问量
- 分布式锁:通过
SETNX实现简单的互斥锁 - 会话存储:存储用户Session数据
性能提示:对于多个相关的键值对,使用MSET/MGET比多次SET/GET更高效,因为它减少了网络往返次数。
二、Hash(哈希表):存储对象的最佳选择
如果你需要存储一个对象(如用户信息、商品信息),Hash是你的最佳选择。它类似于编程语言中的字典或Map,适合存储字段-值对的集合。
为什么用Hash而不用多个String?
假设我们要存储用户信息,有两种方案:
-
方案一(多个String):
SET user:1001:name "Bob" SET user:1001:age 30 SET user:1001:email "bob@example.com" -
方案二(一个Hash):
HSET user:1001 name "Bob" age 30 email "bob@example.com"
Hash的优势:
- 内存效率更高:Redis对Hash有特殊优化,特别是字段较少时
- 原子性操作:可以一次性获取或修改整个对象
- 更少的Key:避免键空间膨胀,管理更方便
核心命令详解
# 1. 设置和获取字段
127.0.0.1:6379> HSET product:1001 name "iPhone 15" price 5999 stock 100
(integer) 3 # 返回设置的字段数量
127.0.0.1:6379> HGET product:1001 name
"iPhone 15"
127.0.0.1:6379> HGETALL product:1001 # 获取所有字段和值
1) "name"
2) "iPhone 15"
3) "price"
4) "5999"
5) "stock"
6) "100"
# 2. 批量操作
127.0.0.1:6379> HMGET product:1001 name price # 获取多个字段
1) "iPhone 15"
2) "5999"
# 3. 数字运算
127.0.0.1:6379> HINCRBY product:1001 stock -1 # 库存减1(售出一件)
(integer) 99
# 4. 检查字段
127.0.0.1:6379> HEXISTS product:1001 name
(integer) 1
127.0.0.1:6379> HKEYS product:1001 # 获取所有字段名
1) "name"
2) "price"
3) "stock"
实战应用场景
- 用户信息存储:将用户的所有属性存储在一个Hash中
- 商品信息:商品的名称、价格、库存等信息
- 配置信息:系统的各种配置参数
- 购物车:用户ID作为Key,商品ID和数量作为字段-值对
三、List(列表):实现简单的消息队列
List是一个按插入顺序排序的字符串元素集合,你可以在列表的头部(左边)或尾部(右边)添加元素。Redis的List底层实现是双向链表,这意味着在头部和尾部添加元素的速度极快,但通过索引访问中间元素相对较慢。
核心命令详解
# 1. 从两端添加元素
127.0.0.1:6379> LPUSH tasks "send_email" # 从左边添加
(integer) 1
127.0.0.1:6379> LPUSH tasks "process_image"
(integer) 2
127.0.0.1:6379> RPUSH tasks "generate_report" # 从右边添加
(integer) 3
# 此时列表:["process_image", "send_email", "generate_report"]
# 头(左) <---------> 尾(右)
# 2. 从两端弹出元素
127.0.0.1:6379> LPOP tasks # 从左边弹出
"process_image"
127.0.0.1:6379> RPOP tasks # 从右边弹出
"generate_report"
# 3. 查看列表范围(不会弹出元素)
127.0.0.1:6379> LRANGE tasks 0 -1 # 查看所有元素,0表示开始,-1表示末尾
1) "send_email"
# 4. 阻塞操作 - 消息队列的核心
# 从一个空列表中阻塞地等待元素,最多等待10秒
127.0.0.1:6379> BLPOP message_queue 10
(nil) # 10秒内没有元素,返回nil
# 在另一个客户端执行:LPUSH message_queue "new_message"
# 此时BLPOP会立即返回:"message_queue" "new_message"
实战应用场景
- 消息队列:使用
LPUSH添加任务,BRPOP阻塞获取任务 - 最新消息列表:使用
LPUSH添加新消息,LRANGE 0 9获取最新的10条 - 历史记录:用户浏览历史、搜索历史
- 文章评论列表:文章的评论按时间顺序排列
重要特性:List的阻塞操作(BLPOP, BRPOP)使其成为实现简单消息队列的理想选择,消费者可以在队列为空时等待,而不需要轮询。
四、Set(集合):无序与唯一性的力量
Set是String类型的无序集合,它最大的特点是:元素唯一且无序。底层通过哈希表实现,添加、删除、查找的时间复杂度都是O(1)。
核心命令与集合运算
# 1. 基本操作
127.0.0.1:6379> SADD tags "java" "python" "redis" "java"
(integer) 3 # "java"重复,只添加了3个元素
127.0.0.1:6379> SMEMBERS tags # 获取所有元素(顺序不确定)
1) "redis"
2) "python"
3) "java"
127.0.0.1:6379> SISMEMBER tags "python" # 检查元素是否存在
(integer) 1
# 2. 集合运算 - Set的精华所在
127.0.0.1:6379> SADD user:1001:follows "user:1002" "user:1003" "user:1004"
(integer) 3
127.0.0.1:6379> SADD user:1002:follows "user:1003" "user:1005"
(integer) 2
# 交集 - 共同关注
127.0.0.1:6379> SINTER user:1001:follows user:1002:follows
1) "user:1003"
# 并集 - 所有的关注
127.0.0.1:6379> SUNION user:1001:follows user:1002:follows
1) "user:1002"
2) "user:1003"
3) "user:1004"
4) "user:1005"
# 差集 - A有但B没有的
127.0.0.1:6379> SDIFF user:1001:follows user:1002:follows
1) "user:1002"
2) "user:1004"
# 3. 随机元素 - 抽奖功能
127.0.0.1:6379> SADD lottery_users "user1" "user2" "user3" "user4" "user5"
(integer) 5
127.0.0.1:6379> SRANDMEMBER lottery_users 2 # 随机返回2个元素,不删除
1) "user3"
2) "user5"
127.0.0.1:6379> SPOP lottery_users 1 # 随机弹出1个元素并删除
1) "user2"
实战应用场景
- 标签系统:给文章、用户打标签
- 社交关系:共同好友、共同关注
- 数据去重:防止重复提交、重复处理
- 随机抽奖:从参与用户中随机抽取中奖者
- 黑白名单:IP白名单、用户黑名单
五、Sorted Set(有序集合):排行榜的灵魂
Sorted Set是Set的增强版,它在保证元素唯一性的基础上,为每个元素关联了一个分数(Score),元素按照分数进行排序。这是Redis中最复杂但也最强大的数据结构之一。
底层实现:跳表(Skip List)
Sorted Set使用跳表(一种类似链表但有多级索引的数据结构)实现,可以在O(logN)时间内完成插入、删除和按分数范围查找,兼具了链表和二分查找的优点。
核心命令详解
# 1. 添加元素(带分数)
127.0.0.1:6379> ZADD leaderboard 2500 "Alice" 1800 "Bob" 3200 "Charlie" 1500 "David"
(integer) 4
# 2. 按分数范围查询(升序)
127.0.0.1:6379> ZRANGE leaderboard 0 -1 WITHSCORES # 获取所有元素(分数从低到高)
1) "David"
2) "1500"
3) "Bob"
4) "1800"
5) "Alice"
6) "2500"
7) "Charlie"
8) "3200"
# 3. 按分数范围查询(降序 - 排行榜常用)
127.0.0.1:6379> ZREVRANGE leaderboard 0 2 WITHSCORES # 获取前三名
1) "Charlie"
2) "3200"
3) "Alice"
4) "2500"
5) "Bob"
6) "1800"
# 4. 按分数范围查询
127.0.0.1:6379> ZRANGEBYSCORE leaderboard 2000 3000 WITHSCORES # 分数在2000-3000之间的玩家
1) "Alice"
2) "2500"
# 5. 获取排名和分数
127.0.0.1:6379> ZRANK leaderboard "Alice" # 获取升序排名(从0开始)
(integer) 2
127.0.0.1:6379> ZREVRANK leaderboard "Alice" # 获取降序排名(排行榜名次)
(integer) 1
127.0.0.1:6379> ZSCORE leaderboard "Alice" # 获取分数
"2500"
# 6. 分数操作
127.0.0.1:6379> ZINCRBY leaderboard 500 "Alice" # Alice增加500分
"3000"
实战应用场景
- 排行榜:游戏积分榜、销量排行榜、热搜榜
- 带权重的队列:优先级任务调度
- 时间轴:按时间排序的消息列表(时间戳作为Score)
- 范围查询:查找分数/价格在某个区间的数据
性能提示:Sorted Set的范围查询(ZRANGEBYSCORE)非常高效,特别适合需要按范围检索数据的场景。
数据结构选择指南
面对具体业务场景时,如何选择合适的数据结构?这里有一个快速参考:
| 需求场景 | 推荐数据结构 | 理由 |
|---|---|---|
| 缓存简单数据 | String | 简单直接,性能最佳 |
| 存储对象 | Hash | 内存效率高,支持部分更新 |
| 消息队列 | List | 支持阻塞操作,顺序保证 |
| 最新N条记录 | List | LPUSH + LTRIM 实现固定长度列表 |
| 去重、标签、共同好友 | Set | 天然去重,支持集合运算 |
| 排行榜、范围查询 | Sorted Set | 按分数排序,范围查询高效 |
| 时间序列数据 | Sorted Set | 时间戳作为Score,天然排序 |
总结
通过本篇的学习,我们已经掌握了Redis五大核心数据结构的特性和应用:
- String:简单但强大,支持数字操作
- Hash:存储对象的理想选择,内存效率高
- List:顺序数据结构,适合消息队列和时间线
- Set:无序唯一集合,强大的集合运算能力
- Sorted Set:有序唯一集合,排行榜和范围查询的利器
Redis的哲学是:将复杂的数据操作下推到存储层,而不是在应用层处理。理解每种数据结构的特性和适用场景,能够让你在设计系统时做出更优雅、更高效的决策。
现在,当你需要实现计数器时,不会选择在应用层读取-计算-保存,而是直接使用INCR命令;当你需要排行榜时,不会在数据库中排序,而是使用Sorted Set。这就是Redis数据结构的威力所在!
动手练习:尝试用你学到的数据结构实现以下功能:
- 使用String实现一个文章阅读量计数器
- 使用Hash存储你的个人简历信息
- 使用List实现一个简单的待办事项列表
- 使用Set找出你和你朋友共同喜欢的电影
- 使用Sorted Set创建一个游戏分数排行榜
欢迎在评论区分享你的实现代码和心得体会!