成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

基于Redis游戲中的實(shí)時(shí)排行榜實(shí)現(xiàn)

wangym / 3211人閱讀

摘要:年月日前言前段時(shí)間剛為項(xiàng)目手游實(shí)現(xiàn)了一個(gè)實(shí)時(shí)排行榜功能主要特性實(shí)時(shí)全服排名可查詢單個(gè)玩家排名支持雙維排序數(shù)據(jù)量不大大致在區(qū)間開服合服會(huì)導(dǎo)致單個(gè)服角色數(shù)越來越多排行榜分類按照排行主體類型劃分主要分為角色軍團(tuán)公會(huì)坦克該項(xiàng)目是個(gè)坦克手游大致情況

[TOC]

Last-Modified: 2019年6月4日18:18:37

1. 前言

前段時(shí)間剛為項(xiàng)目(手游)實(shí)現(xiàn)了一個(gè)實(shí)時(shí)排行榜功能, 主要特性:

實(shí)時(shí)全服排名

可查詢單個(gè)玩家排名

支持雙維排序

數(shù)據(jù)量不大, 大致在 1W ~ 50W區(qū)間(開服, 合服會(huì)導(dǎo)致單個(gè)服角色數(shù)越來越多).

2. 排行榜分類

按照排行主體類型劃分, 主要分為:

角色

軍團(tuán)(公會(huì))

坦克

該項(xiàng)目是個(gè)坦克手游, 大致情況是每個(gè)角色有N輛坦克, 坦克分為多種類型(輕型, 重型等), 玩家可加入一個(gè)軍團(tuán)(公會(huì)).

具體又可以細(xì)分為:

角色

- 等級(jí)排行榜(1. 等級(jí) 2.戰(zhàn)力)
- 戰(zhàn)斗力排行榜(1. 戰(zhàn)斗 2.等級(jí))
- 個(gè)人競技場排行榜(1. 競技場排名)
- 通天塔排行榜(1.通天塔層數(shù) 2.通關(guān)時(shí)間)
- 威望排行榜(1.威望值 2.等級(jí))

軍團(tuán)(公會(huì))

- 軍團(tuán)戰(zhàn)斗力排行榜(1. 軍團(tuán)總戰(zhàn)斗力 2.軍團(tuán)等級(jí))
- 軍團(tuán)等級(jí)排行榜(1.軍團(tuán)等級(jí) 2.軍團(tuán)總戰(zhàn)斗力)

坦克(1.坦克戰(zhàn)斗力 2.坦克等級(jí))

- 輕型坦克戰(zhàn)斗力排行榜
- 中型
- 重型
- 反坦克炮
- 自行火炮

↑ 括號(hào)內(nèi)為排序維度
3. 思路

基于實(shí)時(shí)性的考慮, 決定使用Redis來實(shí)現(xiàn)該排行榜.

文章中用到的redis命令如有不清楚的, 可參照 Redis在線手冊.


需要解決如下問題:

復(fù)合排序(2維)

排名數(shù)據(jù)的動(dòng)態(tài)更新

如何取排行榜

4. 實(shí)現(xiàn) 復(fù)合排序

基于Redis的排行榜主要使用的是Redis的 有序集合(SortedSet)來實(shí)現(xiàn)

添加 成員-積分 的操作是通過Redis的zAdd操作 
ZADD key score member [[score member] [score member] ...]

默認(rèn)情況下, 若score相同, 則按照 member 的字典順序排序.

4.1 等級(jí)排行榜

首先以等級(jí)排行榜(1. 等級(jí) 2.戰(zhàn)力)為例, 該排行榜要求同等級(jí)的玩家, 戰(zhàn)斗力大的排在前. 因此分?jǐn)?shù)可以定為:
分?jǐn)?shù) = 等級(jí)*10000000000 + 戰(zhàn)斗力

游戲中玩家等級(jí)范圍是1~100, 戰(zhàn)力范圍0~100000000.

