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

用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

原神神里绫人突破材料角色定位天赋背景故事等消息整理旅行者们好,我是up主尤雪落儿,今天给大家带来一篇关于神里绫人的文章。一神里绫人突破材料从官方发的图片我们可以知道,神里绫人需要涤净青金碎绯樱绣球刀镡无相之水掉落的排异之露风雅系列王者荣耀都哪些英雄改过名字?算上你知道的荆轲,共13名英雄大家好我是指尖,王者荣耀已经上线了六年之久,期间英雄不断产出,每个赛季都有全新的体验,那么你知道王者荣耀英雄背后的故事吗?其实我们现在所使用的英雄,名字有一部分都是经历过改动的,就文明与征服开荒带什么技能好?英雄开荒技能一览萨拉丁乌尔班射石炮翼骑兵进军诺曼征服凯撒(一速)天主之盾斯巴达壁垒海尔曼西班牙大方阵固守坚城半月型骑兵阵海尔曼9本后换君士坦丁技能不变,这套阵容需要高红是冲榜最好用的阵容,高红可以九紫游戏代金卷(限时活动)九紫游戏激活码,九紫游戏礼包,九紫游戏特权卡,九紫游戏客服,九紫游戏下载九紫游戏来参加活动免费领吧!数量有限,先到抢先得!每天送3004000充值卷内部号进服送VIP10内部号可升环环骗氪,游戏世界里上头可能倾家荡产没有一个孩子不迷游戏,尤其是男孩子。适度游戏益脑,这是没毛病的。可是怕的是过度游戏伤身,过度充值伤财。其实,我们(也包括孩子)都懂得游戏充钱的后果,但我们无法拒绝。这说明一些游戏把黑暗光年终极装备介绍终极贪狼刺影之刃基础属性攻击462925准确100元素属性切割伤害1850伤害加成15吸血15暴击几率15终极贪狼刺影战甲基础属性防御615615攻击155308暴伤抵抗15元素属马哲入驻快手,首播人气高涨,主播挑战赛值得期待提到自己的启蒙游戏,你会想到哪一款呢?相信很多玩家都会说是穿越火线。其实穿越火线这款游戏竞技性非常高,而且游戏技巧也非常多,对武器的要求也非常高,需要长年累月的不断练习才能成为高手天堂W快速升级轻松战斗养成强化多多云全天托管任务日常天堂作为全球系列的作品,上线20多年以来始终拥有着不错的人气热度,而随着全新一代的天堂W国际服上线,更是再一次引燃了玩家们的热情。天堂W中不仅拥有着整体更高的完成度,同时也是对天堂王者荣耀又一6云史诗皮肤确定!恭喜元歌喜提源梦新皮王者刚确定了嫦娥源梦皮肤拒霜思会在十二月初上线,马上又开始发布下一位源梦皮肤获得者的线索。小话可等不及天美挤牙膏式的爆料,所以直接给大家做了个现有线索的合集,直接给大家揭晓得主线索英雄联盟如何从玩家手指按键方式来判断一个玩家的游戏水平?英雄联盟在当前也是一款非常热门的竞技游戏,不管是学生还是上班族,都喜欢在闲暇之余玩上几把,虽然看似比较简单的游戏,学问却是非常大的,在之前网友们总结了一个结论,那就是从玩家的手指按明日之后1月31日新版本中出现的榴弹炮,有玩家称有保底必得方式,这是怎么回事?欢迎诸位小伙伴们来到本期游戏君开讲的明日之后生存那点事儿今天呢,明日之后正式更新了新年版本,接下来呢咱们聊聊在玩家在更新之后的一些新发现得,废话不多说了,还是你熟悉的老规矩有图有证
大表哥2最终版体验(Epic)昨天把永劫无间和2077给体验了。永劫无间留下了。2077有贴图错误我也懒得折腾,直接退款了。然后转手入了大表哥2最终版。昨天加今天一共体验了三个多小时。Epic现在购买最终版只要奖金高达150W美元!永劫无间世界赛抽签出炉,YJJ再遇李星痕前言永劫无间这款游戏自公测以来就深受玩家的喜爱,他凭借着武侠的游戏风格外加绝地求生的运行模式,一举成为了当时最火的游戏,现在热度也相当之高!由于热度提升的太快,导致一系列联赛全部脱狼队公布挑战者杯名单,人员不变林教练依旧不在,真要等明年了?王者荣耀kpl秋季赛正在火热进行中,在今年秋季赛,重庆狼队获得的成绩还是非常不错的。虽然没有向大家想象的那样打进总决赛,但是总的来说,成绩和上个赛季想必还是有很大的提升的。不过重庆主播集体斗地主,吕德华夺冠拿下胜利,结果剑仙不同意了最近在某牙举办的KPI杯S2相信大家应该都有所耳闻,在S1的的比赛当中吕德华率领的神机营没能够击败刘伟家族最后只拿到了亚军,心有不甘的吕德华随后立马报名了S2的比赛,但是S2的比赛KPI杯S2斗地主大赛落幕,吕德华抱走2万奖金,剑仙却遗憾收场?相信大家对于最近一段时间正在举办的虎牙KPI杯不会感到陌生吧?这一场赛事严格意义上来讲就是一场斗地主的比萨,不过这一次所汇聚的选手质量却相当高,有王者荣耀板块的孤影剑仙吕德华等人,光遇可爱到冒泡?三种玩法畅想,泡泡机至少128光遇可爱到冒泡?三种玩法畅想,泡泡机至少128前言大家好,我是林克。每日分享游戏电竞情报攻略玩法等。今天这个话题的灵感,来自于家里的小朋友,他们在玩吹泡泡,一脸天真无邪,真的可爱至日服任天堂Switch2021年度下载游戏排名公布任天堂日本官网公开了日服NintendoSwitch2021年度下载前10名的榜单,其中获得前10的游戏分别是1怪物猎人崛起游戏简介怪物猎人崛起是为Switch平台量身打造的怪物猎单机武侠RPG活侠传登Steam唐门杂鱼江湖求生人在江湖,身不由己。单机武侠RPG游戏活侠传上架Steam商店页面,发售日未定。Steam商店地址在活侠传中,玩家将扮演一名资质平庸的唐门杂鱼,没有主角光环,人生难度EXHARD。上线在即!一文读懂MetaKrypton如何达成最高收益?MetaKrypton将于本月正式上线,小编作为一直关注这个项目的资深链游爱好者,针对官方公布的内容,整理了以下游戏攻略。游戏玩法简介MetaKrypton就是一个既简单又需要经常2022年元宇宙NFT及区块链会对游戏行业产生何等影响?2021年的NFT和P2E游戏经历了令人印象深刻的采用率,而2022年更广泛的采用依然令人翘首以盼世界游戏领域领导者之一史克威尔艾尼克斯(SquareEnix)将NFT和P2E视为严阵以待评测战术比突突突更重要严阵以待是一款非常真实的战术小队模拟射击游戏。在游戏中你对敌人,或者敌人对你都是一两枪就倒,即使身穿重甲也只是远距离可以多扛两枪。因此需要使用装备取得战术优势,像是可以穿过斗篷的偷