基于redis排行榜的实战总结

news/2025/2/13 15:52:10/

前言:
  之前写过排行榜的设计和实现, 不同需求其背后的架构和设计模型也不一样.
  平台差异, 有的立足于游戏平台, 为多个应用提供服务, 有的仅限于单个游戏.排名范围差异, 有的面向全局排名, 有的只做朋友圈排名. 实时性差异, 离线统计有之, 实时排名更常见.
  不管如何, 本文将结合之前写的网页闯关游戏, 来具体阐述基于redis排行榜的实战过程.

相关文章系列:之前写过两篇关于排行榜的文章, 不过那是针对游戏平台(类似微信, 手Q等)而言的. 每个用户都有自己的排行榜, 不是全局性的.• 社交游戏的排行榜设计和实现(1)• 社交游戏的排行榜设计和实现(2) 针对游戏全局排行版的文章• 基于redis的排行榜设计和实现 
需求说明:以闯关游戏为例, 其排行榜是基于玩家的闯关个数来进行排名的, 这是合乎合理. 但是若两个玩家得分相同, 这种场景又该如何评定呢?有一种思路是, 当得分相同时, 以玩家最近一关的破解时间来排定, 既鼓励准确率, 又鼓励速度. 换句话说, score(得分)为第一排序因素, time(破解时间)为第二排序因素.然而, 如果采用redis的sorted set去实现, 只能设定单一的排序分值score. 这样的话, 二级排序想借助redis, 似乎这条路行不通.不要灰心, 梦想是有的, 万一实现了呢? ^_^.是的, 解决方案是有的, 先卖个关子, 且看下面分解. 同时也来分析下, 使用redis较之mysql的优势在哪?
mysql方案:玩家每闯过一关, 需要记录其在该关的得分记录. 另一方面玩家是存在重复闯关的行为, 因此在设计得分模型中, 该得分记录也帮助去重.• 闯关记录数据模型
1
2
3
4
5
6
7
8
CREATE TABLE IF NOT EXISTS `tb_game_record` (
   `id`  int (11) NOT NULL AUTO_INCREMENT,
   `userid` varchar(32) NOT NULL COMMENT  '用户标识' ,
   `gateid`  int (11) NOT NULL COMMENT  '关卡编号' ,
   `slove_time` bigint(20) NOT NULL COMMENT  '解决时间点' ,
   PRIMARY KEY (`id`),
   UNIQUE KEY `userid` (`userid`,`gateid`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8; 
    注: userid表示用户, gateid为关卡编号, slove_time为解决的时间点. userid+gateid是联合唯一索引, 用于去重.根据单纯的依赖这个数据表的设计, top-n查询会如何演化.排行榜查询类似于Top-N, 其SQL表达有些复杂, 为一个嵌套的子查询.1). 统计闯关数和最晚破关时间点
1
2
3
SELECT userid, COUNT(gateid) AS score, MAX(slove_time) AS last_slove_time
FROM tb_game_record
GROUP BY userid
    2). 进行排序(按得分降序, 时间升序)
1
2
3
SELECT useid, score, last_slove_time
FROM (...)
ORDER BY score DESC, last_slove_time ASC
    3). 整合的SQL+区间段
1
2
3
4
5
6
7
8
SELECT userid, score, last_slove_time
   FROM (
       SELECT userid, COUNT(gateid) AS score, MAX(slove_time) AS last_slove_time
       FROM tb_game_record
       GROUP BY userid
   ) t
   ORDER BY score DESC, last_slove_time ASC
   LIMIT ?, ?
    总的来说, 还是比较顺利的, 但是性能如何呢? 我们来做一下explain评估.

  

    子SQL使用到filesort, 这个是很耗性能, 但确实也无可奈何.那有没有改进的方案呢? 当然有, 为何不单独引入一个得分表呢?• 总得分记录数据模型
