基本介绍

Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。

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

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

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O (1)。集合中最大的成员数为 2^32 - 1^ (4294967295, 每个集合可存储 40 多亿个成员)。

有序集合首先是集合,其成员(member)具有唯一性,其次,每个成员关联了一个分数(score),使得成员可以按照分数排序。

需求描述

设想在一个游戏中,有上百万的玩家数据,如果现在需要你根据玩家的经验值整理一个前 10 名的排行榜,你会怎么做呢?一般的做法是写一条类似下面这条 sql 语句的方式来获取:

select * from game_socre order by score desc limit 0,20

这种方式在数据量较小的情况下可行,但是在数据量大的情况下查询速度将变慢,特别是还需要联表查询时,速度下降的就更明显了。

实现

这时你可以考虑使用 redis 来实现这个功能。

实现这个功能主要用到的 redis 数据类型是 redis 的有序集合 zset。zset 是 set 类型的一个扩展,比原有的类型多了一个顺序属性。此属性在每次插入数据时会自动调整顺序值,保证 value 值按照一定顺序连续排列。

主要的实现思路是:

1、在一个新的玩家参与到游戏中时,在 redis 中的 zset 中新增一条记录(记录内容看具体的需求)score 为 0

2、当玩家的经验值发生变化时,修改该玩家的 score 值

3、使用 redis 的 ZREVRANGE 方法获取排行榜

返回有序集 key 中,指定区间内的成员。其中成员的位置按 score 值递减 (从大到小) 来排列。具有相同 score 值的成员按字典序的反序排列。除了成员按 score 值递减的次序排列这一点外,ZREVRANGE 命令的其他方面和 ZRANGE 命令一样。

redis 127.0.0.1:6379> ZADD KEY_NAME SCORE1 VALUE1.. SCOREN VALUEN

1、数据准备

2、获取 score 高分 top10 排名 (ZREVRANGE 为降序,ZRANGE 为升序)

3、查看用户 ee 的实际排名 (ZREVRANK 为降序,ZRANK 为升序)、实时分数

进一步需求

需要实现最近的 24 小时用户积分排行榜,并统计前 10 名的玩家和积分

实现

主要的实现思路是:

利用 ZADD 按小时划分添加用户的积分信息,然后用 ZUNIONSTORE 并集实现 24 小时的游戏积分总和,实现 “24 小时排行榜”;(如果有更好的思路,能够在下方留言不吝赐教一下就更好了)

ZUNIONSTORE destination numkeys key [key ...]

Redis Zunionstore 命令计算给定的一个或多个有序集的并集,其中给定 key 的数量必须以 numkeys 参数指定,并     将该并集(结果集)储存到 destination 。

默认情况下,结果集中某个成员的分数值是所有给定集下该成员分数值之和 。

可能碰到的问题

1、相同分数问题

Redis 在遇到分数相同时是按照集合成员自身的字典顺序来排序,这里即是按照”user2″和”user3″这两个字符串进行排序,以逆序排序的话 user3 自然排到了前面。要解决这个问题,我们可以考虑在分数中加入时间戳,计算公式为:

带时间戳的分数 = 实际分数*10000000000 + (9999999999 – timestamp)

timestamp 我们采用系统提供的 time () 函数,也就是 1970 年 1 月 1 日以来的秒数,我们采用 32 位的时间戳(这能坚持到 2038 年),由于 32 位时间戳是 10 位十进制整数(最大值 4294967295);

所以我们让时间戳占据低 10 位(十进制整数),实际分数则扩大 10^10 倍,然后把两部分相加的结果作为 zset 的分数。考虑到要按时间倒序排列,所以时间戳这部分需要颠倒一下,这便是用 9999999999 减去时间戳的原因。

当我们要读取玩家实际分数时,只需去掉后 10 位即可。

初步看起来这个方案还不错,但这里面有两个问题。

第一个问题是小问题,采用秒为时间戳可能区分度还不够,如果同一秒出现两个分数相同的仍然会出现前面的问题,当然我们可以选择精度更高的时间戳,但在实际场景中,同一秒谁排前面已经无关紧要。

第二个问题是大问题,因为 Redis 的分数类型采用的是 double,64 位双精度浮点数只有 52 位有效数字,它能精确表达的整数范围为 - 2^53 到 2^53,最高只能表示 16 位十进制整数(最大值为 9007199254740992,其实连 16 位也不能完整表示)。

这就是说,如果前面时间戳占了 10 位的话,分数就只剩下 6 位了,这对于某些排行榜分数来说是不够用的。我们可以考虑缩减时间戳位数,比如从 2015 年 1 月 1 日开始计时,但这仍然增加不了几位。

或者减少区分度,以分钟、小时来作为时间戳单位。

如果 Redis 的分数类型为 int64,我们就没有上面的烦恼。

说到这里,其实 Redis 真应该再额外提供一个 int64 类型的 ZSet,但目前只能是幻想,除非自己改其源码。

