用 Redis 搞定游戏中的实时排行榜,附源码!
1. 前言
实时全服排名 可查询单个玩家排名 支持双维排序
2. 排行榜分类
角色 军团(公会) 坦克
该项目是个坦克手游, 大致情况是每个角色有N辆坦克, 坦克分为多种类型(轻型, 重型等), 玩家可加入一个军团(公会).
角色
- 战斗力排行榜(1. 战斗 2.等级)
- 个人竞技场排行榜(1. 竞技场排名)
- 通天塔排行榜(1.通天塔层数 2.通关时间)
- 威望排行榜(1.威望值 2.等级)
军团(公会)
- 军团等级排行榜(1.军团等级 2.军团总战斗力)
坦克(1.坦克战斗力 2.坦克等级)
- 中型
- 重型
- 反坦克炮
- 自行火炮
↑ 括号内为排序维度
3. 思路
复合排序(2维) 排名数据的动态更新 如何取排行榜
4. 实现 复合排序
添加 成员-积分 的操作是通过Redis的zAdd操作 ZADD key score member [[score member] [score member] ...]
4.1 等级排行榜
游戏中玩家等级范围是1~100, 战力范围0~100000000.
有序集合的score取值是是64位整数值或双精度浮点数, 最大表示值是 9223372036854775807, 即能完整表示 18位 数值,因此用于此处的 13位score 绰绰有余.
4.2 通天塔排行榜
很明显的, 通关时间越近(大), 则 基准时间 - 通关时间 值越小, 符合该排行榜要求.
分数 = 层数_ 10^N + (2524579200 - 通过时间戳)
述分数公式中, N取10, 即保留10位数的相对时间.4.3 坦克排行榜
这点是需要注意的.
5. 排名数据的动态更新
角色名 Uid 战斗力 头像 所属公会名 VIP等级
用于存储玩家等级排行榜有序集合如下
-- s1:rank:user:lv ---------- zset --
| 玩家id1 | score1
| ...
| 玩家idN | scoreN
-------------------------------------
member为角色uid, score为复合积分
-- 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名玩家, 及其数据.
[`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个玩家的uidhGet("s1:rank:user:lv:item", $uid)
逐个获取前100个玩家的具体信息
zRange时间复杂度是O(log(N)+M) , N 为有序集的基数,而 M 为结果集的基数 hGet时间复杂度是 O(1) 步骤2由于最多需要获取100个玩家数据, 因此需要执行100次, 此处的执行时间还得加上与redis通信的时间, 即使单次只要1MS, 最多也需要100MS.
借助Redis的Pipeline, 整个过程可以降低到只与redis通信2次, 大大降低了所耗时间.
// $redis
$redis->multi(Redis::PIPELINE);
foreach ($uids as $uid) {
$redis->hGet($userDataKey, $uid);
}
$resp = $redis->exec(); // 结果会一次性以数组形式返回
参考: 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);
}
}
好书推荐
《深入理解计算机系统》
理解计算机系统首首选书目, 10余万程序员的共同选择。卡内基-梅隆、北京大学、清华大学、上海交通大学等国内外众多知名高校选用指定教材。从程序员视角全面剖析的实现细节,使读者深刻理解程序的行为,将所有计算机系统的相关知识融会贯通。每位不只想一直做码农的IT从业者都该仔细阅读!
评论