前言
Redis 之所以能成为后端开发的“瑞士军刀”,很大程度上归功于它丰富且高效的数据结构。不同于传统的 key-value 数据库,Redis 的 value 支持多种结构,让开发者能够像使用编程语言中的集合一样操作数据。本文将结合 Redis 7,深入剖析九大数据结构的命令、底层设计、使用场景及避坑指南。
本文所有命令均可在Redis7中运行,建议开启一个redis-cil边看边试。
一、字符串(String)——最灵活的基石
字符串是Redis中最简单的类型,但是它的能力远超你的想象。一个key对应一个值,值可以是文本、整数、浮点数、甚至是二进制数据。
1.1基本命令
# 基本存取
SET user:1001 "roy"
GET user:1001
# 批量操作(减少网络往返)
MSET user:1001 "roy" user:1002 "loulan"
MGET user:1001 user:1002
# 仅当 key 不存在时设置(分布式锁核心)
SETNX lock:order "locked"
# 带过期时间的原子设置(防止死锁)
SET lock:order "locked" EX 10 NX
# 追加内容
APPEND user:1001 " is a coder"
# 原子增减(用于计数、限流)
INCR article:1001:views
DECR product:stock
INCRBY balance 100
DECRBY balance 50
1.2底层原理
- Redis 3.2 以后,字符串使用 SDS(Simple Dynamic String) 结构。SDS 记录了长度,获取长度 O(1),且杜绝缓冲区溢出。
- 整数编码:当值是整数且在
-2^63 ~ 2^63-1范围内,Redis 会使用 int 编码存储,极大节省内存并支持高效加减。
1.3经典应用场景
| 场景 | 实现方式 |
|---|---|
| 对象缓存 | set user:1 ‘{“name”:“roy”}’ 或 MSET 拆字段 |
| 分布式锁 | SET key value NX EX seconds |
| 计数器 | 文章阅读量、商品库存、限流(INCR + 过期) |
| 全局ID生成 | INCR global:uuid |
二、哈希(Hash)——对象储存神器
哈希相当于一个 key 对应一个内部字典,适合存储对象的多字段。
2.1基本命令
HSET user:1001 name roy age 28
HGET user:1001 name
HMGET user:1001 name age
HGETALL user:1001
HDEL user:1001 age
HLEN user:1001
HINCRBY user:1001 age 1 # 字段自增
HINCRBYFLOAT user:1001 score 0.5 # 浮点数增量
HSETNX user:1001 email "[email protected]" # 字段不存在才设置
2.2应用案例:电商购物车
# 以用户 1001 的购物车为例,商品 10088 数量 1
HSET cart:1001 10088 1
HINCRBY cart:1001 10088 1 # 增加一件
HLEN cart:1001 # 购物车商品种类数
HGETALL cart:1001 # 获取所有商品及数量
HDEL cart:1001 10088 # 删除商品
2.3底层原理
- Redis 哈希底层使用 ziplist(压缩列表) 或 hashtable(字典)。
- 当字段少且 value 短时,使用 ziplist 连续存储,节省内存。
- 字段增多后自动转为 hashtable,查询 O(1)。
- Redis 7 优化了 ziplist 的内存布局,进一步减少碎片。
三、列表(List)——双端队列
List是一个双向链表结构,支持头尾操作。
3.1核心命令
LPUSH queue a b c # 左侧插入三个元素,最终顺序 c b a
RPUSH queue x y z # 右侧插入
LPOP queue # 弹出左边元素
RPOP queue
LRANGE queue 0 -1 # 查看全部
LINDEX queue 1 # 按下标取值
LREM queue 2 a # 删除两个值为 a 的元素
BLPOP queue 10 # 阻塞左弹,超时 10 秒(0 表示永久阻塞)
BRPOPLPUSH src dest 10 # 阻塞右弹并左推到另一个列表
数据模型及应用
| 数据结构 | 命令组合 | 应用场景 |
|---|---|---|
| 栈(Stack) | LPUSH + LPOP | 最新消息、撤销操作 |
| 队列(Queue) | LPUSH + RPOP | 异步任务、订单处理 |
| 阻塞队列 | LPUSH + BRPOP | 简易消息队列(生产者-消费者) |
| 有限列表 | LTRIM 修剪 | 保留最近 N 条日志 |
3.2底层原理
Redis 3.2 之后,列表底层使用 quicklist——一个由 ziplist 组成的双向链表。
- 每个 ziplist 存放一段连续数据,减少内存碎片。
- 两端插入是 O(1),中间插入/删除是 O(N)。
注意事项 - 容量上限:2^32 - 1 个元素(约 42 亿),但注意 大 key 问题。
- 不要用 LRANGE 取超大范围的元素,会阻塞主线程。
- 阻塞命令 BLPOP 等可能造成客户端假死,需合理设置超时。
四、集合(Set)——无序且唯一
Set是一个无序的字符串集合,支持交、并、差集运算
4.1常用命令
SADD tags "redis" "database"
SMEMBERS tags
SISMEMBER tags "redis"
SCARD tags # 元素个数
SREM tags "database"
SRANDMEMBER tags 2 # 随机取 2 个(不删除)
SPOP tags 1 # 随机弹出 1 个(删除)
4.2集合运算(用于数据分析)
SINTER set1 set2 # 交集
SUNION set1 set2 # 并集
SDIFF set1 set2 # 差集(属于 set1 不属于 set2)
# 并存储到新集合
SINTERSTORE new_set set1 set2
4.3经典案例
- 微信抽奖小程序
- 参与:SADD lottery:20250101 user:1001
- 查看所有参与者:SMEMBERS lottery:20250101
- 抽 3 名中奖者(允许重复抽):SRANDMEMBER lottery:20250101 3
- 抽 3 名并移除(一人只能中一次):SPOP lottery:20250101 3
- 社交关系
- 我关注的人:SADD follow:roy tom jerry
- 共同关注:SINTER follow:roy follow:loulan
- 我关注的人也关注她(交集判断)
- 点赞/收藏
- 点赞:SADD like:article:1001 user:1002
- 取消:SREM
- 是否点赞:SISMEMBER
- 点赞总数:SCARD
4.4底层实现
- 整数集合 (intset):当所有元素都是整数且数量少时,使用有序整数数组存储,二分查找 O(logN)。
- 哈希表 (dict):元素多或含字符串时转为哈希表,操作 O(1)。
五、有序集合(ZSet)——排行榜之王
ZSet 每个元素关联一个 double 类型的分数(score),按分数从小到大排序。
5.1基本命令
ZADD rank 100 "roy" 90 "loulan" # 添加或更新分数
ZSCORE rank "roy"
ZINCRBY rank 10 "roy" # 增加分数
ZRANGE rank 0 -1 WITHSCORES # 正序,带分数
ZREVRANGE rank 0 -1 WITHSCORES # 倒序(高分在前)
ZRANK rank "roy" # 排名(从 0 开始)
ZREVRANK rank "roy" # 倒序排名
ZREM rank "loulan"
ZCARD rank # 元素个数
ZREVRANGEBYSCORE rank 200 100 WITHSCORES # 按分数区间倒序
5.2聚合操作(合并排行榜)
ZUNIONSTORE week_rank 2 day1_rank day2_rank # 合并两个集合
ZINTERSTORE common 2 set1 set2 # 交集(分数可自定义聚合方式)
5.3实战案例:新闻热搜榜
# 某新闻被点击一次
ZINCRBY hot:20250101 1 "Redis 7 发布"
# 获取今日前十
ZREVRANGE hot:20250101 0 9 WITHSCORES
# 计算七日热点(合并 7 个日榜)
ZUNIONSTORE hot:week 7 hot:20250101 hot:20250102 ... hot:20250107
# 显示七日榜前十
ZREVRANGE hot:week 0 9 WITHSCORES
六、Bitmap —— 位级别的魔法
Bitmap 其实不是新的数据类型,它是 string 类型的一种按位操作模式。一个 bit 只能存 0 或 1。
6.1核心命令
SETBIT sign:user:1001 365 1 # 第 365 天签到(365 位偏移量)
GETBIT sign:user:1001 365
BITCOUNT sign:user:1001 # 统计这位用户一共签到多少天
BITPOS sign:user:1001 1 # 第一个签到日的偏移量
BITOP AND result bit1 bit2 # 位运算(AND/OR/XOR/NOT)
6.2应用案列:每日签到
# 用户 1001 在 2025 年第 100 天签到
SETBIT sign:2025:1001 100 1
# 统计该用户 2025 年签到次数
BITCOUNT sign:2025:1001
# 统计某一天内签到的总用户数
# 需要将所有用户的当天位集中到一个 bitmap 中,然后 BITCOUNT
优点与限制
- 极省内存:存储 1 亿个标志位仅需 12 MB 左右。
- 操作极快:位操作由 CPU 直接支持。
- 偏移量最大 2^32(约 4.29e9),足够日常使用。
七、HyperLogLog —— 基数统计的轻骑兵
HyperLogLog(HLL)是一种概率数据结构,用于统计集合中 不重复元素的数量(基数),误差率约 0.81%,但内存仅需 12KB!
7.1核心命令
PFADD uv:20250101 "ip1" "ip2" "ip1" # 添加元素,重复自动去重
PFCOUNT uv:20250101 # 返回估算的独立访客数
PFMERGE uv:week uv:mon uv:tue ... # 合并多日数据(用于周 UV)
7.2应用场景
- 统计网站的独立访客(UV)
- 统计搜索词的去重数量
- 注册IP去重计数
7.3原理浅析
HLL 使用哈希函数将元素映射成一个二进制串,然后观察前导零的最大长度来估算基数。Redis 实现时用了 16384 个寄存器(桶),每个 6 位,总内存 12KB。
八、GEO——位置即信息
GEO基于ZSet实现,将经纬度编码成score存储
8.1核心命令
GEOADD cities 113.017489 28.200454 "火车站" 112.96903 28.201195 "橘子洲"
GEOPOS cities "火车站" # 获取经纬度
GEODIST cities "火车站" "橘子洲" km # 计算距离
# 查询某个点半径 5km 内的地点
GEORADIUS cities 113.017 28.200 5 km WITHDIST ASC
# 查询某个成员附近的地点
GEORADIUSBYMEMBER cities "火车站" 2 km
# Redis 6.2 引入的新命令
GEOSEARCH cities FROMLONLAT 113.017 28.200 BYRADIUS 5 km
8.2实战:附近商家
GEOADD shops 113.017489 28.200454 "麦当劳" 113.019 28.201 "星巴克"
GEORADIUS shops 113.017 28.200 1 km WITHDIST # 显示 1 公里内的店铺
8.3底层原理
- GEO 将经度、纬度转换为 52 位的 Geohash 整数,作为 ZSet 的 score。
- 因此 GEO 的命令本质是 ZSet 命令的封装,支持范围查询。
九、Stream——强大的消息队列
Redis 5.0 引入 Stream,弥补了 List 和 Pub/Sub 的不足,支持持久化、消费者组、消息确认等。
9.1基本命令
# 添加消息(* 表示自动生成 ID,格式 时间戳-序号)
XADD mystream * name roy age 28
XLEN mystream
XRANGE mystream - + COUNT 2 # 遍历消息
# 创建消费者组,从头部开始消费
XGROUP CREATE mystream group1 0
# 消费者 consumer1 读取消息(> 表示从未消费过的开始)
XREADGROUP GROUP group1 consumer1 COUNT 1 STREAMS mystream >
# 确认消息处理完成
XACK mystream group1 message_id
消费者组特性
- 多个消费者自动分配分区(类似 Kafka)
- 挂起的消息可通过 XPENDING 查看和重试
- 支持 XCLAIM 转移未确认的消息
9.2应用场景
- 简单的订单消息系统
- 日志收集管道
- 实时数据同步
十、总结与选型建议
| 数据结构 | 最优场景 | 不适合场景 |
|---|---|---|
| String | 缓存、计数器、简单存储 | 复杂查询、对象内部操作 |
| Hash | 对象存储、购物车 | 需要单个 field 过期、集群倾斜 |
| List | 队列、栈、最新消息 | 随机访问、超大列表 |
| Set | 唯一性判断、标签、共同关注 | 需要排序 |
| ZSet | 排行榜、范围查询、延迟队列 | 频繁更新分数、内存敏感 |
| Bitmap | 签到、布尔状态、布隆过滤器 | 稀疏数据(浪费空间) |
| HyperLogLog | 海量数据基数统计(UV) | 需要精确计数或获取元素 |
| GEO | LBS 应用、附近的人/店铺 | 复杂的空间运算 |
| Stream | 可靠消息队列、消费者组 | 超高吞吐(不如 Kafka) |
Redis 的世界远不止 CRUD,理解数据结构的内核,才能写出高效、可靠的代码。希望这篇长文能成为你 Redis 进阶之路上的一个路标。如有疑问,欢迎评论区讨论!
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2402_83239589/article/details/161569675



