redis 集合排重_使用Redis的有序集合实现排行榜功能

2023-10-27

游戏中存在各种各样的排行榜,比如玩家的等级排名、分数排名等。玩家在排行榜中的名次是其实力的象征,位于榜单前列的玩家在虚拟世界中拥有无尚荣耀,所以名次也就成了核心玩家的追求目标。

一个典型的游戏排行榜包括以下常见功能:

能够记录每个玩家的分数;

能够对玩家的分数进行更新;

能够查询每个玩家的分数和名次;

能够按名次查询排名前N名的玩家;

能够查询排在指定玩家前后M名的玩家。

更进一步,上面的操作都需要在短时间内实时完成,这样才能最大程度发挥排行榜的效用。

由于一个玩家名次上升x位将会引起x+1位玩家的名次发生变化(包括该玩家),如果采用传统数据库(比如MySQL)来实现排行榜,当玩家人数较多时,将会导致对数据库的频繁修改,性能得不到满足,所以我们只能另想它法。

Redis作为NoSQL中的一员,近年来得到广泛应用。与Memcached相比,Redis拥有更多的数据类型和操作接口,具有更大的适用范围,其中的有序集合(sorted set,也称为zset)就非常适合于排行榜的构建。下面简要总结一下。

1. Redis的安装

Ubuntu下安装Redis非常简单,执行如下命令即可:

$ sudo apt-get install redis-server

安装完毕,运行命令行客户端redis-cli就可以访问本地redis服务器。

$ redis-cli

redis 127.0.0.1:6379>

如果要使用最新版本,需要到Redis官网(redis.io)下载最新的代码自行编译,步骤略。

2. ZSet的常用命令

有序集合首先是集合,其成员(member)具有唯一性,其次,每个成员关联了一个分数(score),使得成员可以按照分数排序。关于有序集合的介绍见redis.io/topics/data…,其命令见redis.io/commands#so…。

下面介绍几个能用于排行榜的命令。

假设lb为排行榜名称,user1、user2等为玩家唯一标识。

1) zadd——设置玩家分数

命令格式:zadd 排行榜名称 分数 玩家标识 时间复杂度:O(log(N))

下面设置了4个玩家的分数,如果玩家分数已经存在,则会覆盖之前的分数。

> redis 127.0.0.1:6379> zadd lb 89 user1

> (integer) 1

> redis 127.0.0.1:6379> zadd lb 95 user2

> (integer) 1

> redis 127.0.0.1:6379> zadd lb 95 user3

> (integer) 1

> redis 127.0.0.1:6379> zadd lb 90 user4

> (integer) 1

2) zscore——查看玩家分数

命令格式:zscore 排行榜名称 玩家标识 时间复杂度:O(1)

下面是查看user2这个玩家在lb排行榜中的分数。

redis 127.0.0.1:6379> zscore lb user2

“95”

3) zrevrange——按名次查看排行榜

命令格式:zrevrange 排行榜名称 起始位置 结束位置 [withscores] 时间复杂度:O(log(N)+M)

由于排行榜一般是按照分数由高到低排序的,所以我们使用zrevrange,而命令zrange是按照分数由低到高排序。

起始位置和结束位置都是以0开始的索引,且都包含在内。如果结束位置为-1则查看范围为整个排行榜。

带上withscores则会返回玩家分数。

下面为查看所有玩家分数。

> redis 127.0.0.1:6379> zrevrange lb 0 -1 withscores

> 1) “user3”

> 2) “95”

> 3) “user2”

> 4) “95”

> 5) “user4”

> 6) “90”

> 7) “user1”

> 8) “89”

下面为查询前三名玩家分数。

> redis 127.0.0.1:6379> zrevrange lb 0 2 withscores

> 1) “user3”

> 2) “95”

> 3) “user2”

> 4) “95”

> 5) “user4”

> 6) “90”

4) zrevrank——查看玩家的排名

命令格式:zrevrank 排行榜名称 玩家标识 时间复杂度:O(log(N))

与zrevrange类似,zrevrank是以分数由高到低的排序返回玩家排名(实际返回的是以0开始的索引),对应的zrank则是以分数由低到高的排序返回排名。

下面是查询玩家user3和user4的排名。

> redis 127.0.0.1:6379> zrevrank lb user3

> (integer) 0

> redis 127.0.0.1:6379> zrevrank lb user1

> (integer) 3

5) zincrby——增减玩家分数

命令格式:zincrby 排行榜名称 分数增量 玩家标识 时间复杂度:O(log(N))

