范文健康探索娱乐情感热点
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

用Redis搞定游戏中的实时排行榜,附源码

  1. 前言
  前段时间刚为项目(手游)实现了一个实时排行榜功能, 主要特性:  实时全服排名  可查询单个玩家排名  支持双维排序
  数据量不大 , 大致在 1W ~ 50W区间(开服, 合服会导致单个服角色数越来越多).  2. 排行榜分类
  按照排行主体类型划分, 主要分为:  角色  军团(公会)  坦克
  该项目是个坦克手游, 大致情况是每个角色有N辆坦克, 坦克分为多种类型(轻型, 重型等), 玩家可加入一个军团(公会).
  具体又可以细分为:  角色       - 战斗力排行榜(1. 战斗 2.等级)     - 个人竞技场排行榜(1. 竞技场排名)     - 通天塔排行榜(1.通天塔层数 2.通关时间)     - 威望排行榜(1.威望值 2.等级) 军团(公会)       - 军团等级排行榜(1.军团等级 2.军团总战斗力) 坦克(1.坦克战斗力 2.坦克等级)       - 中型     - 重型     - 反坦克炮     - 自行火炮
  ↑ 括号内为排序维度   3. 思路
  基于实时性的考虑, 决定使用Redis来实现该排行榜.
  文章中用到的redis命令如有不清楚的, 可参照 Redis在线手册 .
  需要解决如下问题:  复合排序(2维)  排名数据的动态更新  如何取排行榜  4. 实现 复合排序
  基于Redis的排行榜主要使用的是Redis的 有序集合(SortedSet)来实现
  添加 成员-积分 的操作是通过Redis的zAdd操作
  ZADD key score member [[score member] [score member] ...]
  默认情况下, 若score相同, 则按照 member 的字典顺序排序.  4.1 等级排行榜
  首先以等级排行榜(1. 等级 2.战力)为例, 该排行榜要求同等级的玩家, 战斗力大的排在前. 因此分数可以定为:
  分数 = 等级*10000000000 + 战斗力
  游戏中玩家等级范围是1~100, 战力范围0~100000000.
  此处设计中为战斗力保留的值范围是 10位数值, 等级是 3位数值, 因此最大数值为  13位  .
  有序集合的score取值是是64位整数值或双精度浮点数, 最大表示值是 9223372036854775807, 即能完整表示 18位  数值,因此用于此处的 13位score 绰绰有余. 4.2 通天塔排行榜
  另一个典型排行榜是  通天塔排行榜(1.层数 2.通关时间)  , 该排行榜要求通过层数相同的, 通关时间较早的优先.
  由于要求的是通关时间较早的优先, 因此不能像之前那样直接  分数=层数*10^N+通关时间  .
  我们可以将通关时间转换为一个相对时间, 即  分数=层数*10^N + (基准时间 - 通关时间)
  很明显的, 通关时间越近(大), 则 基准时间 - 通关时间  值越小, 符合该排行榜要求.
  基准时间的选择则随意选择了较远的一个时间   2050-01-01 00:00:00   , 对应时间戳2524579200
  最终,  分数 = 层数_ 10^N + (2524579200 - 通过时间戳) 述分数公式中, N取10, 即保留10位数的相对时间. 4.3 坦克排行榜
  坦克排行榜跟其他排行榜的区别在于, 有序集合中的 member 是一个复合id, 由  uid_tankId  组成.
  这点是需要注意的. 5. 排名数据的动态更新
  还是以等级排行榜为例
  游戏中展示的等级排行榜所需的数据包括(但不限于):  角色名  Uid  战斗力  头像  所属公会名  VIP等级
  由于这些数据在游戏过程中是会动态变更的, 因此此处不考虑将这些数据直接作为 member 存储在有序集合中.
  用于存储玩家等级排行榜有序集合如下       -- s1:rank:user:lv ---------- zset --     | 玩家id1    | score1     | ...     | 玩家idN    | scoreN     -------------------------------------
  member为角色uid, score为复合积分
  使用hash存储玩家的动态数据(json)       -- s1:rank:user:lv:item ------- string --     | 玩家id1    | 玩家数据的json串     | ...     | 玩家idN    |      -----------------------------------------
  使用这种方案, 只需要在玩家创建角色时, 将该角色添加到等级排行榜中, 后续则是当玩家   等级战斗力   发生变化时需实时更新  s1:rank:user:lv  该玩家的复合积分即可. 若玩家其他数据(用于排行榜显示)有变化, 则也相应地修改其在 s1:rank:user:lv:item  中的数据json串. 6. 取排行榜
  依旧以等级排行榜为例.
  目的       需要从 `s1:rank:user:lv` 中取出前100名玩家, 及其数据.     
  用到的Redis命令       [`ZRANGE key start stop [WITHSCORES]`](http://redisdoc.com/sorted_set/zrange.html)     时间复杂度: O(log(N)+M), N 为有序集的基数,而 M 为结果集的基数。     
  步骤  zRange("s1:rank:user:lv", 0, 99)  获取前100个玩家的uid hGet("s1:rank:user:lv:item", $uid)  逐个获取前100个玩家的具体信息
  具体实现时, 上面的步骤2是可以优化的.
  分析  zRange时间复杂度是O(log(N)+M) , N 为有序集的基数,而 M 为结果集的基数  hGet时间复杂度是 O(1)  步骤2由于最多需要获取100个玩家数据, 因此需要执行100次, 此处的执行时间还得加上与redis通信的时间, 即使单次只要1MS, 最多也需要100MS.
  解决  借助Redis的Pipeline, 整个过程可以降低到只与redis通信2次, 大大降低了所耗时间.
  以下示例为php代码       // $redis     $redis->multi(Redis::PIPELINE);     foreach ($uids as $uid) {         $redis->hGet($userDataKey, $uid);     }     $resp = $redis->exec();    // 结果会一次性以数组形式返回
  Tip: Pipeline 与 Multi 模式的区别
  参考: https://blog.csdn.net/weixin_...   Pipeline 管线化, 是在客户端将命令缓冲, 因此可以将多条请求合并为一条发送给服务端. 但是  不保证原子性  !!! Multi 事务, 是在服务端将命令缓冲, 每个命令都会发起一次请求,  保证原子性  , 同时可配合 WATCH  实现事务, 用途是不一样的. 7. Show The Code     <?php     class RankList     {         protected $rankKey;         protected $rankItemKey;         protected $sortFlag;         protected $redis;              public function __construct($redis, $rankKey, $rankItemKey, $sortFlag=SORT_DESC)         {             $this->redis = $redis;             $this->rankKey = $rankKey;             $this->rankItemKey = $rankItemKey;             $this->sortFlag = SORT_DESC;         }              /**          * @return Redis          */         public function getRedis()         {             return $this->redis;         }              /**          * @param Redis $redis          */         public function setRedis($redis)         {             $this->redis = $redis;         }              /**          * 新增/更新单人排行数据          * @param string|int $uid          * @param null|double $score          * @param null|string $rankItem          */         public function updateScore($uid, $score=null, $rankItem=null)         {             if (is_null($score) && is_null($rankItem)) {                 return;             }                  $redis = $this->getRedis()->multi(Redis::PIPELINE);             if (!is_null($score)) {                 $redis->zAdd($this->rankKey, $score, $uid);             }             if (!is_null($rankItem)) {                 $redis->hSet($this->rankItemKey, $uid, $rankItem);             }             $redis->exec();         }              /**          * 获取单人排行          * @param string|int $uid          * @return array          */         public function getRank($uid)         {             $redis = $this->getRedis()->multi(Redis::PIPELINE);             if ($this->sortFlag == SORT_DESC) {                 $redis->zRevRank($this->rankKey, $uid);             } else {                 $redis->zRank($this->rankKey, $uid);             }             $redis->hGet($this->rankItemKey, $uid);             list($rank, $rankItem) = $redis->exec();             return [$rank===false ? -1 : $rank+1, $rankItem];         }              /**          * 移除单人          * @param $uid          */         public function del($uid)         {             $redis = $this->getRedis()->multi(Redis::PIPELINE);             $redis->zRem($this->rankKey, $uid);             $redis->hDel($this->rankItemKey, $uid);             $redis->exec();         }              /**          * 获取排行榜前N个          * @param $topN          * @param bool $withRankItem          * @return array          */         public function getList($topN, $withRankItem=false)         {             $redis = $this->getRedis();             if ($this->sortFlag === SORT_DESC) {                 $list = $redis->zRevRange($this->rankKey, 0, $topN);             } else {                 $list = $redis->zRange($this->rankKey, 0, $topN);             }                  $rankItems = [];             if (!empty($list) && $withRankItem) {                 $redis->multi(Redis::PIPELINE);                 foreach ($list as $uid) {                     $redis->hGet($this->rankItemKey, $uid);                 }                 $rankItems = $redis->exec();             }             return [$list, $rankItems];         }              /**          * 清除排行榜          */         public function flush()         {             $redis = $this->getRedis();             $redis->del($this->rankKey, $this->rankItemKey);         }     }
  这就是一个排行榜最简单的实现了, 排行项的积分计算由外部自行处理.
  转载自:https://segmentfault.com/a/1190000019139010

孤存事件扯出了korol的陈年老瓜?EDG雪藏扣肉的真相来了在LPL赛场上只有比赛的情况下,隔壁绝地求生却在最近闹出了一件大事,那就是孤存与4AM战队翻脸,并离开俱乐部回家,拒绝训练,还表示要退役当主播。由于孤存此前在职业赛场上给人留下的印王思聪的熊猫TV凉了,这个锅真的该由思聪来背吗?王思聪当年信誓旦旦的将熊猫TV做了起来,而且以他在电竞行业的地位来说,似乎谁都没有怀疑这个平台会变成如今这个样子。如今的熊猫TV已经宣告破产,这也意味着拖欠主播的薪资将会烟消云散,上单王又换了!吕布怂了,狂铁也怂了,程咬金更是不去了!王者的上路一直是比较难守的一路,毕竟是面对对面的两个人,1对2能占到优势的英雄也不是很多,往往是迫于对面的压力,要么送掉一塔,要么送掉一血,这是上单非常尴尬的地方。坚守上单的英雄也孤存和4AM闹了这么久,为什么作为老板的韦神一句话都不说?第一韦神正在积极打比赛,这个事还忙的不过来。第二龙神绝就相当于大管家,他出面解决这个事正合适,不能一出什么事就得老大出马,那老大不得累死。第三韦神和蜜蜂仔都把gc当弟弟一样照顾,疼良心做工值得购买惠普暗影精灵4Pro拆解评析惠普暗影精灵4Pro15DC1004TXPRC(6GE52PA)春节前夕,英伟达携众多OEM厂商带来了首批搭载RTX显卡的游戏笔记本电脑。不过从这些首发的RTX游戏本售价来看,普遍坦克之王再度易主,项羽苏烈已经没落,他沉寂已久终于爆发坦克是峡谷里面必不可少一个角色,它承受了整局对战中最高的伤害,而且还要时刻保护C位,任务可以说相当重了。之前我们了解的很强势的坦克无异于项羽苏烈之类的,但是经过了版本不断地变化,它她曾与小智并称葫芦岛双雄,如今留学归来,已无人知!大家好,我是溪子,我是一个来自岛国的高中生,我今年八岁,今天我给大家带来的是艾欧尼亚的单排天坑赛,之所以选择这个英雄是因为伊丽丝享有蜘蛛女皇之名,叫我女王大人,这句话相信勾起了许多鹰眼系统奖励来袭,信誉积分你够了吗?伴随着目前手机游戏的不断发展,各个游戏的机制也变得更加完善了。而在这些众多游戏里面,王者荣耀无论是在游戏防沉迷系统还是在挂机惩罚方面都已经做的足够优秀了。在王者荣耀这款游戏里面,为鬼泣5评测别问,问就是满分打完了鬼泣5,包括最高难度的DMD模式,是一种极度满足后的虚脱感,不愧是让我等了十年之久的神作(苦笑)。DMD难度真的很难,系列最高难度当然,鬼泣5没有打完的时候,我还能再玩100将您带入疯狂的深处层层恐惧VR(LayersofFearVR)将您带入疯狂的深处层层恐惧VR(LayersofFearVR)大家好,我是痴迷VR世界,吹爆机皇Quest的幻境流浪汉白马,有好玩的,不分享怎么行,为你献上好玩的有用的VR游戏情报璀璨传奇复古豹子头玩道士准没错啦!自带两个特戒直接送群攻传奇玩了这么多年三个职业都玩过战士的话主打的是后期暴击的爽法师也是后期的火雨让战士和道士羡慕不已但是大部分的散人和豹子头还是喜欢玩道士的因为道士可以单打独斗当独行侠打一切的boss
怀旧Retroarch安装与使用每天进步一点点加油()各位小伙伴们,大家好!希望我的分享能伴随着大家每天进步一点点!RetroarchSEGASaturn(世嘉土星,简称SS)是日本世嘉(SEGA)公司开发的第六后羿后期伤害无解,但为何出场率持续降低,后羿真的不能玩了吗阿浩的游戏研究室,一心只为研究而生。不知道大家最近在王者荣耀中有多久没有碰到过后羿这个英雄呢,好像在我印象里面自从养猪流衰败之后,后羿这个英雄也是一蹶不振,逐渐在王者峡谷中丧失了出每日Steam折扣漂移21UnMetal银河护卫队总览1漂移21(DRIFT21)(史低49)(现价49)2UnMetal(史低39)(现价39)3银河护卫队(MarvelsGuardiansoftheGalaxy)(史低39)(翼星求生ICARUS照片模式怎么使用?详细教程玩翼星求生,就用野豹游戏加速器!畅享流畅稳定加速在键盘的数字键盘上按8进入照片模式在键盘的数字键盘上按9以打开关闭游戏UI您可以在设置菜单中更改这些键绑定照片将保存到您的photo一个原神并不够,中国游戏音乐离走向全球还有多远?导读游戏音乐的本质还是一个带着镣铐跳舞,但是你要跳得漂亮的一件事情。国内音乐从业环境的改善,人才的培养,经验的积累,非常非常重要。米哈游音乐总监蔡近翰(采访整理观察者网周昊)201LOL2021年最后一次版本更新,薇恩男刀遭削弱,先攻被砍让游戏成为一味调味料,给乏味的生活带来一点轻松和娱乐,大家好,欢迎收看游戏君为大家带来的最新的游戏资讯!英雄联盟在进入S12赛季之后,进行了一些的改变,改变之后多少会给游戏环境带来王者荣耀成吉思汗攻略,轻松学会王者荣耀成吉思汗英雄实战技巧攻略。王者荣耀成吉思汗是一名强大的射手英雄,强大的爆发能力让成吉思汗出场以来就深得人心。那么成吉思汗在实战的时候需要注意哪些技巧呢?被动成吉思汗的被动给申请仙剑奇侠传商标被驳回?热IP也能蹭吗?剑南春就这么做了仙剑奇侠传是由大宇资讯所制作的一款国产单机中文角色扮演电脑游戏,大宇资讯股份有限公司是中国台湾一家网络游戏开发商。相对来说这款游戏还是比较小众的,与王者荣耀阴阳师等火爆全网的游戏模玩得再菜也能月入上万?起底游戏搬砖党一边玩游戏,一边月入上万听起来似乎有点天方夜谭,但对于游戏中的搬砖党而言却早已见怪不怪。近日,西山居发布了剑侠情缘网络版的复刻手游剑网1归来,不仅吸引了众多情怀玩家回归,其自由交易防沉迷影响腾讯收入?腾讯游戏研发不为赚未成年人的钱澎湃新闻记者范佳来在未成年人防沉迷新规落地后,企业如何执行成为外界关注的焦点。12月15日,由中国音像与数字出版协会主办的2021中国游戏产业年会未成年人守护分论坛暨落实防沉迷新规部落与弯刀正式版即将发布,游戏内容迎来重大扩展如果有这样一款游戏,它的玩法多样,官方更新速度快,还支持创意工坊,你会喜欢它吗?部落与弯刀就是这样的一款游戏,玩家操纵角色驰骋在一望无际的大漠上,经历领主们的勾心斗角,与入侵的火魔