Redis 有序集合(sorted set)(长文解析)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

截止目前, 星球 内专栏累计输出 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 统计与查询:ZCARDZRANK

  • ZCARD:返回集合成员总数。
    ZCARD leaderboard  # 返回 2  
    
  • ZRANK:获取成员的排名(从小到大)。
    ZRANK leaderboard "Charlie"  # 返回 1(因为 Alice 是第0位)  
    

三、有序集合的典型应用场景

3.1 实时排行榜

案例:电商平台的商品销量排名。

  • 实现思路
    1. 每次商品销量变化时,用 ZADD 更新其分数(销量值)。
    2. 使用 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)凭借其独特的分数+成员设计,成为解决复杂业务场景的核心工具。通过灵活运用 ZADDZRANGEBYSCORE 等命令,开发者可以高效实现排行榜、优先级队列等需求。理解其底层的跳跃表与哈希表结合机制,也有助于进一步优化性能。

进阶建议

  • 结合 Lua 脚本 实现原子性操作(如同时更新分数并返回旧值)。
  • 探索 Redis 6.2+ 的新特性,如 ZPOPMAX(按分数弹出成员)。

通过本篇文章的学习,读者应能掌握 Redis 有序集合的核心知识,并在实际项目中游刃有余地应用这一强大的数据结构。

最新发布