链接:https://mp.weixin.qq.com/s/kK4WHghsg5yt_kcdnVQWNg

PHP+Redis 有序集合实现 24 小时排行榜实时更新的更多相关文章

  1. Redis 有序集合(sorted set),发布订阅,事务,脚本,连接,服务器(三)

    Redis 有序集合(sorted set) Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员. 不同的是每个元素都会关联一个double类型的分数.redis正是通过 ...

  2. Redis 有序集合(sorted set)

    Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员. 不同的是每个元素都会关联一个double类型的分数.redis正是通过分数来为集合中的成员进行从小到大的排序. 有序 ...

  3. redis 有序集合(zset)函数

    redis 有序集合(zset)函数 zAdd 命令/方法/函数 Adds the specified member with a given score to the sorted set stor ...

  4. Redis有序集合

    Redis有序集合类似Redis集合存储在设定值唯一性.不同的是,一个有序集合的每个成员带有分数,用于以便采取有序set命令,从最小的到最大的分数有关. Redis 有序set添加,删除和测试中的O( ...

  5. redis有序集合的使用

    Redis 有序集合(sorted set) Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员. 不同的是每个元素都会关联一个double类型的分数.redis正是通过 ...

  6. redis有序集合数据类型---sortedset

    一.概述 redis有序集合和集合一样,也是string类型元素的集合,且不允许重复的成员. 不同的是每个元素都会关联一个double类型的分数. redis正式通过分数来为集合中的重圆进行从小到大的 ...

  7. 数据结构与算法简记--redis有序集合实现-跳跃表

    跳表 定义 为一个值有序的链表建立多级索引,比如每2个节点提取一个节点到上一级,我们把抽出来的那一级叫做索引或索引层.如下图所示,其中down表示down指针,指向下一级节点.以此类推,对于节点数为n ...

  8. 基于redis有序集合,实现简单的延时任务

    基于redis有序集合,实现简单的延时任务 延时任务的场景很多,开发过程中我们经常会遇到,比如说: 1.订单未付款,5分钟后自动取消,这是电商网站非常普遍的需求: 2.用户创建订单不付款,3分钟后自动 ...

  9. redis(十四):Redis 有序集合(sorted set)

    Redis 有序集合(sorted set) Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员. 不同的是每个元素都会关联一个double类型的分数.redis正是通过 ...

随机推荐

  1. Java类加载顺序练习题

    一.题目 public class Test { public static Test t1 = new Test();// 标记1 // 普通代码块 { System.out.println(&qu ...

  2. 【leetcode】564. Find the Closest Palindrome

    题目如下: 解题思路:既然是要求回文字符串,那么最终的输出结果就是对称的.要变成对称字符串,只要把处于对称位置上对应的两个字符中较大的那个变成较小的那个即可,假设n=1234,1和4对称所以把4变成1 ...

  3. Android Fastboot 与 Recovery 和刷机 千山万水迷了鹿

    1. 首先来看下Android系统的分区:   Android系统的分区.jpg   Android分区解释.png 安卓系统一般把rom芯片分成7个区,如果再加上内置sd卡这个分区,就是8个: hb ...

  4. 基于oracle 的PL/SQL编程 - 存储过程

    接上篇,游标使用的语句,相当于一段匿名的函数,窗口关闭了就不存在了.如果想要窗口关闭了,还能继续执行那段代码,就需要存储过程了: PLSQL是指一个个PLSQL的业务处理过程存储起来进行复用,这些被存 ...

  5. 文字在线中间,CSS巧妙实现分隔线的几种方法

    单个标签实现分隔线: .demo_line_01{ padding: 0 20px 0; margin: 20px 0; line-height: 1px; border-left: 200px so ...

  6. LINK : fatal error LNK1561: 必须定义入口点

    转自VC错误:http://www.vcerror.com/?p=1313 问题描述: 错误:LINK : fatal error LNK1561: 必须定义入口点 解决方法: 详细的解决方法可参考V ...

  7. JVM调优(四)——tomcat远程debug

    JVM调优(四)--tomcat远程debug tomcat远程debug jdwp协议 使用步骤 登录远程服务器,进入tomcat目录,并打开文件: //tomcat/bin/startup.sh ...

  8. DomainObjectUtility

    using System; using System.Collections; using System.Collections.Generic; using System.Collections.S ...

  9. Linux下实现MySQL数据库每天定时自动备份

    使用MySQL自带的备份工具+ crontab 的方式来实现备份 1.查看磁盘挂载信息(选一个容量合适的) #df -h 2.创建备份目录 为了方便,在/home保存备份文件: cd /home/ga ...

  10. nginx 虚拟主机+反向代理+负载均衡

    nginx是一款免费.开源的http服务器,它是由俄罗斯程序设计师开发的,官方测试,nginx能支撑5万的并发量,主要功能有虚拟主机.反向代理和负载均衡等. nginx配置 # 全局块 ... # e ...