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

相关文章系列:
  之前写过两篇关于排行榜的文章, 不过那是针对游戏平台(类似微信, 手Q等)而言的. 每个用户都有自己的排行榜, 不是全局性的.
  • 社交游戏的排行榜设计和实现(1)
  • 社交游戏的排行榜设计和实现(2)
针对游戏全局排行版的文章
基于redis的排行榜设计和实现
需求说明:
以闯关游戏为例, 其排行榜是基于玩家的闯关个数来进行排名的, 这是合乎合理. 但是若两个玩家得分相同, 这种场景又该如何评定呢?
有一种思路是, 当得分相同时, 以玩家最近一关的破解时间来排定, 既鼓励准确率, 又鼓励速度. 换句话说, score(得分)为第一排序因素, time(破解时间)为第二排序因素.
然而, 如果采用redis的sorted set去实现, 只能设定单一的排序分值score. 这样的话, 二级排序想借助redis, 似乎这条路行不通.
不要灰心, 梦想是有的, 万一实现了呢? ^_^.
是的, 解决方案是有的, 先卖个关子, 且看下面分解. 同时也来分析下, 使用redis较之mysql的优势在哪?
mysql方案:
玩家每闯过一关, 需要记录其在该关的得分记录. 另一方面玩家是存在重复闯关的行为, 因此在设计得分模型中, 该得分记录也帮助去重.
闯关记录数据模型
    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). 统计闯关数和最晚破关时间点
    SELECT userid, COUNT(gateid) AS score, MAX(slove_time) AS last_slove_time
FROM tb_game_record
GROUP BY userid
    2). 进行排序(按得分降序, 时间升序)
    SELECT useid, score, last_slove_time
FROM (...)
ORDER BY score DESC, last_slove_time ASC
    3). 整合的SQL+区间段
  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, 这个是很耗性能, 但确实也无可奈何.
那有没有改进的方案呢? 当然有, 为何不单独引入一个得分表呢?
总得分记录数据模型
  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演变为:
    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)域.
可以简单设定:
score(得分)+time(9999999999-unix的纪元秒, 且固定长度)
    注: unix的纪元秒, 在可预见的将来, 时间长度都是固定长度的, 且取负. score在前, time在后.
比如玩家A的得分为10, 最后闯关的关卡时间为2016/3/30 17:35:47, 则时间戳为:1459330547. 最终为:8540669452=9999999999 - 1459330547.
最后的sorted set的score得分值为: .
    这样就能完美的到达初期设定的二级排序的排行榜需求了.
映射函数设计注意点
这个其实很重要, 因为sorted set的score是double域, 其表达的精度其实是有所限制的. 如果超过这个精度限度, 那么无论几级排序都是没有意义的.
Double 域的表示
    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游戏世界

  

  个人游戏作品集站点(尚在建设中...): www.mmxfgame.com,  也可直接ip访问http://120.26.221.54/.

