侧边栏壁纸
博主头像
博客技术 博主等级

行动起来,活在当下

  • 累计撰写 42 篇文章
  • 累计创建 4 个标签
  • 累计收到 3 条评论

目 录CONTENT

文章目录

redis如何实现上亿用户实时积分排行版

Administrator
2025-03-06 / 0 评论 / 0 点赞 / 8 阅读 / 0 字

如何用Redis实现上亿用户的实时积分排行榜。这个问题有两个关键点:用户量非常大实时性要求高。传统的数据库在这种场景下肯定是扛不住的,而Redis的ZSET(有序集合)可以很好地解决这个问题

1. 为什么用Redis的ZSET?

Redis的ZSET是一个非常强大的数据结构,它不仅可以存储数据,还可以根据分数(score)进行排序。每个ZSET中的元素都有一个分数,我们可以通过分数来对元素进行排序。对于积分排行榜来说,用户的积分就是分数,用户的ID就是元素的值。这样,我们就可以很方便地根据积分来排序,快速获取排行榜。

举个例子,假设我们有用户的ID和对应的积分,我们可以这样存储:

  • 用户A,积分100

  • 用户B,积分200

  • 用户C,积分150

public class RedisLeaderboard {
    // ... 省略初始化代码

    // 添加或更新用户积分
    public static void updateUserScore(String userId, double score) {
        try (Jedis jedis = getJedis()) {
            // 使用ZADD命令更新用户积分
            jedis.zadd("leaderboard", score, userId);
        }
    }
}

2. 数据量大了怎么办?

虽然ZSET在小数据量下表现非常好,但当用户量达到上亿级别时,ZSET也会遇到瓶颈。比如,排序操作可能会变慢,Redis的内存占用也会增加,甚至可能导致Redis的延迟和堵塞,影响系统的吞吐量。

那么,如何解决这个问题呢?我们可以采用分桶的策略。

3. 分桶策略

分桶的核心思想是将用户按照积分范围分成多个桶。比如:

  • 积分在1000以上的用户放在一个桶里

  • 积分在500到1000之间的用户放在另一个桶里

  • 积分在0到500之间的用户再放在一个桶里

这样,每个桶里的数据量就会大大减少。当我们需要查询排行榜时,只需要从积分最高的桶里取出前几名用户即可。如果最高分的桶里用户数量不够,我们再从下一个桶里补充。

举个例子,假设我们有三个桶:

  • 桶1:积分 >= 1000

  • 桶2:500 <= 积分 < 1000

  • 桶3:0 <= 积分 < 500

如果我们要查询前10名用户,首先从桶1里取前10名。如果桶1里的用户不足10个,再从桶2里补充,依此类推。

public class RedisLeaderboard {
    // ... 省略初始化代码

    // 根据积分范围获取桶的名称
    private static String getBucketName(double score) {
        if (score >= 1000) {
            return "leaderboard_bucket_1000";
        } else if (score >= 500) {
            return "leaderboard_bucket_500";
        } else {
            return "leaderboard_bucket_0";
        }
    }

    // 添加或更新用户积分(带分桶)
    public static void updateUserScoreWithBucket(String userId, double score) {
        try (Jedis jedis = getJedis()) {
            // 获取桶的名称
            String bucketName = getBucketName(score);
            // 将用户添加到对应的桶中
            jedis.zadd(bucketName, score, userId);
        }
    }
}

4. 分桶的优化

如果某个桶里的用户数量还是太多,我们可以进一步细分。比如,桶1(积分 >= 1000)里的用户数量很大,我们可以把这个桶再拆分成多个子桶:

  • 桶1.1:积分 >= 900

  • 桶1.2:800 <= 积分 < 900

  • 桶1.3:700 <= 积分 < 800

这样,每个子桶里的用户数量会更少,查询效率也会更高。

import java.util.Set;

public class RedisLeaderboard {
    // ... 省略初始化代码

    // 获取排行榜前N名用户
    public static Set<String> getTopNUsers(int n) {
        try (Jedis jedis = getJedis()) {
            // 先尝试从最高分的桶中获取用户
            Set<String> topUsers = jedis.zrevrange("leaderboard_bucket_1000", 0, n - 1);
            if (topUsers.size() < n) {
                // 如果数量不足,从下一个桶中补充
                Set<String> nextBucketUsers = jedis.zrevrange("leaderboard_bucket_500", 0, n - topUsers.size() - 1);
                topUsers.addAll(nextBucketUsers);
            }
            if (topUsers.size() < n) {
                // 如果还不够,从最低分的桶中补充
                Set<String> lowestBucketUsers = jedis.zrevrange("leaderboard_bucket_0", 0, n - topUsers.size() - 1);
                topUsers.addAll(lowestBucketUsers);
            }
            return topUsers;
        }
    }
}

5. 测试代码

public class Main {
    public static void main(String[] args) {
        // 添加一些用户积分
        RedisLeaderboard.updateUserScoreWithBucket("user1", 1200);
        RedisLeaderboard.updateUserScoreWithBucket("user2", 800);
        RedisLeaderboard.updateUserScoreWithBucket("user3", 1500);
        RedisLeaderboard.updateUserScoreWithBucket("user4", 400);
        RedisLeaderboard.updateUserScoreWithBucket("user5", 600);

        // 获取排行榜前3名
        Set<String> topUsers = RedisLeaderboard.getTopNUsers(3);
        System.out.println("Top 3 users: " + topUsers);
    }
}

6. 运行结果

Top 3 users: [user3, user1, user2]

7. 总结

通过Redis的ZSET和分桶策略,我们可以很好地实现上亿用户的实时积分排行榜。ZSET提供了高效的排序功能,而分桶策略则帮助我们解决了大数据量下的性能瓶颈问题。当然,具体的分桶策略可以根据实际业务需求进行调整,比如桶的数量、积分范围等。

0

评论区