关注

Redis核心数据结构全解:从底层原理到实战应用

前言

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底层原理

  1. Redis 哈希底层使用 ziplist(压缩列表) 或 hashtable(字典)。
  • 当字段少且 value 短时,使用 ziplist 连续存储,节省内存。
  • 字段增多后自动转为 hashtable,查询 O(1)。
  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经典案例

  1. 微信抽奖小程序
  • 参与:SADD lottery:20250101 user:1001
  • 查看所有参与者:SMEMBERS lottery:20250101
  • 抽 3 名中奖者(允许重复抽):SRANDMEMBER lottery:20250101 3
  • 抽 3 名并移除(一人只能中一次):SPOP lottery:20250101 3
  1. 社交关系
  • 我关注的人:SADD follow:roy tom jerry
  • 共同关注:SINTER follow:roy follow:loulan
  • 我关注的人也关注她(交集判断)
  1. 点赞/收藏
  • 点赞:SADD like:article:1001 user:1002
  • 取消:SREM
  • 是否点赞:SISMEMBER
  • 点赞总数:SCARD

4.4底层实现

  1. 整数集合 (intset):当所有元素都是整数且数量少时,使用有序整数数组存储,二分查找 O(logN)。
  2. 哈希表 (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. 极省内存:存储 1 亿个标志位仅需 12 MB 左右。
  2. 操作极快:位操作由 CPU 直接支持。
  3. 偏移量最大 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底层原理

  1. GEO 将经度、纬度转换为 52 位的 Geohash 整数,作为 ZSet 的 score。
  2. 因此 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

消费者组特性

  1. 多个消费者自动分配分区(类似 Kafka)
  2. 挂起的消息可通过 XPENDING 查看和重试
  3. 支持 XCLAIM 转移未确认的消息

9.2应用场景

  1. 简单的订单消息系统
  2. 日志收集管道
  3. 实时数据同步

十、总结与选型建议

数据结构最优场景不适合场景
String缓存、计数器、简单存储复杂查询、对象内部操作
Hash对象存储、购物车需要单个 field 过期、集群倾斜
List队列、栈、最新消息随机访问、超大列表
Set唯一性判断、标签、共同关注需要排序
ZSet排行榜、范围查询、延迟队列频繁更新分数、内存敏感
Bitmap签到、布尔状态、布隆过滤器稀疏数据(浪费空间)
HyperLogLog海量数据基数统计(UV)需要精确计数或获取元素
GEOLBS 应用、附近的人/店铺复杂的空间运算
Stream可靠消息队列、消费者组超高吞吐(不如 Kafka)

Redis 的世界远不止 CRUD,理解数据结构的内核,才能写出高效、可靠的代码。希望这篇长文能成为你 Redis 进阶之路上的一个路标。如有疑问,欢迎评论区讨论!

转载自CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2402_83239589/article/details/161569675

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--