有的排行榜是在变更时重新设置玩家的分数,而还有的排行榜则是以增量方式修改玩家分数,增量可正可负。如果执行zincrby时玩家尚不在排行榜中,则认为其原始分数为0,相当于执行zdd。

下面将user4的分数增加6,使其名次上升到第一位。

> redis 127.0.0.1:6379> zincrby lb 6 user4

> “96”

> redis 127.0.0.1:6379> zrevrange lb 0 -1 withscores

> 1) “user4”

> 2) “96”

> 3) “user3”

> 4) “95”

> 5) “user2”

> 6) “95”

> 7) “user1”

> 8) “89”

6) zrem——移除某个玩家

命令格式:zrem 排行榜名称 玩家标识 时间复杂度:O(log(N))

下面移除玩家user4。

> redis 127.0.0.1:6379> zrem lb user4

> (integer) 1

> redis 127.0.0.1:6379> zrevrange lb 0 -1 withscores

> 1) “user3”

> 2) “95”

> 3) “user2”

> 4) “95”

> 5) “user1”

> 6) “89”

7) del——删除排行榜

命令格式:del 排行榜名称

排行榜对象在我们首次调用zadd或zincrby时被创建,当我们要删除它时,调用redis通用的命令del即可。

> redis 127.0.0.1:6379> del lb

> (integer) 1

> redis 127.0.0.1:6379> get lb

> (nil)

3. 相同分数问题

免费的方案总有那么一些不完美。从前面的例子我们可以看到,user2和user3具有相同的分数,但在按分数逆序排序时,user3排在了user2前面。而在实际应用场景中,我们更希望看到user2排在user3前面,因为user2比user3先加入排行榜,也就是说user2先到达该分数。

但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,但目前只能是幻想,除非自己改其源码。

既然Redis也不能完美解决排行榜问题,那最终是不是有必要自己实现一个专门的排行榜数据结构呢?毕竟实际应用中的排行榜有很多可以优化的地方,比玩家呈金字塔分布,越是低分段玩家数量越多,同一分数拥有大量玩家,玩家增加一分都可能超越很多玩家,这就为优化提供了可能。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

