当前位置:

首页 > 编程开发 > Redis如何实现排行榜及相同积分按时间排序功能

Redis如何实现排行榜及相同积分按时间排序功能

Redis Assistant
Redis Assistant

Redis Assistant 是一款为Redis设计的高效可视化管理与监控工具,提供多种操作模式,高效处理服务器数据。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

立即下载
¥19
Windows/macOS/Linux 2026-08-22
正版软件 Windows macOS软件 Linux 数据库管理工具

在日常的开发中,经常会碰到需要对用户的分值等进行排序,比如在游戏里面需要对战斗力进行排行,在组队活动中需要对各个队伍的贡献值进行排行,在微信中需要对各个好友的步数进行排行,此时一般会选择redis的有序集合对用户的分数进行存储,从而实现排行榜的需求,但是不同的场景排行榜的方式也略有不同,以下根据自己日常的开发进行了一下归纳总结。需求:对组队活动中各个队伍的贡献值进行排行。不考虑积分相同Redis的SortedSet是String类型的有序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。每个元素

本文目录

    在日常的开发中,经常会碰到需要对用户的分值等进行排序,比如在游戏里面需要对战斗力进行排行,在组队活动中需要对各个队伍的贡献值进行排行,在微信中需要对各个好友的步数进行排行,此时一般会选择redis的有序集合对用户的分数进行存储,从而实现排行榜的需求,但是不同的场景排行榜的方式也略有不同,以下根据自己日常的开发进行了一下归纳总结。

    需求:对组队活动中各个队伍的贡献值进行排行。

    不考虑积分相同

    Redis的Sorted Set是String类型的有序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。

    每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

    有序集合的成员是唯一的,但分数(score)却可以重复。

    下面先不考虑积分相同的情况,实现排行榜:

    // 准备数据,其中value为每个队伍的ID,score为队伍的贡献值
    > zadd z1 5 a 6 b 1 c 2 d 10 e
    (integer) 5
    
    // 分页查询排行榜所有的队伍和贡献值,要使用zrevrange,而不是zrange,贡献值越大越排在前面
    > zrevrange z1 0 2 withscores
    1) "e"
    2) "10"
    3) "b"
    4) "6"
    5) "a"
    6) "5"
    
    // 增加某个队伍的贡献值
    > zincrby z1 3 d
    "5"
    > zincrby z1 4 c
    "5"
    
    // 查询排行榜所有的队伍
    > zrevrange z1 0 -1 withscores
     1) "e"
     2) "10"
     3) "b"
     4) "6"
     5) "d"
     6) "5"
     7) "c"
     8) "5"
     9) "a"
    10) "5"
    
    // 查询某个队伍的排名
    > zrevrank z1 d
    (integer) 2

    Redis默认实现是相同分数的成员按字典顺序排序(09,AZ,a~z),上面使用的是zrevrange,所以是倒序,所以相同分数排序就不能根据时间优先来排序。

    积分相同按时间排序,排名唯一

    在上面的实现中,如果两个队伍的贡献值相同,也就是积分值相同,无法根据时间的先后进行排行。

    所以需要设计一个分数 = 贡献值 + 时间戳 ,谁分数大谁排前面,最后还要能根据分数能解析出来贡献值。

    设计1

    使用整型存储分数值,redis中score本身是一个double类型,能精确存储的最大整型数字为2^53=9007199254740992(16位)。而精确到毫秒的时间戳需要13位,此时留给存储贡献值只有3位数了,当前如果时间只要精确到秒,只需要10位,这样留给贡献值就有6位。

    整体设计:高3位表示贡献值,低13位表示时间戳。

    如果我们简单地把score结构由:贡献值 * 10^13 + 时间戳 拼凑,因为分数越大越靠前,而时间戳越小则越靠前,这样两部分的判断规则是相反的,无法简单把两者合成一起成为score。

    但是我们可以逆向思维,可以用同一个足够大的数Integer.MAX减去时间戳,时间戳越小,则得到的差值越大,这样我们就可以把score的结构改为:贡献值 * 10^13 + (Integer.MAX-时间戳),这样就能满足我们的需求了。

    设计2

    由于redis的score值是double类型,可以使用整数部分存储贡献值,小数部分存储时间戳,同样时间戳的部分使用一个最大值减去它。

    这样,整体设计变为:分数=贡献值 + (Integer.MAX-时间戳) * 10^-13

    弊端:由于分数值是由两个变量来计算得出,所以在给队伍增加贡献值时,无法简单的使用之前的zincrby来改变score的值了,这样在并发情况下为队伍增加贡献值就会导致score值不准确。

    错误情况模拟:

    假设现在队伍A的贡献值为10队伍A中的队员X为队伍增加贡献值1,在程序中算出score为11.xxx队伍A中的队员Y为队伍增加贡献值1,在程序中算出score为11.yyy队伍A中的队员X调用redis的zadd命令设置队伍的贡献值为11.xxx队伍A中的队员Y调用redis的zadd命令设置队伍的贡献值为11.yyy最后算出队伍A的贡献值为11,无法保证增加贡献值这一个操作的原子性。

    此时需要借助lua脚本来保证计算和设置贡献值这两个操作的原子性:

    // 其中KEYS[1]为排行榜key,KEYS[2]为队伍ID
    // 其中ARGV[1]为增加的贡献值,ARGV[2]为Integer.MAX-时间戳
    local score = redis.call('zscore', KEYS[1], KEYS[2]) 
    if not(score) then
    	score=0 
    end 
    score=math.floor(score) + tonumber(ARGV[1]) + tonumber(ARGV[2]) 
    redis.call('zadd', KEYS[1], score, KEYS[2]) return 1

    由于redis中无法使用时间函数,所以(Integer.MAX-时间戳) * 10^-13部分由脚本外程序计算好传入。

    分页查询排行榜,查询队伍的排名等功能都可以继续使用上面的命令。

    积分相同按时间排序,并列排名

    所谓并列排行榜,就是存在相同排名情况的排行榜。

    我们期望的结果如下表:

    队伍ID贡献值排名
    a1001
    b992
    c992
    d884
    e875

    当然现实中也有排名不跳过的情况,我这里考虑的是排名跳过的情况。

    redis中score的设计还是采用上面的分数=贡献值 + (Integer.MAX-时间戳) * 10^-13,只是在查询排名时需要进行计算。

    比如要查上表中队伍b的排名,思路如下:

    • 首先查到队伍b的score

    • 再查到跟队伍b的score的整数部分相同(也就是贡献值一样),排在第一个的队伍的value(队伍ID)

    • 根据上一步得到的队伍ID查询此队伍的排名就是队伍b的排名

    使用命令实现上面的步骤如下:

    > zscore 排行榜key teamId
    > zrevrangebyscore(排行榜key, 上一步得到的score+1, 上一步得到的score, limit, 0 , 1)
    > zrevrank(排行榜key, 上一步得到的teamId)

    为了性能考虑,可以使用下面的脚本一次查出来:

    // KEYS[1]表示排行榜key
    // KEYS[2]表示要查询的队伍的ID
    local rank = 0 
    local score = redis.call('zscore', KEYS[1], KEYS[2]) 
    if not(score) then
        score=0 
    else 
        score=math.floor(score) 
        local firstScore = redis.call('zrevrangebyscore', KEYS[1], score+1, score, 'limit', 0, 1) 
        rank=redis.call('zrevrank', KEYS[1], firstScore[1]) 
    end 
    return {score,rank}

    下面附上分页查询排行榜的脚本,假如一页10条,不用下面的脚本需要查询10次上面的脚本,如果连上面的脚本都没有使用的话就要查询30次redis。

    // 排行榜key
    // ARGV[1]分页起始偏移
    // ARGV[2]分页结束偏移
    local list = redis.call('zrevrange', KEYS[1], ARGV[1], ARGV[2], 'withscores') 
    local result={} 
    local i = 1 
    for k,v in pairs(list) do 
        if k%2 == 0 then 
            local teamId = list[k-1] 
            local score = math.floor(v) 
            local firstScore = redis.call('zrevrangebyscore', KEYS[1], score+1, score, 'limit', 0, 1) 
            local rank=redis.call('zrevrank', KEYS[1], firstScore[1]) 
            local l = {teamId=teamId, contributionValue=score, teamRank=rank+1} 
            result[i] = l i = i + 1 
        end 
    end 
    return cjson.encode(result)

    此脚本使用了cjson库,返回的是一个json。

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发
    相关文章 更多
    php72w redis,docker php7安装php-redis
    php72w redis,docker php7安装php-redis

    在Docker环境下为PHP安装Redis扩展:手动编译需下载源码、安装phpize、配置编译并安装,生成redis.so并在php.d目录新建redis.ini加载,重启PHP-FPM;也可直接yum安装php72w-pecl-redis,免手动编译。

    php 如何配置 redis,php 如何设置redis
    php 如何配置 redis,php 如何设置redis

    配置PHP的Redis扩展:先安装Redis服务,从GitHub下载phpredis源码,用phpize编译安装,修改php.ini添加extension=redis.so,重启php-fpm或Apache,通过phpinfo验证并连接Redis。

    c++如何将结构体序列化为Redis兼容的RESP协议格式【深度】
    c++如何将结构体序列化为Redis兼容的RESP协议格式【深度】

    C++结构体无法直接序列化为RESP协议,需手动将字段映射为*N数组嵌套$lenbulkstring形式。整数需转换为字符串并加:前缀,空字符串用$0\r\n,null用$-1\r\n。长度计算需精确到字节数,特别注意二进制安全,同时确保字段顺序与结构体定义一致。

    如何在Golang架构中部署Redict作为高能数据临时存储中转站
    如何在Golang架构中部署Redict作为高能数据临时存储中转站

    Redis作为跨服务临时状态枢纽,需按消息队列、缓存和原子状态三重角色设计。选型用List实现任务分发及延迟重试,Stream用于事件广播与位点回溯。go-redis/v8需配置MinIdleConns、MaxRetries及超时。数据写入应JSON序列化并带type标签、设合理TTL、将关键字段提至顶层。消费通过原子GET+DEL或processedkey

    TP8.0 高并发下库存超卖?Redis 原子递减结合数据库乐观锁控制
    TP8.0 高并发下库存超卖?Redis 原子递减结合数据库乐观锁控制

    在高并发秒杀场景下,利用Redis的减库存原子操作预扣减库存拦截超卖请求,再通过数据库乐观锁(版本号与库存校验)确保最终一致性,异常时回滚Redis库存并借助Lua脚本保证原子性,从而有效防止库存超卖。

    如何在 Go 中利用 go-redis 执行 Lua 脚本优化逻辑
    如何在 Go 中利用 go-redis 执行 Lua 脚本优化逻辑

    在Go中使用go-redis执行Lua脚本可借助原子性解决竞态问题,通过redis.Script封装脚本并自动处理SHA1缓存与重试。需注意KEYS必须为非空切片且所有键在Cluster模式下落在同一slot,ARGV以字符串传入并由tonumber转换。返回值需先检查错误再调用Val(),避免panic。

    TP6.0 基于 Redis SetNX 实现的简单互斥锁机制【并发】
    TP6.0 基于 Redis SetNX 实现的简单互斥锁机制【并发】

    先看一个典型场景:电商秒杀、库存扣减、定时任务重复调度——这些并发冲突在 TP6 应用里屡见不鲜。用 Redis 的 SETNX 做互斥锁,确实是轻量又直接的方案,但“能用”和“稳妥”之间,隔着三层硬功夫:原子加锁、身份校验、合理超时。这三条,差一条就翻车。 很多人第一次上手,直接写 SETNX 加

    TP6.0 如何处理高并发抢购?Redis 原子锁与队列削峰详解【秒杀】
    TP6.0 如何处理高并发抢购?Redis 原子锁与队列削峰详解【秒杀】

    高并发抢购中,Redis的DECR原子操作可安全扣库存;配合think-queue异步削峰,接口仅扣库存和推消息;分布式锁需用SETNXEX加唯一标识,释放时用Lua脚本校验;成功资格后须以数据库事务双写为准,失败时补回Redis库存。

    Golang 实现基于 Redis 的延时任务队列
    Golang 实现基于 Redis 的延时任务队列

    基于Redis的ZSET实现延时队列需解决并发重复消费、失败重试混乱、精度不足及内存爆炸等问题。通过Lua脚本保证取删原子性,使用毫秒级时间戳,对临时错误采用指数退避重试,永久错误记入独立哈希表,并限制最大延迟、定期清理过期任务、使用紧凑序列化格式。

    RedisInsight怎么修改Key的过期时间 Redis操作方法【步骤】
    RedisInsight怎么修改Key的过期时间 Redis操作方法【步骤】

    操作 Redis 的时候,改个 Key 的过期时间其实是个高频场景。特别是做运维排查或者临时调试那会儿,谁也不想反复切命令行窗口、背那些命令参数。好在 RedisInsight 这个图形工具把这事儿做得很顺手——三种方式任选,总有一种合你胃口。 在动手前,先确认一下 Key 当前的 TTL 状态是个

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。

    即将离开本站
    您即将前往第三方网站,请确认是否继续?