Redis ZSet 实现排行榜(支持分数相同按时间顺序排序)

文章目录

  • [一、为什么用 ZSet 做排行榜?](#一、为什么用 ZSet 做排行榜?)
  • [二、难点:ZSet 只有一个 score,如何实现"同分按时间排序"?](#二、难点:ZSet 只有一个 score,如何实现“同分按时间排序”?)
  • [三、核心方案:复合 score(Composite Score)](#三、核心方案:复合 score(Composite Score))
  • 四、完整示例(医疗场景:医生接诊排行榜)
  • [五、Redis 命令实现](#五、Redis 命令实现)
  • [六、Java 代码实现(可直接用)](#六、Java 代码实现(可直接用))
  • 七、时间复杂度分析
  • 八、方案优点总结
  • 九、总结

在业务系统中,我们经常会遇到排行榜:

  • 医生接诊量排行榜
  • 护士工作量排行榜
  • 科室服务质量排名
  • 健康管理积分排行榜

这些排行榜通常有两个排序维度:

  1. 业务分数(例如接诊人数、评分、积分)从高到低排序
  2. 分数相同的人,按时间顺序排序(越早出现的排越前)

Redis 的 ZSet 天生适合做排行榜,但它只有一个 score 字段,

不支持"分数 + 时间"多维度排序。

本文介绍一个经过实践验证、业内常用的方案:

用复合 score(Composite Score)在 ZSet 中同时实现分数排序 + 时间排序。


一、为什么用 ZSet 做排行榜?

ZSet 是 Redis 唯一"自带排序"的结构

zset 的底层是"跳表 + hash",具备天然排序能力,复杂度:

操作 时间复杂度
插入 / 更新分数 (ZADD) O(logN)
查某人排名 (ZRANK) O(logN)
查前 N 名 (ZREVRANGE) O(logN + N)

对于实时更新 + 高频读取的排行榜场景非常适合:

  • 医生每接诊一个病人 → 分数变化 → ZINCRBY
  • 页面的排行榜实时展示 → ZREVRANGE

如果使用 MySQL 排序,每次都要 ORDER BY LIMIT,数据量一大系统就吃不消了。


二、难点:ZSet 只有一个 score,如何实现"同分按时间排序"?

ZSet 的规则很简单:

只按 score 排序(score 小在前),

score 相同再按 member 字典序排序。

我们真正想要的排序逻辑是:

  1. 业务分数高的排前面
  2. 分数相同时,时间越早的排前面

但 Redis 只给我们一个 score,所以必须把两个维度合成一个数字。


三、核心方案:复合 score(Composite Score)

公式如下:

复制代码
finalScore = bizScore * FACTOR - timestamp

含义:

  • bizScore * FACTOR:把业务分数放到"高位",确保分数优先排序
  • - timestamp:让时间越早(timestamp 越小)的最终 score 越大,在逆序(ZREVRANGE)时排前面

并且:

我们最后用 ZREVRANGE(从大到小)取排行榜。

为什么使用 "减 timestamp"?

假设我们的排序目标:

  • 分数越高越靠前
  • 同样分数中,时间越早越靠前

因为 ZSet 正序是从"小到大",而我们想要的是"从大到小",所以最终使用:

  • finalScore 越大越靠前
  • 时间越早 (timestamp 越小) → finalScore 越大(因为减得少)

逻辑完全正确。


四、完整示例(医疗场景:医生接诊排行榜)

假设我们做"医生接诊量排行榜",规则如下:

  1. 接诊人数多的医生排前
  2. 接诊人数相同时,"第一次接诊时间早"的医生排前

我们选择:

复制代码
FACTOR = 1,000,000,000(10^9)

确保分数部分远大于时间部分。

示例数据

医生 接诊人数 首次接诊时间戳(秒) finalScore 计算
A 45 1709100000 45×1e9 - 1709100000 = 43,290,900,000
B 45 1709103600 45×1e9 - 1709103600 = 43,290,896,400
C 43 1709107200 43×1e9 - 1709107200 = 41,290,892,800

排行榜结果(ZREVRANGE):

复制代码
1. A(45 分,时间最早)
2. B(45 分,时间较晚)
3. C(43 分)

完全符合业务要求。


五、Redis 命令实现

加入排行榜(ZADD)

bash 复制代码
# A 医生
ZADD doctor:rank 43290900000 doc_A

# B 医生
ZADD doctor:rank 43290896400 doc_B

# C 医生
ZADD doctor:rank 41290892800 doc_C

取前 10 名

bash 复制代码
ZREVRANGE doctor:rank 0 9 WITHSCORES

更新分数

bash 复制代码
ZINCRBY doctor:rank 1 doc_A

(P.S. 如果接诊人数变化,需要重新计算复合 score)


六、Java 代码实现(可直接用)

java 复制代码
public class DoctorRankService {

    private static final long FACTOR = 1_000_000_000L;

    private final Jedis jedis;

    public DoctorRankService(Jedis jedis) {
        this.jedis = jedis;
    }

    public void updateDoctorScore(String doctorId, long bizScore, long timestamp) {
        // 关键:分数 * 大倍数 - 时间戳
        long finalScore = bizScore * FACTOR - timestamp;
        jedis.zadd("doctor:rank", finalScore, doctorId);
    }

    public Set<String> topN(int n) {
        return jedis.zrevrange("doctor:rank", 0, n - 1);
    }
}

七、时间复杂度分析

操作 ZSet(跳表) 说明
更新分数(ZADD/ZINCRBY) O(logN) 适合实时更新
查前 N 名(ZREVRANGE) O(logN + N)
查名次(ZRANK) O(logN)

对比:

  • 用 hash/list 排序 = O(N log N)(应用层排序)
  • 用 MySQL 排序 = O(N log N)(大量读盘、索引开销)

结论:ZSet 速度优势巨大。


八、方案优点总结

1. 只用一个 ZSet 就能实现"分数 + 时间"排序

非常适合需要多维排序的业务。

2. 更新分数是 O(logN),取前 N 名很快

医生接诊量、护士工作量这类实时更新场景特别适合。

3. 可以承载高 QPS 查询

排行榜页面通常访问很频繁,ZSet 用作缓存非常高效。

4. 业务逻辑统一:所有排序规则全靠 finalScore 控制

既灵活又易扩展。


九、总结

在医疗系统中,"实时排行榜"是一个高频且关键的能力:

医生接诊榜、护士执行榜、健康管理积分榜、科室满意度榜都令人熟悉。

Redis 的 ZSet 提供了天然有序结构,非常适合这个场景。

但 ZSet 只有一个 score,我们需要把:

  • 业务分数(分高的排前)
  • 时间顺序(同分时间早的排前)

转化成一个数字,这就是复合 score:

复制代码
finalScore = bizScore * FACTOR - timestamp
相关推荐
qq_192779874 小时前
高级爬虫技巧:处理JavaScript渲染(Selenium)
jvm·数据库·python
u0109272715 小时前
使用Plotly创建交互式图表
jvm·数据库·python
爱学习的阿磊5 小时前
Python GUI开发:Tkinter入门教程
jvm·数据库·python
Leon-zy5 小时前
Redis7.4.5 主备冗余+哨兵模式部署
redis·哨兵模式·主备模式
tudficdew5 小时前
实战:用Python分析某电商销售数据
jvm·数据库·python
sjjhd6526 小时前
Python日志记录(Logging)最佳实践
jvm·数据库·python
Configure-Handler6 小时前
buildroot System configuration
java·服务器·数据库
2301_821369616 小时前
用Python生成艺术:分形与算法绘图
jvm·数据库·python
电商API_180079052477 小时前
第三方淘宝商品详情 API 全维度调用指南:从技术对接到生产落地
java·大数据·前端·数据库·人工智能·网络爬虫
2401_832131957 小时前
Python单元测试(unittest)实战指南
jvm·数据库·python