redis 集合排重_使用Redis的有序集合实现排行榜功能 的相关文章

  • u盘装系统

    1 用ultraiso将ios写入u盘 2 U盘插入电脑 3 开机狂按某键进入 boot启动页面 4 选择该u盘 enter回车确认安装
  • ag-grid表格基本使用方法-React版本

    AG表格基本用法及Api 在要使用的项目中 首次使用需要引入相关组件包 注 项目中所有组件都是封装之后的 引入方式如下 import Table from pkg common table 引入完成后 在view层需要用到表格的地方直接放入
  • Vue3的filter过滤器代替方法

    Vue3的过滤器代替方法 前言 一 使用步骤 1 vue2的filter 2 vue3的computed 总结 前言 最近博主从vue2转到vue3 惊奇的发现vue2里面的filter在vue3中不再支持 官方建议用计算属性或方法代替过滤
  • 现代博弈论与多智能体强化学习系统

    如今 大多数人工智能 AI 系统都是基于处理任务的单个代理 或者在对抗模型的情况下 是一些相互竞争以改善系统整体行为的代理 然而 现实世界中的许多认知问题是大群人建立的知识的结果 以自动驾驶汽车场景为例 任何座席的决策都是场景中许多其他座席
  • 遭遇难缠的病毒群ntldr.exe和c0nime.exe等,可杀

    据当事人说中毒后就上不了网了 我没有确认 每个硬盘分区下都有 Autorun inf和ntldr exe C Windows System 目录 下有 c0nime exe 那是数字0不是字母o 等若干个可疑的可执行文件 硬盘上N多exe可
  • Vue模板语法

    Vue js使用了基于HTML的模板语法 允许开发者声明式地将DOM绑定至底层Vue实例的数据 所有Vue js的模板都是合法的HTML 所以能够被遵循规范的浏览器和HTML解析器解析 在底层的实现上 Vue将模板编译成虚拟DOM渲染函数
  • Andorid与其他操作系统的知识

    安卓用的是LINUX的内核 利用LINUX的几个库 应用层运行JAVA虚拟机上 这点和iPhone很想 只不过 iphone是基于unix系统 是微内核结构 同样运行在java虚拟机上 所以 安卓只是一个linux的衍生系统 是LINUX的
  • Opecv检测多个圆形(霍夫圆检测,轮廓面积筛选,C/C++)

    主要是利用霍夫圆检测 面积筛选等完成多个圆形检测 具体代码及结果如下 第一部分是头文件 common h pragma once include
  • Springboot+Mybatis中typeAliasesPackage正则扫描实现

    mybatis默认配置typeAliasesPackage是不支持正则扫描package的 因此需要手动继承org mybatis spring SqlSessionFactoryBean 自己实现正则扫描 方法和传统的spring myb
  • uniapp封装请求失败的提示

    当数据请求失败之后 经常需要调用uni showToast 方法来提示用户 可以在全局封装一个uni showMsg 方法 来简化uni showToast 方法的调用 在main js中 将自定义的 showMsg 方法挂载到uni对象上
  • JSONObject 获取全部键(键值对)

    JSONObject也是迭代器 故可使用Itear的方式进行获取全部键 主要是针对不确定键的情况下使用 直接上代码 JSONObject jsonObject new JSONObject response response 为str It
  • LeGO-LOAM 源码阅读笔记(imageProjecion.cpp)

    LeGO LOAM是一种在LOAM之上进行改进的激光雷达建图方法 建图效果比LOAM要好 但是建图较为稀疏 计算量也更小了 本文原地址 wykxwyc的博客 github注释后LeGO LOAM源码 LeGO LOAM NOTED 关于代码
  • 毕业设计-基于机器学习的入侵检测技术研究

    目录 前言 课题背景和意义 实现技术思路 一 基于机器学习的入侵检测模型 二 基于梯度下降树不同粒度特征的入侵检测 实现效果图样例 最后 前言 大四是整个大学期间最忙碌的时光 一边要忙着备考或实习为毕业后面临的就业升学做准备 一边要为毕业设
  • lua-nginx-module

    Name ngx http lua module Embed the power of Lua into Nginx HTTP Servers This module is a core component of OpenResty If
  • 一款超级简洁的个人博客系统搭建教程(附源码)

    开发环境 IDEA jdk1 8 mysql8 33 开发框架 springboot 1 首先 确保已安装 Git 和 IntelliJ IDEA 如果你还没有安装 Git 请前往官网下载并安装 Git 2 打开 IntelliJ IDEA
  • 线程生命周期与线程状态

    每个Java程序都有一个主线程 即main 方法对应的线程 要实现多线程 必须在主线程中创建新的线程 每个线程要经历新生 new 就绪 runnable 运行 running 阻塞 blocked 死亡 dead 五种状态 线程从新生到死亡
  • js文件中再引入js文件的方法

    原文地址 http hi baidu com maojianlw item f0cfc9dcefc8ac12e0f46fe0 在我们的网站项目中 经常会出现这种场景 我们有一个或几个通用的js代码文件 比如专门进行字符串处理的string
  • sqli-labs/Less-27

    这一关 首先我们先去判断一下注入类型是否为数字型 然后我担心空格被过滤 所以将空格都换成了 0b 输入如下 id 1 0band 0b1 2 回显如下 从这可以看出逻辑运算符是没有被过滤掉的 并且这个注入点属于字符型注入 然后判断是单引还是
  • 详解Scrapy Cluster中Kafka与Redis的消息生产和消费

    相对于Scrapy框架 增加了Kafka和Redis模块的Scrapy Cluster要复杂的多 因此要搞清楚各大模块之间是如何工作的 就至关重要了 在Scrapy Cluster框架中 有三大系统模块 Kafka Redis Scrapy