此處設(shè)計(jì)中為戰(zhàn)斗力保留的值范圍是 10位數(shù)值, 等級(jí)是 3位數(shù)值, 因此最大數(shù)值為 13位.
有序集合的score取值是是64位整數(shù)值或雙精度浮點(diǎn)數(shù), 最大表示值是 9223372036854775807, 即能完整表示18位數(shù)值, 因此用于此處的 13位score 綽綽有余.

4.2 通天塔排行榜

另一個(gè)典型排行榜是 通天塔排行榜(1.層數(shù) 2.通關(guān)時(shí)間), 該排行榜要求通過層數(shù)相同的, 通關(guān)時(shí)間較早的優(yōu)先.

由于要求的是通關(guān)時(shí)間較早的優(yōu)先, 因此不能像之前那樣直接 分?jǐn)?shù)=層數(shù)*10^N+通關(guān)時(shí)間.

我們可以將通關(guān)時(shí)間轉(zhuǎn)換為一個(gè)相對時(shí)間, 即 分?jǐn)?shù)=層數(shù)*10^N + (基準(zhǔn)時(shí)間 - 通關(guān)時(shí)間)
很明顯的, 通關(guān)時(shí)間越近(大), 則 基準(zhǔn)時(shí)間 - 通關(guān)時(shí)間 值越小, 符合該排行榜要求.

基準(zhǔn)時(shí)間的選擇則隨意選擇了較遠(yuǎn)的一個(gè)時(shí)間 2050-01-01 00:00:00, 對應(yīng)時(shí)間戳2524579200

最終, *分?jǐn)?shù) = 層數(shù)10^N + (2524579200 - 通過時(shí)間戳)
上述分?jǐn)?shù)公式中, N取10, 即保留10位數(shù)的相對時(shí)間.

4.3 坦克排行榜

坦克排行榜跟其他排行榜的區(qū)別在于, 有序集合中的 member 是一個(gè)復(fù)合id, 由 uid_tankId 組成.
這點(diǎn)是需要注意的.

5. 排名數(shù)據(jù)的動(dòng)態(tài)更新

還是以等級(jí)排行榜為例

游戲中展示的等級(jí)排行榜所需的數(shù)據(jù)包括(但不限于):

角色名

Uid

戰(zhàn)斗力

頭像

所屬公會(huì)名

VIP等級(jí)

由于這些數(shù)據(jù)在游戲過程中是會(huì)動(dòng)態(tài)變更的, 因此此處不考慮將這些數(shù)據(jù)直接作為 member 存儲(chǔ)在有序集合中.
用于存儲(chǔ)玩家等級(jí)排行榜有序集合如下

-- s1:rank:user:lv ---------- zset --
| 玩家id1    | score1
| ...
| 玩家idN    | scoreN
-------------------------------------
member為角色uid, score為復(fù)合積分

使用hash存儲(chǔ)玩家的動(dòng)態(tài)數(shù)據(jù)(json)

-- s1:rank:user:lv:item ------- string --
| 玩家id1    | 玩家數(shù)據(jù)的json串
| ...
| 玩家idN    | 
-----------------------------------------

使用這種方案, 只需要在玩家創(chuàng)建角色時(shí), 將該角色添加到等級(jí)排行榜中, 后續(xù)則是當(dāng)玩家 等級(jí)戰(zhàn)斗力 發(fā)生變化時(shí)需實(shí)時(shí)更新s1:rank:user:lv該玩家的復(fù)合積分即可. 若玩家其他數(shù)據(jù)(用于排行榜顯示)有變化, 則也相應(yīng)地修改其在 s1:rank:user:lv:item 中的數(shù)據(jù)json串.

6. 取排行榜

依舊以等級(jí)排行榜為例.

目的

需要從 `s1:rank:user:lv` 中取出前100名玩家, 及其數(shù)據(jù).

用到的Redis命令

