专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

用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

吃鸡玩家壮起胆子揭发神仙,看到专属奖励后,怒砸手机欢迎诸位小伙伴们来到天哥开讲的吃鸡小课堂不知不觉间,由蓝洞绕过光子独立研发且运营的吃鸡手游2(全名叫PUBGNEWSTATE)已经将近2个月之久了。这款吃鸡新手游曾被称为和平精英的非手机电脑游戏推荐!聚餐,场地游戏大盘点告别吃鸡王者因为马上就要过年过节了,大家走亲访友,有要好的亲戚朋友或者许久没见的闺蜜兄弟,大家一起除了吃饭喝酒,打牌或者打王者都显得有些无聊,本期给大家推荐一些餐桌游戏,可以让大家可以在餐桌上三国杀发育流武将,给我三轮时间,还你一个一打三的强度青崖发现最近斗地主发现不管是地主还是农民发育流武将(或者打法)赢面都挺大的,应该是因为匹配机制的缘故(大宝被砍了出场率也有关系),发育流武将简单来讲就是越拖越强,越打到后面越强。今冬季时尚全攻略梦幻新诛仙新时装要风度也要温度最近,全国范围内的寒潮,让大家迎来了一场突如其来的初雪。很多时尚人士在兼顾好看和保暖性的穿搭上绞尽脑汁,各种花里胡哨的时装层出不穷。作为一直走在仙侠时尚前沿的梦幻新诛仙,也在冰雪节原神钟离不需要两命?策划最初的目标也不是月卡党,别太在意游戏中的笔杆王者,每日靠玩游戏过日子,玩网游也有20个年头,我有自己独特的见解,作为一个10年不脱坑的老玩家,如果文章写的有什么问题,请重喷!如果大家觉得好,请转发加点赞!非常感谢梦幻西游网页版人物属性面板大解析,原来越战的秘密在这(上)为什么低战少侠可以成功越战高战少侠?越战1亿你敢信?为什么总能让敌方大秀太空步,我方关键攻击疯狂miss?为什么别人家的2星忘川童子和精卫能够撑到第5回合究竟是三界出了bug还是我热血传奇怀旧服已经绝版的三把战士武器,其隐藏属性都是破防大家都知道在热血传奇中战法道三个职业,每个职业都有一个对自己提升巨大的技能,像战士的烈火,法师的魔法盾,道士的神兽,学习这几个技能之后角色就会发生质变,其中最变态的技能就是法师的魔梦幻西游2022低成本五开攻略不多BB,直接正题,有不懂的再问。第一门派组合2大唐1普陀1无底1盘丝。选2大唐的理由号便宜,经脉风刃超级好用,横扫50获得风魂,只需要配置一把必中刀。经脉自带小罗汉,起后发能抗能永劫无间刚摆完烧烤就开始拼阔刀?无奈血量给自己埋下伏笔随着永劫无间赛事的逐渐影响力扩大,如今越来越多的玩家来共同欣赏这一精彩视觉盛宴。在目前的中国大陆赛区单排前序积分赛上,我们看到了各个战队的选手精彩单排发挥。相比于团队赛的配合默契。原神2。4版本即将到来,角色池公布,你想好抽谁了吗?原神2。4版本前瞻一经发布,许多玩家都表示内鬼赢麻了。3复刻的消息早在版本前瞻前半个月就已经传遍各大贴吧(原神吧),让大家直呼不可能复刻的钟离随着官方的证实也敲定了下来。2。4版本明日方舟备战肉鸽模式这几位六星干员有望成为新一代肉鸽战神明日方舟对于这次即将开启的常驻肉鸽模式,估计不少玩家已经按捺不住要积极参与的心情了,借此也来展开说一下在最初肉鸽模式开放之后,到目前为止,涉及和追加的六星干员中,有哪些适合在这次的
巨头网易,是如何低调赚大钱的?曾经被誉为三大门户的网易新浪和搜狐,在21世纪头10年的互联网红利期间,凭借巨大的流量获得了快速发展。近十年来,这三家曾经的互联网巨头,纷纷转变到了各自不同的发展赛道。这期我们就来新赛季T0预测!李白韩信再次强势!蔡文姬吃满红利!射手出现黑马体验服改动S27赛季各路T0英雄预测解析发育路李元芳游走位蔡文姬对抗路夏洛特1hr发育路个人认为,此次改动最明显的发育路中,受益最大的英雄是李元芳。视野机制改动因为视野机制的改动需王者荣耀最容易上分的3个英雄今天呢给大家推荐几个非常容易上分的英雄完全不吃操作1嬴政有大开大非常实用打团可以开大实现火力压制4级后支援不用考虑杀人直接就压对面英雄血线让他们发育困难打团站后面开大注意对面刺客位热血航线1周年福利预告,上线送通行证S任选,有资源双倍诸君安好,雾夏菌报道。热血航线这边的1周年活动预告给了出来,周年活动上线登录就赠送2021年的通行证任选S,而且这次还有双倍资源掉落,这个时候刷角色碎片最舒服的啊。周年福利第一个是你对哪款游戏的捏脸系统最满意?从主机端游到手游甚至页游,在角色创建的界面加个捏脸功能进去似乎已经成了工作室的惯性思维,捏脸素材的质量和丰富程度却是参差不齐。有的游戏捏脸自由度极高,甚至能重现其它作品和现实中的人二次元经济,谁在买单?商场里的游戏主机店。洪叶摄如果没有疫情,清明假期我计划是和男朋友从南京坐高铁去上海看夏目友人帐(一部日本动画)展的,预计来回车票门票以及买周边的费用加起来人均800元左右。00后大S27赛季来袭版本之子已出现,碾压猴子,吊打韩信,操作无上限哈喽,大家好!我是老张。在王者荣耀当中,每一个赛季的更新,各个英雄的强度都会受到或多或少的调整。而不管怎么更改,打野都是一个非常核心的位置,许多小伙伴们都很好奇,为什么打野难玩却一王者荣耀冷知识作为国民手游,王者荣耀可以说非常火爆,不论是王大爷还是小学生,都非常喜欢玩,不过有一些冷知识如果你还不知道的话,怕是不能愉快的玩耍了。180的队友不看小地图捂脸。为什么你去支援的时AG官宣收官之战的首发,再次用上炖鹅老配方,稳住别浪就能赢KPL春季常规赛只剩下最后二天的比赛,AG超玩会即将迎来大结局,如果输给LGD大鹅就彻底无缘季后赛,反之就还有机会争取拿到比上个赛季好一些的成绩。虽然这场比赛大部分玩家都更看好AG放置奇兵堡垒魔法学院校规答案攻略放置奇兵以下哪一条是堡垒学院不允许的校规是复活节放置谜题之一,因此很多玩家想知道放置奇兵堡垒魔法学院校规答案是什么?小编为您带来放置奇兵堡垒魔法学院校规答案介绍。放置奇兵堡垒魔法学iFTY小九遭遇职业危机,楠哥有家不能回,夏季赛转会市场很热闹iFTY战队虽然打进了PCS6洲际赛,但从小九和PigNan的对话中看得出来,A还是会卖出一部分战队选手,而首先被考虑的就是小九,其中的原因大家也能猜到,打进洲际赛的四个功臣老板暂
友情链接:快好知快生活快百科快传网中准网文好找聚热点快软件