随机推荐

  • type-c 介绍

    自从Apple发布了新MacBook 就一堆人在说USB Type C 我来从硬件角度解析下这个USB Type C 顺便解惑 特色 尺寸小 支持正反插 速度快 10Gb 这个小是针对以前电脑上的USB接口说的 实际相对android机上的
  • 七夕 H5小游戏,人脸融合搭载颜值评分

    七夕节因为其独特的文化属性成为各家大显神通的战场 人工智能时代 AI 七夕的玩法也越来越多 人脸融合 颜值评分这些有趣的黑科技都多少与大家见过面 但是将两个有趣的点结合到一起还是第一次 七夕 x H5小游戏 x 黑科技 近日 旷视科技的Fa
  • 揭开正则表达式的神秘面纱

    正则表达式30分钟入门教程 http deerchao net tutorials regex regex htm 揭开正则表达式的神秘面纱 http www regexlab com zh regref htm 原创文章 转载请保留或注明
  • angular:ng-star-inserted作用

    参考 javascript Angular 5 adds ng star inserted in some classes what is that Stack Overflow BrowserAnimationsModule来使用的 控制
  • chatgpt赋能python:如何用Python输出HelloWorld?

    如何用Python输出Hello World 作为初学者入门Python编程 输出Hello World是一个最基本的练习 那么 究竟该如何用Python输出Hello World呢 环境准备 在开始之前 需要先安装Python 并确保环境
  • (亲试有效)u盘制作启动盘后空间容量变小解决方法

    问题 大家有可能使用U盘来制作启动盘的需要 但是使用过来发现U盘的空间容量变小了 1G 2G 4G 8G 16G等变成了几百M都有可能 但是无论你再怎么格式化 还是找不回原来的空间容量 怎么办呢 自己经历过 亲身体验有效 方法简单 所以写出
  • 百度交易中台之账房系统架构浅析

    导读 百度交易中台作为集团移动生态战略的基础设施 面向收银交易与清分结算场景 为赋能业务提供高效交易生态搭建 目前支持百度体系内多个产品线 主要包含 小程序 地图打车 百家号 招财猫 好看视频等 本文主要介绍了百度交易中台的商户财务对账相关
  • 关于获取时间戳函数gettimeofday的用法小结

    Linux下gettimeofday函数 2020年6月8日16点33分 函数头文件及原型为 include
  • 常用LaTex指令

    目录 表格 跨行 列 表格 图片 双栏图片 单栏图片 多图 左中右 字体 加粗 斜体 公式 加粗 向量 花体 只适用于大写字母 引用参考文献 引用图片 表格 公式等 脚注 行号 单栏 双栏 格式 去除页码 表格 跨行 列 表格 begin
  • idea 使用Maven 建web项目模板选择

    1 选择模板 2 修改pom xml文件 3 完善目录 需要添加的目录 4 修改web xml 版本 最后修改web xm名即可 完成
  • kafka入门案例

    来源 我是码农 转载请保留出处和链接 本文链接 http www 54manong com id 1228 Conumer demo1 java内容如下 package com lenovo kafka demo import org ap
  • QT 设置样式的两种方式

    1 通过直接加载样式表文件 若现为整个APP加载同一个样式表文件 可直接读取整个qss文件 然后QApplication 设置样式表的成员函数最终继承自父类QGuiApplication 的类对象执行a setStyleSheet cons
  • 星星之火-59:ETSI与FCC在5GHz非授权频谱LAA上要求的差异

    目录 1 ETSI与FCC概述 2 ETSI与FCC在5GHz非授权频谱的比较 2 1 频谱概述 2 2 可用信道数的差异 2 3 基站的发送功率的差异 2 3 雷达信号规范的差异 1 ETSI与FCC概述 ETSI 欧洲电信标准化协会 E
  • golang中的类型断言

    goLang有类型转换 类型断言 类型切换 1 接口类型断言 类型断言就是将接口类型的值 x 装换成类型 T 成功则返回 T 的实例 格式为 x T 不安全 会造成panic 程序中断 v x T 不安全 会造成panic 程序中断 v o
  • Java基础如何学扎实的经验之谈

    文章目录 一 知道Java学习的整体框架 Java基础 Java高级 JavaWeb 二 该怎么学习 入门工具 入门书籍推荐 三 怎么边学边敲代码 训练提示 解题方案 操作步骤 参考代码 四 记笔记的方法 五 其他 六 解决问题的能力 首先
  • java string 转小写_Java String toLowerCase()(String转小写)与示例 - Break易站

    Java 字符串 Java String toLowerCase String转小写 与示例 Java字符串toLowerCase 方法中的字符串的所有字符转换为小写字母 有两种类型的toLowerCase 方法 签名 public Str
  • 通俗讲解:什么是Web

    转自 微点阅读 https www weidianyuedu com content 1017370521955 html 简单的说Web就是为用户提供的一种在互联网上浏览信息的服务 Web服务是动态的 可交互的 跨平台的和图形化的 Web
  • vscode安装_VsCode安装shader glsl环境

    在扩展tab中搜索Shader languages support for VS Code sls for vs code插件 2 安装后搜索glsl canvas 并安装 glsl canvas插件 至此环境安装完成 可新建个工程测试 测
  • v-model cannot be used on v-for or v-slot scope variables because they are not writable报错问题解决方案

    报错 错误代码 div div
  • redis 集合排重_使用Redis的有序集合实现排行榜功能

    游戏中存在各种各样的排行榜 比如玩家的等级排名 分数排名等 玩家在排行榜中的名次是其实力的象征 位于榜单前列的玩家在虚拟世界中拥有无尚荣耀 所以名次也就成了核心玩家的追求目标 一个典型的游戏排行榜包括以下常见功能 能够记录每个玩家的分数 能