基于redis排行榜的实战总结的更多相关文章

  1. 想知道谁是你的最佳用户?基于Redis实现排行榜周期榜与最近N期榜

    本文由云+社区发表 前言 业务已基于Redis实现了一个高可用的排行榜服务,长期以来相安无事.有一天,产品说:我要一个按周排名的排行榜,以反映本周内用户的活跃情况.于是周榜(按周重置更新的榜单)诞生了 ...

  2. Java实现排行榜基于Redis

    访问我的博客 前言 排行榜作为互联网应用中几乎必不可少的一个元素,其能够勾起人类自身对比的欲望,从而来增加商品的销量.排行榜的实现方式基本大同小异,大部分都基于 Redis 的有序集合 sorted ...

  3. Tomcat7基于Redis的Session共享实战二

    目前,为了使web能适应大规模的访问,需要实现应用的集群部署.集群最有效的方案就是负载均衡,而实现负载均衡用户每一个请求都有可能被分配到不固定的服务器上,这样我们首先要解决session的统一来保证无 ...

  4. 不用找了,基于 Redis 的分布式锁实战来了!

    Java技术栈 www.javastack.cn 优秀的Java技术公众号 作者:菜蚜 my.oschina.net/wnjustdoit/blog/1606215 前言:在分布式环境中,我们经常使用 ...

  5. 从Redis分布式缓存实战入手到底层原理分析、面面俱到覆盖大厂面试考点

    概述 官方说明 Redis官网 https://redis.io/ 最新版本6.2.6 Redis中文官网 http://www.redis.cn/ 不过中文官网的同步更新维护相对要滞后不少时间,但对 ...

  6. Redis入门到实战

    一.Redis基础 Redis所有的命令都可以去官方网站查看 1.基本命令 keys * 查找所有符合给定模式pattern(正则表达式)的 key .可以进行模糊匹配 del key1,key2,. ...

  7. 《Netty Zookeeper Redis 高并发实战》 图书简介

    <Netty Zookeeper Redis 高并发实战> 图书简介 本书为 高并发社群 -- 疯狂创客圈 倾力编著, 高度剖析底层原理,深度解读面试难题 疯狂创客圈 Java 高并发[ ...

  8. ASP.NET Core WebApi基于Redis实现Token接口安全认证

    一.课程介绍 明人不说暗话,跟着阿笨一起玩WebApi!开发提供数据的WebApi服务,最重要的是数据的安全性.那么对于我们来说,如何确保数据的安全将会是需要思考的问题.在ASP.NET WebSer ...

  9. 二、Redis基本操作——String(实战篇)

    小喵万万没想到,上一篇博客,居然已经被阅读600次了!!!让小喵感觉压力颇大.万一有写错的地方,岂不是会误导很多筒子们.所以,恳请大家,如果看到小喵的博客有什么不对的地方,请尽快指正!谢谢! 小喵的唠 ...

随机推荐

  1. [移动端]rem适配

    原理:给html根节点设置一个基础font-size值,然后页面的所有元素布局均相对于该font-size值采用rem单位设定.font-size的取值通过js计算. 但字体不用rem单位,原因如下: ...

  2. php翻页

    <?php$conn = mysql_connect("localhost","root","") or die("连接数据 ...

  3. MFC之鼠标消息处理

    今天学了点MFC的鼠标处理.用鼠标处理编写了一个小程序.在文本窗口内,绘制鼠标移动轨迹,当按下CTRL键时鼠标将变成十字,并且填充为蓝色的矩形. 第一步:建立单文的MFC程序,添加类CMouseDem ...

  4. 《AngularJS权威教程》中关于指令双向数据绑定的理解

    在<AngularJS权威教程>中,自定义指令和DOM双向数据绑定有一个在线demo,网址:http://jsbin.com/IteNita/1/edit?html,js,output,具 ...

  5. Visro 应用的前端模板工具介绍 -JsRender

    1.什么是JsRender: JsRender是一款JavaScript模版引擎,是具有简单直观,功能强大,可扩展的,早期版本是基于JQUERY 写的,后来作者重构了,就不再依赖JQUERY了. 它的 ...

  6. Primitive JS completion of AJAX

    Firstly , let us explain XMLHttpRequest open(), send(), readyState 1. open(method, url, async, user, ...

  7. C++小项目:directx11图形程序(二):systemclass

    先上代码: systemclass.h #pragma once #include"graphicsclass.h" const bool FULLSCREEN = true; c ...

  8. hdu 1159, LCS, dynamic programming, recursive backtrack vs iterative backtrack vs incremental, C++ 分类: hdoj 2015-07-10 04:14 112人阅读 评论(0) 收藏

    thanks prof. Abhiram Ranade for his vedio on Longest Common Subsequence 's back track search view in ...

  9. 曲线拟合的最小二乘法(基于OpenCV实现)

    1.原理 在现实中经常遇到这样的问题,一个函数并不是以某个数学表达式的形式给出,而是以一些自变量与因变量的对应表给出,老师讲课的时候举的个例子是犯罪人的身高和留下的脚印长,可以测出一些人的数据然后得到 ...

  10. eap-tls

    eap-tls       文件路径 用途 示例 备注 #gedit /usr/local/etc/raddb/sites-available/default #gedit /usr/local/et ...