1
2
3
4
5
6
7
8
CREATE TABLE IF NOT EXISTS `tb_game_score` (
     `id`  int (11) NOT NULL AUTO_INCREMENT,
     `userid` varchar(32) NOT NULL,
     `score`  int (11) NOT NULL,
     `last_slove_time` bigint(20) NOT NULL,
     UNIQUE KEY `id` (`id`),
     UNIQUE KEY `userid` (`userid`)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    注: score为记录的userid总得分.每当玩家破解一关的时候, 就自动添加一, 虽然有所写消耗, 但对于查询top-n, 则帮了很大的忙.TOP-N的查询SQL演变为:
1
2
3
SELECT userid, score, last_slove_time
FROM tb_game_score
ORDER BY score DESC, last_slove_time ASC
    如果使用explain进行sql分析:

  

    虽然也使用到了filesort, 但其数据规模却比tb_game_record少了一个数量级.当然它也引入了数据一致性的风险, 因此更新的时候需要做事务上的保护.
redis+mysql方案:引入总得分记录表, 在查询上还是有一定性能损失的. redis被誉为内存数据结构服务器, 能否代替mysql+cache的功能呢?至少在排行榜的功能上, 其数据结构sorted set是完全可以满足要求的. 其可以代替得分记录表, ^_^.当然其难点在于二级排序的模型抽象, sorted set只支持一级排序(sorted set的score域为double类型), 所以问题就演变为能否构建一个映射函数, 把二级排序映射为一级排序(double域).幸好在排行榜的需求上, 二级排序(score, time)是可以映射为一级排序的(sorted set的score)域.可以简单设定:
1
score(得分)+time(9999999999-unix的纪元秒, 且固定长度)
    注: unix的纪元秒, 在可预见的将来, 时间长度都是固定长度的, 且取负. score在前, time在后.比如玩家A的得分为10, 最后闯关的关卡时间为2016/3/30 17:35:47, 则时间戳为:1459330547. 最终为:8540669452=9999999999 - 1459330547.最后的sorted set的score得分值为: 108540669452.
    这样就能完美的到达初期设定的二级排序的排行榜需求了.• 映射函数设计注意点这个其实很重要, 因为sorted set的score是double域, 其表达的精度其实是有所限制的. 如果超过这个精度限度, 那么无论几级排序都是没有意义的.Double 域的表示
1
2
3
4
5
6
1bit(符号位)
11bits(指数位)
52bits(尾数位)
value of floating-point = significand x  base  ^ exponent , with sign
(浮点) 数值 =      尾数    ×    底数 ^ 指数,(附加正负号)
    而2^52, 2^52 = 4503599627370496,一共16位,理论上, double的绝对精度为15位.在映射函数中, 切记15位的上限限定. 之前的设定排行榜的排序映射, 总共为12位(2位游戏得分值, 10位unix纪元秒数), 这是满足要求的.
总结:网上对redis sorted set用于排行榜的文章很多, 但真正的案列解说并不多. 可能这种多级排序在应用中, 更常见.
 
公众号&游戏站点:个人微信公众号: 木目的H5游戏世界

http://www.ppmy.cn/news/191594.html

相关文章

【100万用户量“排行榜列表”和“我的排名”功能优化记录】

100万用户量“排行榜列表”和“我的排名”功能优化记录 前言背景:难点:解决过程:mongodb小坑:总结优化过程:最后:参考: 前言背景: 最近做一个预算100万用户量的项目,需要…

凉了呀,面试官叫我设计一个排行榜。

这是why哥的第89篇原创文章 前两天,有一个读者给我发了一张图片。 我问:发什么肾么事了? 于是有了这样的对话: 他发的图,就是微信运动步数排行榜的截图: 其实扯了这么多,这就是个常见的面试场景…

微信小游戏排行榜功能快速开发教程

要做这个好友排行榜.必然要有好友的战绩比分,然后再做排序,最后将数据呈现在UI上 , 可以分为下面几个步骤: 保存每个用户的分数获取好友列表,并获取好友的分数渲染排行榜 保存每个用户的分数 保存每个用户的分数,需要调用微信的云存储API,将用户的分数持久化的存起来 . // 保…

Redis结合业务逻辑实现排行榜

文章目录 前言实现个人总结附加信息缓存问题删除问题排序问题 测试demo 前言 大家好,我又回来了。已经好久没有写博客了,今天要写的排行榜的需求:排名前50,然后标记自己的位置,如果超过就补到后面去。 其实实现很简单…

golang笔记:游戏中排行榜的实现

date: 2017-08-29 title: “golang笔记:游戏中排行榜的实现” draft: false categories: golanggame tags:golanggame thumbnailImagePosition: left 原文地址https://github.com/liyiheng/blog-gen 游戏开发中排行榜经常出现,接触过的排行榜有两种。一种是由玩家挑…

使用Redis构建高效稳定低延迟的排行榜业务

一、业务描述 现有一排行榜业务,数据库中拥有百万级用户,中秋佳节将要来临,用户可以写一篇关于中秋的故事,故事可邀请好友点赞,也可以打赏该故事,现在要挑选出关于中秋话题相关的故事,根…

PyFlink 有状态流处理实例 实时排行榜

01 UDAF 聚合函数的使用 自定义聚合函数(UDAF),将多条记录聚合成一条记录。其输入与输出是多对一的关系,即将多条输入记录聚合成一条输出值。 需要注意的是:当前聚合函数仅在流模式下的 GroupBy 聚合和 Group Window…

排行榜奖励发放

一般来说排行榜奖励都通过邮件来发放,要不就是对于在线用户直接发给用户自身,对于离线用户发到用户的离线邮箱,用户上线可以通过邮件取到。这样做简单直接,易于处理,也不容易出错,但是在用户很多的时候可能…