[`ZRANGE key start stop [WITHSCORES]`](http://redisdoc.com/sorted_set/zrange.html)
時(shí)間復(fù)雜度: O(log(N)+M), N 為有序集的基數(shù),而 M 為結(jié)果集的基數(shù)。

步驟

zRange("s1:rank:user:lv", 0, 99) 獲取前100個(gè)玩家的uid

hGet("s1:rank:user:lv:item", $uid) 逐個(gè)獲取前100個(gè)玩家的具體信息

具體實(shí)現(xiàn)時(shí), 上面的步驟2是可以優(yōu)化的.

分析

zRange時(shí)間復(fù)雜度是O(log(N)+M) , N 為有序集的基數(shù),而 M 為結(jié)果集的基數(shù)

hGet時(shí)間復(fù)雜度是 O(1)

步驟2由于最多需要獲取100個(gè)玩家數(shù)據(jù), 因此需要執(zhí)行100次, 此處的執(zhí)行時(shí)間還得加上與redis通信的時(shí)間, 即使單次只要1MS, 最多也需要100MS.

解決

借助Redis的Pipeline, 整個(gè)過程可以降低到只與redis通信2次, 大大降低了所耗時(shí)間.

以下示例為php代碼

// $redis
$redis->multi(Redis::PIPELINE);
foreach ($uids as $uid) {
    $redis->hGet($userDataKey, $uid);
}
$resp = $redis->exec();    // 結(jié)果會(huì)一次性以數(shù)組形式返回

Tip: Pipeline 與 Multi 模式的區(qū)別

參考: https://blog.csdn.net/weixin_...

Pipeline 管線化, 是在客戶端將命令緩沖, 因此可以將多條請求合并為一條發(fā)送給服務(wù)端. 但是不保證原子性!!!

Multi 事務(wù), 是在服務(wù)端將命令緩沖, 每個(gè)命令都會(huì)發(fā)起一次請求, 保證原子性, 同時(shí)可配合 WATCH 實(shí)現(xiàn)事務(wù), 用途是不一樣的.

7. Show The Code
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;
    }

    /**
     * 新增/更新單人排行數(shù)據(jù)
     * @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個(gè)
     * @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);
    }
}

這就是一個(gè)排行榜最簡單的實(shí)現(xiàn)了, 排行項(xiàng)的積分計(jì)算由外部自行處理.

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/31439.html

相關(guān)文章

  • 玩轉(zhuǎn)Redis - 使用有序集合(sorted sets)實(shí)現(xiàn)投票游戲

    摘要:是求兩個(gè)有序集合的并集,可以用來合并兩個(gè)投票中所有參與的人的排行榜。經(jīng)過考察技術(shù)方案和實(shí)現(xiàn)成本,決定采用提供的有序集合,實(shí)現(xiàn)投票過程和實(shí)時(shí)排名的展示,直接讀取緩存,避免了非核心業(yè)務(wù)對數(shù)據(jù)庫的突發(fā)高并發(fā)訪問。 redis是一種提供多種數(shù)據(jù)類型的開源key-value存儲(chǔ)系統(tǒng),通常將數(shù)據(jù)全部存儲(chǔ)在內(nèi)存中。 showImg(https://segmentfault.com/img/remot...

    AndroidTraveler 評論0 收藏0
  • 手游開發(fā)如何選擇后端服務(wù)

    摘要:云函數(shù)是萬金油為實(shí)現(xiàn)用戶游戲數(shù)據(jù)存儲(chǔ)和每日任務(wù)分發(fā),我們最先用了存儲(chǔ)服務(wù)和云引擎。不過我們并沒有用提供的來直接調(diào)用存儲(chǔ)服務(wù),而是選擇用調(diào)用云引擎里面的云函數(shù),然后通過云函數(shù)調(diào)用存儲(chǔ)服務(wù)來實(shí)現(xiàn)相應(yīng)的邏輯。 【 玩轉(zhuǎn) LeanCloud 】開發(fā)者投稿分享: 作者:趙天澤 作為一個(gè)通過 LeanCloud 入門后端開發(fā)的小白,一年多的開發(fā)歷程讓我收獲滿滿。多個(gè)項(xiàng)目也在 LeanCloud 可...

    codecook 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<