基于redis排行榜的实战总结
前言:
之前写过排行榜的设计和实现, 不同需求其背后的架构和设计模型也不一样.
平台差异, 有的立足于游戏平台, 为多个应用提供服务, 有的仅限于单个游戏.排名范围差异, 有的面向全局排名, 有的只做朋友圈排名. 实时性差异, 离线统计有之, 实时排名更常见.
不管如何, 本文将结合之前写的网页闯关游戏, 来具体阐述基于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排行榜的实战总结的更多相关文章
- 想知道谁是你的最佳用户?基于Redis实现排行榜周期榜与最近N期榜
本文由云+社区发表 前言 业务已基于Redis实现了一个高可用的排行榜服务,长期以来相安无事.有一天,产品说:我要一个按周排名的排行榜,以反映本周内用户的活跃情况.于是周榜(按周重置更新的榜单)诞生了 ...
- Java实现排行榜基于Redis
访问我的博客 前言 排行榜作为互联网应用中几乎必不可少的一个元素,其能够勾起人类自身对比的欲望,从而来增加商品的销量.排行榜的实现方式基本大同小异,大部分都基于 Redis 的有序集合 sorted ...
- Tomcat7基于Redis的Session共享实战二
目前,为了使web能适应大规模的访问,需要实现应用的集群部署.集群最有效的方案就是负载均衡,而实现负载均衡用户每一个请求都有可能被分配到不固定的服务器上,这样我们首先要解决session的统一来保证无 ...
- 不用找了,基于 Redis 的分布式锁实战来了!
Java技术栈 www.javastack.cn 优秀的Java技术公众号 作者:菜蚜 my.oschina.net/wnjustdoit/blog/1606215 前言:在分布式环境中,我们经常使用 ...
- 从Redis分布式缓存实战入手到底层原理分析、面面俱到覆盖大厂面试考点
概述 官方说明 Redis官网 https://redis.io/ 最新版本6.2.6 Redis中文官网 http://www.redis.cn/ 不过中文官网的同步更新维护相对要滞后不少时间,但对 ...
- Redis入门到实战
一.Redis基础 Redis所有的命令都可以去官方网站查看 1.基本命令 keys * 查找所有符合给定模式pattern(正则表达式)的 key .可以进行模糊匹配 del key1,key2,. ...
- 《Netty Zookeeper Redis 高并发实战》 图书简介
<Netty Zookeeper Redis 高并发实战> 图书简介 本书为 高并发社群 -- 疯狂创客圈 倾力编著, 高度剖析底层原理,深度解读面试难题 疯狂创客圈 Java 高并发[ ...
- ASP.NET Core WebApi基于Redis实现Token接口安全认证
一.课程介绍 明人不说暗话,跟着阿笨一起玩WebApi!开发提供数据的WebApi服务,最重要的是数据的安全性.那么对于我们来说,如何确保数据的安全将会是需要思考的问题.在ASP.NET WebSer ...
- 二、Redis基本操作——String(实战篇)
小喵万万没想到,上一篇博客,居然已经被阅读600次了!!!让小喵感觉压力颇大.万一有写错的地方,岂不是会误导很多筒子们.所以,恳请大家,如果看到小喵的博客有什么不对的地方,请尽快指正!谢谢! 小喵的唠 ...
随机推荐
- js只保留整数,向上取整,四舍五入,向下取整等函数
1.丢弃小数部分,保留整数部分 parseInt(5/2) 2.向上取整,有小数就整数部分加1 Math.ceil(5/2) 3.四舍五入. Math.round(5/2) 4.向下取整 Math.f ...
- Android的RecyclerView
简介 RecyclerView是support-v7中用来替换ListView的组件.RecyclerView 小组件比 ListView 更高级且更具灵活性. 此小组件是一个用于显示庞大数据集的容器 ...
- Erlang&RabbitMQ服务安装配置
RabbitMQ是流行的开源消息队列系统,是AMQP(Advanced Message Queuing Protocol高级消息队列协议)的标准实现,用erlang语言开发.RabbitMQ据说具有良 ...
- J2EE、J2SE、J2ME是什么意思?
本文介绍Java的三大块:J2EE.J2SE和J2ME.J2SE就是Java2的标准版,主要用于桌面应用软件的编程:J2ME主要应用于嵌入是系统开发,如手机和PDA的编程:J2EE是Java2的企业版 ...
- JDK安装与环境变量配置
1.安装JDK 选择安装目录 安装过程中会出现两次 安装提示 .第一次是安装 jdk ,第二次是安装 jre .建议两个都安装在同一个java文件夹中的不同文件夹中.(不能都安装在java文件夹的根目 ...
- Linq中关键字的作用及用法
Linq中关键字的作用及用法 1.All:确定序列中的所有元素是否都满足条件.如果源序列中的每个元素都通过指定谓词中的测试,或者序列为空,则为 true:否则为 false. Demo: 此示例使用 ...
- I had lots of shrapnel in my leg
People were running up to us, and screaming and hitting us, and throwing stones and eggs. "They ...
- html input节点很多 json字符串提交解决方法
遇到一个页面,38个input节点,页面前端写好的,不太容易改成 js框架 容易操作的样式,只能自己想办法一个一个id获取然后 setvalue getvalue(miniui): 38个一个一个写太 ...
- jQuery性能优化
1. 优化选择器执行的速度 优先使用ID与标记选择器 在jQuery中,访问DOM元素的最快方式是通过元素ID号,其次是通过元素的标记.因为前者源于JavaScript中的document.getEl ...
- Liunx的各种小指令
tsshutdown -y ----关闭所有服务tsboot -g COMMON ----启动公共服务tsboot -g ETXX ----启动XX服务 tsma -e oet1 -l 172.X ...