如何实现排行榜

580 阅读5分钟

1. 排行榜的实现方式

在实现点赞排行榜时,常常使用 Redis 来解决数据存储和访问的需求。这是因为排行榜通常是一个典型的“多读少写”场景,意味着排行榜的读取频率非常高,而更新频率相对较低。

1.1 为什么选择 Redis?

Redis 是一个高性能的内存数据库,支持多种数据结构,特别适合用于这种读取频繁但更新不频繁的场景。具体来说,排行榜的实现依赖于 Redis 的 有序集合(ZSet) 类型。由于 Redis 提供了极高的读写性能,并且支持数据的自动排序,这使得它成为实现排行榜的最佳选择。

2. 排行榜的特点

排行榜有两个显著的特点:

  • 唯一性:一个实体(例如文章、视频等)在排行榜中只能出现一次,不能重复排名。因此,我们需要一个能够保证元素唯一的数据结构。
  • 自动排序:排行榜需要根据点赞数量或其他指标自动排序,展示出排名靠前的内容。因此,我们需要一个能够自动根据分数进行排序的数据结构。

2.1 数据结构的选择

为了满足这两个需求,我们可以选择 Redis 的有序集合(ZSet)。有序集合是一个非常适合排行榜场景的数据结构,它能够根据元素的分值进行自动排序,同时保证每个元素的唯一性。

  • 元素唯一性:ZSet 中的每个成员是唯一的,无法重复。如果一篇文章或视频的点赞数发生变化,它在 ZSet 中的排名会自动更新。
  • 自动排序:ZSet 中的每个成员都有一个分值(score),Redis 会根据这个分值自动进行排序,保证成员按照分值从高到低排序。这样,我们只需要定期更新分值即可保持排行榜的准确性。

3. ZSet 的结构

ZSet 的结构由三个部分组成:

  • Key:排行榜的名称,例如“点赞排行榜”。
  • Member:排行榜的成员,例如视频 ID 或文章 ID。
  • Score:成员的分值,在排行榜中,分值通常是根据点赞数来计算的。

4. 如何使用 ZSet 实现点赞排行榜

在 Redis 中使用 ZSet 实现点赞排行榜时,可以通过以下操作来进行:

  • 添加/更新成员:使用 ZADD 命令来添加或更新一个成员的分值。如果某个视频或文章的点赞数发生变化,可以通过 ZADD 来更新该视频或文章的分值。

    // 使用 Jedis 客户端示例
    Jedis jedis = new Jedis("localhost");
    jedis.zadd("likes_ranking", 100, "video123");  // 为 video123 设定点赞数 100
    
  • 获取排行榜:使用 ZRANGE 命令获取排名前几的成员。可以按分值从高到低进行排序,获取排名靠前的成员。

    Set<String> topVideos = jedis.zrevrange("likes_ranking", 0, 9);  // 获取点赞排行榜前 10 名
    
  • 查询某个成员的排名:使用 ZREVRANK 获取某个成员在排行榜中的排名。

    Long rank = jedis.zrevrank("likes_ranking", "video123");  // 获取 video123 在排行榜中的排名
    

5. 优化建议

在实际应用中,特别是当排行榜数据量非常大的时候,可能需要对 Redis 的配置和使用进行一些优化,例如:

  • 持久化:Redis 提供了 AOF(Append-Only File)和 RDB(Redis Database)两种持久化方式,可以根据需求选择合适的持久化策略,保证数据在 Redis 重启时不会丢失。
  • 内存管理:如果数据量非常庞大,可以设置 Redis 的内存管理策略(如 maxmemory 设置),避免内存溢出。

总结

点赞排行榜通常使用 Redis 的 有序集合(ZSet) 来实现,原因在于 ZSet 能够保证成员唯一性并自动根据分值进行排序,完美适配了“多读少写”的排行榜场景。在实际实现时,可以利用 Redis 的命令如 ZADDZRANGEZREVRANK 来操作排行榜数据。

附录:Zset类型的常用命令

目标:掌握Zset类型的常用命令

  • 理解:像TreeMap,但是是按照V排序

    • K:元素唯一,不会重复的
    • V:Score,按照Score评分做排序
  • 实施
    • zadd:用于添加元素到Zset集合中
  • 语法:zadd K score1 k1 score2 k2 ……
    • zrange:范围查询
  • 语法:zrange K start end [withscores]
    • zrevrange:倒序查询
  • 语法:zrevrange K start end [withscores]
    • zrem:移除一个元素
  • 语法:zrem K k1
    • zcard:统计集合长度
  • 语法:zcard K
  • zscore:获取评分
    • 语法:zscore K k
node1:6379> zadd zset1 10000 hadoop 2000 hive 5999 spark 3000 flink
(integer) 4
node1:6379> zrange zset1 0 -1
1) "hive"
2) "flink"
3) "spark"
4) "hadoop"
node1:6379> zrange zset1 0 -1 withscores
1) "hive"
2) "2000"
3) "flink"
4) "3000"
5) "spark"
6) "5999"
7) "hadoop"
8) "10000"
node1:6379> zrange zset1 0 2 withscores
1) "hive"
2) "2000"
3) "flink"
4) "3000"
5) "spark"
6) "5999"
node1:6379> zrevrange zset1 0 -1
1) "hadoop"
2) "spark"
3) "flink"
4) "hive"
node1:6379> zrevrange zset1 0 2
1) "hadoop"
2) "spark"
3) "flink"
node1:6379> zrevrange zset1 0 2 withscores
1) "hadoop"
2) "10000"
3) "spark"
4) "5999"
5) "flink"
6) "3000"
node1:6379> zadd zset1 4000.9 oozie
(integer) 1
node1:6379> zrange zset1 0 -1
1) "hive"
2) "flink"
3) "oozie"
4) "spark"
5) "hadoop"
node1:6379> zrange zset1 0 -1 withscores
 1) "hive"
 2) "2000"
 3) "flink"
 4) "3000"
 5) "oozie"
 6) "4000.9000000000001"
 7) "spark"
 8) "5999"
 9) "hadoop"
10) "10000"
node1:6379> zrem zset1 oozie
(integer) 1
node1:6379> zrange zset1 0 -1 withscores
1) "hive"
2) "2000"
3) "flink"
4) "3000"
5) "spark"
6) "5999"
7) "hadoop"
8) "10000"
node1:6379> zcard zset1
(integer) 4
node1:6379> zscore zset1 flink
"3000"
node1:6379> 
  • 注意:Redis中不建议存储浮点值,存在精度问题,建议转换为整形存储