Redis 有序集合(sorted set)(长文解析)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;演示链接: http://116.62.199.48:7070 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观
前言
在 Redis 这个强大的内存数据库中,有序集合(Sorted Set)是一个功能丰富且应用场景广泛的底层数据结构。它结合了集合(Set)和列表(List)的特性,允许开发者通过一个独特的成员(member)和一个分数(score)来管理数据。无论是构建实时排行榜、实现优先级队列,还是处理带权重的标签统计,有序集合都能提供高效且灵活的解决方案。本文将从零开始,逐步解析 Redis 有序集合的核心概念、操作命令、实际案例以及性能优化技巧,帮助开发者快速掌握这一重要工具。
一、有序集合(Sorted Set)的基础概念
1.1 什么是有序集合?
Redis 的有序集合是一种键值对结构,其中:
- 键(Key):唯一的字符串标识符,用于定位有序集合。
- 值(Value):由多个**成员(member)和对应的分数(score)**组成。
每个成员是唯一的字符串,而分数是双精度浮点数(double),决定了成员在集合中的排序位置。
形象比喻:
可以将有序集合想象为一个“带优先级的购物清单”。例如,清单中的每个商品(成员)都有一个价格(分数),系统会自动按价格从低到高排列,但允许相同价格的商品并列存在。
1.2 与集合(Set)的区别
虽然有序集合和集合都存储唯一成员,但关键区别在于:
| 特性 | 集合(Set) | 有序集合(Sorted Set) |
|------------------|-----------------------|----------------------------------|
| 成员是否有序 | 无序,随机排列 | 有序,按分数或自定义规则排列 |
| 成员是否有权重 | 无权重 | 每个成员关联一个分数(score) |
示例对比:
- 集合:存储用户 ID 的唯一列表,例如
{"user1", "user2", "user3"}。
- 有序集合:存储用户积分排行榜,例如
member="user1" score=1000,member="user2" score=800
。
二、有序集合的核心操作命令
Redis 提供了丰富的命令来管理有序集合,以下是最常用的操作:
2.1 添加成员:ZADD
语法:
ZADD key score1 member1 score2 member2 ...
功能:向有序集合中添加一个或多个成员,若成员已存在,则根据新分数更新其位置。
示例:
ZADD leaderboard 1000 "Alice" 800 "Bob" 950 "Charlie"
执行后,集合按分数排序为:Alice(1000), Charlie(950), Bob(800)
。
2.2 查询范围:ZRANGEBYSCORE
语法:
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
功能:按分数范围查询成员,支持返回分数和分页。
示例:
ZRANGEBYSCORE leaderboard 800 999 WITHSCORES
返回结果:
1) "Charlie"
2) "950"
3) "Bob"
4) "800"
2.3 删除成员:ZREM
语法:
ZREM key member1 member2 ...
功能:从有序集合中删除指定成员。
示例:
ZREM leaderboard "Bob"
执行后,集合变为:Alice(1000), Charlie(950)
。
2.4 统计与查询:ZCARD
和 ZRANK
- ZCARD:返回集合成员总数。
ZCARD leaderboard # 返回 2
- ZRANK:获取成员的排名(从小到大)。
ZRANK leaderboard "Charlie" # 返回 1(因为 Alice 是第0位)
三、有序集合的典型应用场景
3.1 实时排行榜
案例:电商平台的商品销量排名。
- 实现思路:
- 每次商品销量变化时,用
ZADD
更新其分数(销量值)。 - 使用
ZRANGE
获取前 N 名商品。
- 每次商品销量变化时,用
代码示例:
ZADD product_rank 1500 "iPhone 15" # 初始销量为1500
ZINCRBY product_rank 100 "iPhone 15" # 销量增加100,新分数为1600
ZRANGE product_rank 0 2 WITHSCORES
3.2 带权重的标签统计
案例:社交平台的热门话题统计,按热度排序。
- 实现思路:
- 每个话题(member)的分数(score)代表热度值(如点赞数+分享数)。
- 使用
ZREVRANGE
(降序)获取热门话题。
代码示例:
ZADD trending_topics 8500 "#SummerTravel" 6300 "#TechNews"
ZREVRANGE trending_topics 0 1 WITHSCORES
3.3 优先级队列
案例:任务调度系统,按优先级执行任务。
- 实现思路:
- 任务(member)的分数(score)表示优先级(分数越小优先级越高)。
- 使用
ZRANGEBYSCORE
获取待处理任务。
代码示例:
ZADD task_queue 1 "紧急任务" 3 "普通任务" 2 "中等任务"
ZRANGE task_queue 0 0 WITHSCORES
四、深入理解有序集合的底层原理
4.1 数据结构实现
Redis 的有序集合通过**跳跃表(Skip List)和哈希表(Hash Table)**的组合实现,兼顾高效查询和快速插入/删除:
- 哈希表:用于快速定位成员到其分数和跳跃表节点的指针。
- 跳跃表:按分数排序成员,支持 O(log N) 时间复杂度的范围查询和排名操作。
优点:
- 分数动态更新:成员的分数可随时修改,跳跃表会自动调整位置。
- 范围查询高效:通过跳跃表的层级结构快速定位目标区间。
4.2 分数与成员的唯一性约束
- 成员唯一:若插入已存在的成员,
ZADD
会自动更新其分数。 - 分数可重复:多个成员可以拥有相同的分数,但它们的排列顺序由成员字符串的字典序决定。
示例:
ZADD scores 85 "Alice" 85 "Bob"
ZRANGE scores 0 -1 WITHSCORES
五、性能优化与注意事项
5.1 避免频繁的小批量操作
若需批量插入大量数据,建议使用 ZADD
的多参数形式而非多次单条操作,以减少网络延迟。
5.2 合理控制集合大小
有序集合的内存占用与成员数量成正比。若需长期存储超大规模数据,可考虑分页存储或结合其他数据结构(如 Redis Streams)。
5.3 注意分数的精度限制
分数是双精度浮点数,最大精度为 15 位十进制数。若需更高精度,可将分数存储为字符串并自行处理。
六、总结
Redis 的有序集合(Sorted Set)凭借其独特的分数+成员设计,成为解决复杂业务场景的核心工具。通过灵活运用 ZADD
、ZRANGEBYSCORE
等命令,开发者可以高效实现排行榜、优先级队列等需求。理解其底层的跳跃表与哈希表结合机制,也有助于进一步优化性能。
进阶建议:
- 结合
Lua 脚本
实现原子性操作(如同时更新分数并返回旧值)。 - 探索
Redis 6.2+
的新特性,如ZPOPMAX
(按分数弹出成员)。
通过本篇文章的学习,读者应能掌握 Redis 有序集合的核心知识,并在实际项目中游刃有余地应用这一强大的数据结构。