找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1879

积分

0

好友

247

主题
发表于 7 小时前 | 查看: 3| 回复: 0

在多年的开发实践中,我发现排行榜功能远非一个简单的 ORDER BY 查询那么简单。面对不同的数据量、实时性要求和并发压力,选择不当的方案很容易让系统陷入性能瓶颈。本文将详细对比五种主流的排行榜实现方案,从最简单的数据库排序到应对千万级并流的流处理架构,帮助你根据自身业务场景做出最合适的技术选型。

方案一:数据库直接排序

适用场景:数据量小(万级以下),实时性要求不高

这是最直观的方案,直接在数据库中通过 ORDER BY 进行排序查询。

示例代码如下:

public List<UserScore> getRankingList(){
    String sql = "SELECT user_id, score FROM user_scores ORDER BY score DESC LIMIT 100";
    return jdbcTemplate.query(sql, new UserScoreRowMapper());
}

优点

  • 实现简单
  • 代码维护成本低
  • 适合数据量小的场景

缺点

  • 数据量大时性能急剧下降
  • 每次查询都需要全表扫描
  • 高并发下数据库压力大

架构流程:用户请求直接由应用服务器处理,并触发一次完整的 MySQL 查询排序,最终返回结果。

数据库直接排序流程图

方案二:缓存 + 定时任务

适用场景:数据量中等(十万级),可以接受分钟级延迟

此方案引入缓存层,通过定时任务定期从数据库计算并更新排行榜数据,查询时直接读取缓存。

示例代码如下:

@Scheduled(fixedRate = 60000) // 每分钟执行一次
public void updateRankingCache(){
    List<UserScore> rankings = userScoreDao.getTop1000Scores();
    redisTemplate.opsForValue().set("ranking_list", rankings);
}

public List<UserScore> getRankingList(){
    return (List<UserScore>) redisTemplate.opsForValue().get("ranking_list");
}

优点

  • 减轻数据库压力
  • 查询速度快(O(1))
  • 实现相对简单

缺点

  • 数据有延迟(取决于定时任务频率)
  • 内存占用较高
  • 排行榜更新不及时

架构流程:应用服务器收到请求后,首先检查 Redis 缓存中是否存在排行榜数据;若存在则直接返回,否则从数据库加载数据并更新缓存后再返回。

缓存加定时任务流程图

方案三:Redis 有序集合 (ZSET)

适用场景:数据量大(百万级),需要实时更新

Redis 的有序集合是其内置数据结构,天生适合实现实时排行榜,支持高效的插入、更新和范围查询。

示例代码如下:

public void addUserScore(String userId, double score){
    redisTemplate.opsForZSet().add("ranking", userId, score);
}

public List<String> getTopUsers(int topN){
    return redisTemplate.opsForZSet().reverseRange("ranking", 0, topN - 1);
}

public Long getUserRank(String userId){
    return redisTemplate.opsForZSet().reverseRank("ranking", userId) + 1;
}

优点

  • 高性能(O(log(N))时间复杂度)
  • 支持实时更新
  • 天然支持分页
  • 可以获取用户排名

缺点

  • 单机 Redis 内存有限
  • 需要考虑 Redis 持久化
  • 分布式环境下需要额外处理

架构流程:用户请求触发 Redis ZSET 操作(如查询排名),同时用户行为(如得分更新)会异步更新 Redis ZSET 中的数据。

Redis有序集合流程图

方案四:分片 + Redis 集群

适用场景:超大规模数据(千万级以上),高并发场景

当单节点 Redis 的内存或性能成为瓶颈时,可以采用分片策略将数据分布到 Redis 集群的多个节点上。

示例代码如下(以 Redisson 客户端为例):

public void addUserScore(String userId, double score){
    RScoredSortedSet<String> set = redisson.getScoredSortedSet("ranking:" + getShard(userId));
    set.add(score, userId);
}

private String getShard(String userId){
    // 简单哈希分片
    int shard = Math.abs(userId.hashCode()) % 16;
    return "shard_" + shard;
}

优点

  • 水平扩展能力强
  • 可以支持超大规模数据
  • 高并发下性能稳定

缺点

  • 架构复杂度高
  • 跨分片查询困难
  • 需要维护分片策略

架构流程:用户请求经过负载均衡后,被分发到 Redis 分片集群中的特定分片进行处理,最后汇总结果返回。

Redis分片集群流程图

方案五:实时计算 + 流处理

适用场景:需要实时更新且数据量极大的社交平台、游戏等

对于数据源为持续不断的行为流(如点赞、点击),且需要极低延迟更新排名的场景,可以采用 Apache FlinkSpark Streaming 等流处理框架。

使用 Apache Flink 示例如下:

DataStream<UserAction> actions = env.addSource(new UserActionSource());

DataStream<Tuple2<String, Double>> scores = actions
    .keyBy(UserAction::getUserId)
    .process(new ProcessFunction<UserAction, Tuple2<String, Double>>() {
        private MapState<String, Double> userScores;

        public void open(Configuration parameters){
            MapStateDescriptor<String, Double> descriptor =
                new MapStateDescriptor<>("userScores", String.class, Double.class);
            userScores = getRuntimeContext().getMapState(descriptor);
        }

        public void processElement(UserAction action, Context ctx, Collector<Tuple2<String, Double>> out){
            double newScore = userScores.getOrDefault(action.getUserId(), 0.0) + calculateScore(action);
            userScores.put(action.getUserId(), newScore);
            out.collect(new Tuple2<>(action.getUserId(), newScore));
        }
    });

scores.keyBy(0)
      .process(new RankProcessFunction())
      .addSink(new RankingSink());

优点

  • 真正的实时更新
  • 可处理超高并发
  • 支持复杂计算逻辑

缺点

  • 架构复杂度高
  • 运维成本高
  • 需要专业团队维护

架构流程:用户行为数据实时进入 Kafka 等消息队列,由 Flink 流处理任务进行实时计算和排名更新,结果写入存储(如 Redis)供排行榜服务查询。

实时计算流处理流程图

方案对比与选择指南

方案 数据量 实时性 复杂度 适用场景
数据库排序 个人项目、小规模应用
缓存+定时任务 中小型应用,可接受分钟级延迟
Redis有序集合 大型应用,需要实时更新排名
分片+Redis集群 超大 超大型应用,超高并发
实时计算+流处理 超大 实时 极高 社交平台/游戏,需毫秒级实时排名

总结

选择排行榜方案时,必须综合考虑数据规模、实时性要求、并发量、团队技术栈及运维能力。对于绝大多数应用,方案二(缓存+定时任务)和方案三(Redis有序集合) 是平衡性能与复杂度的优选。当业务快速增长时,可平滑演进至方案四(分片集群)

方案五(流处理) 虽然能力最强,但架构和运维复杂度陡增,适用于有明确实时需求且技术储备充足的团队。在 云栈社区后端与架构 板块,你可以找到更多关于分布式系统设计与高并发处理的深度讨论。

记住,没有最好的方案,只有最适合当前业务阶段和技术团队的方案。在决策前,充分的性能压测与场景模拟至关重要。




上一篇:亿级视频排行榜架构设计:基于Redis、Kafka与滑动时间窗口的实战方案
下一篇:深入Redis内核:九大数据类型底层原理、高可用架构与性能调优实战指南
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-3 19:56 , Processed in 0.473389 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表