休闲游戏中使用redis作为排行榜数据存储

1.排行榜的需求

           

我有做了一个网站 http://www.cgame360.com/,专门放一些简单的html5游戏。现在有2个需求,我想获得一个游戏的top 10的用户的分数,还有一个是某个用户的排名,加上前后4名的人员。上图中少了名字,那个cocos2d js输入框有点问题就没加了。

现在的问题是用什么数据库来存储比较好?我想要尽可能快的查找出数据。

我搞过mysql这种关系型的数据库,第一感觉用在这里会很慢。数据量一大就会很慢,因为它排序的时候会对所有数据进行处理。还有第二个需求用sql语句会很蛋疼,我还没想到高效的实现方式。第一个前10名的,可以搞一个表来专门存前10名的。主要就是卡在第二个需求。

我google了下,发现nosql比较适合现在的需求。nosql比较有名的是Hadoop,MongoDB。但我发现一个更加适合这个需求的数据库redis.

2.redis中的有序集合

2.1redis有序集合插入一条数据

这里没时间再从安装redis讲起了。我记得好像Mac系统比较容易用,windows好像不太好用redis。

redis的有序集合插入一条数据非常容易:

ZADD key score memeber

具体例子:

ZADD game1 89 tom

这样game1中就有一条数据了Tom的分数是89

2.2 redis得到排名在某个范围的元素列表

ZRANGE key start stop [withscores]

ZREVRANGE key start stop [withscores]

具体例子:

ZRANGE game1 0 -1 withscores

这样就可以得到game1中所有的记录了,从小到大排序,-1表示最后一个元素的意思。

那么我们想要得到前10的记录用什么命令呢?

非常简单:ZRANGE game1 -10 -1 withscores  -10表示往数10个的意思。下面是我mac机执行的输出:

127.0.0.1:6379> zrange g_1 -10 -1 withscores
 1) "tom5"
 2) "506"
 3) "tom6"
 4) "507"
 5) "hug"
 6) "600"
 7) "tom"
 8) "5000"
 9) "tom8"
10) "5088"
11) "tom9"
12) "5089"
13) "tom10"
14) "5090"
15) "tom11"
16) "5091"
17) "tom12"
18) "5092"
19) "tom13"
20) "5093"

zrevrange 是表示逆向的意思,会从大到小输出。

2.3 redis得到一个元素的排名

ZRANK key member

ZREVRANK key memeber

具体例子:

zrank game1 tom

2.4 redis 得到一个元素的分数

ZSCORE key member

具体例子:

zscore game1 tom

3.PHP 中使用redis

上面讲的redis命令已经够我们使用了,接下来就是用redis与java或者php等服务器语言来组成一个服务了。我这里选择了PHP,我感觉用户提交一个分数用http的post,得到排行榜的分数用php的get够用了。

php中使用redis有两种方式,分别是predis 和phpredis,前者是完全使用php的,后者是c语言编写的php扩展。后者性能会好些,但需要安装php扩展比较麻烦。predis非常简单,就是一些php文件,扔到网站目录下就能工作了。这里选择使用了predis.

3.1 插入一条数据

<?php
require ‘./predis/autoload.php‘;

$redis = new Predis\Client(
    array(
        ‘scheme‘=>‘tcp‘,
        ‘host‘=>‘127.0.0.1‘,
        ‘port‘=>6379,
 ));

//header("content-type:text/html;charset=utf-8");

if(trim($_POST[‘game_id‘]) == ‘‘ || trim($_POST[‘user_name‘]) == ‘‘  || trim($_POST[‘score‘]) == ‘‘){
    echo ‘need more information!‘;
    exit;
}else{
    // echo ‘OK‘;
    // echo $_POST[‘game_id‘];
    // echo $_POST[‘user_name‘];
    // echo $_POST[‘score‘];

    $game_id = "g_" . $_POST[‘game_id‘];
    $name = $_POST[‘user_name‘];
    $score = $_POST[‘score‘];

    if($score > 0){
       $beforeScore = $redis->zscore($game_id
, $name);

       if($score > $beforeScore){
         $itemScore = array($name => $score);
         $redis->zadd($game_id, $itemScore);
       }

    }

}

主要这里我就简单地判断了下他现在的分数是不是比以前的高,比以前的高就插入一条数据。非常简单。

3.2 得到前10的玩家分数

<?php
require ‘./predis/autoload.php‘;
$redis = new Predis\Client(
    array(
        ‘scheme‘=>‘tcp‘,
        ‘host‘=>‘127.0.0.1‘,
        ‘port‘=>6379,
 ));

if(trim($_GET[‘game_id‘]) == ‘‘){
    echo ‘need game_id‘;
}else{
	$game_id = "g_" . $_GET[‘game_id‘];
    $board_score = $redis->zrange($game_id, -10, -1, ‘withscores‘);
    //print_r($board_score);
    echo json_encode($board_score);
    //echo $board_score;

}

这里看起来非常简单,最后我用了json_encode把php中的数组转换成json了。这样方便处理。

3.3得到我的排名

<?php
require ‘./predis/autoload.php‘;
$redis = new Predis\Client(
    array(
        ‘scheme‘=>‘tcp‘,
        ‘host‘=>‘127.0.0.1‘,
        ‘port‘=>6379,
 ));

if(trim($_GET[‘game_id‘]) == ‘‘ || trim($_GET[‘user_name‘]) == ‘‘){
    echo ‘need game_id and name‘;
}else{
	$game_id = "g_" . $_GET[‘game_id‘];
    $user_name = $_GET[‘user_name‘];

    $userCurrentRange = $redis->zrevrank($game_id, $user_name); 

     //echo ‘current range:‘ . $userCurrentRange;

    $result = array();
    if(true){
        $halfCount = 4;

         $leftRank = $userCurrentRange - $halfCount;
        $rightRank = $userCurrentRange + $halfCount;
        if($leftRank < 0){
          $leftRank  = 0;
        }

        $board_score = $redis->zrevrange($game_id, $leftRank, $rightRank, ‘withscores‘);
        $index = 0;
        foreach($board_score as $name => $score){
           if($name == $user_name){
               break;
           }else{
               $index = $index + 1;
           }
       }

        $firstRank = $userCurrentRange - $index + 1;

        foreach($board_score as $name => $score){
           $eachItem = array();
           $eachItem[‘name‘] = $name;
           $eachItem[‘score‘] = $score;
           $eachItem[‘rank‘] = $firstRank;

           $result[] = $eachItem;

           $firstRank = $firstRank + 1;
        }

        echo json_encode($result);

    }else{
        echo json_encode($result);
    }

}

这里稍微复杂了一点点,我先得到这个玩家的排名,这样就可以得到他前后4名玩家的排名区间了,用下zrevrange就ok了。就是要注意left是负数的话是其他的意思,所以要避免是负数。最后我对php数组中加入了排名信息,再次以json返回。

4.0 讲下cocos2d js中的http

php已经可以返回json的数据了,讲下cocos2d js中如何使用http 显示top 10的玩家信息。这里直接给出代码了,也比较简单。主要要注意的是要对返回的数据进行JSON.parse处理。插入数据用post,得到排行榜信息用get就OK了。我也没加反作弊功能。

getJsonFromUrl:function(){
        var xhr = cc.loader.getXMLHttpRequest();
        var args = "?game_id=" + gameId;
        xhr.open("GET", rankBoardURL + args, true);
        xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
        xhr.onreadystatechange = function () {
            if (xhr.readyState == 4 && (xhr.status >= 200 && xhr.status <= 207)) {
                var response = xhr.responseText;
                cc.log("res:" + response);
                cc.log("send success");
                var obj = JSON.parse(response);
                this.initContent(obj);
            }
        }.bind(this);

        xhr.send();
    },
    initContent:function(jsonValue){
        var size = cc.director.getWinSize();

        var fontDefBlueStroke = new cc.FontDefinition();
        fontDefBlueStroke.fontName = "Arial";
        fontDefBlueStroke.fontSize = 50;
        fontDefBlueStroke.textAlign = cc.TEXT_ALIGNMENT_LEFT;

        var jsonLength = 0;
        for(var name in jsonValue){
            jsonLength++;
        }
        cc.log("jsonlength:" + jsonLength);
        var nameMargin = 50;
        var nameStartPositionY = 800 - nameMargin * jsonLength;

        var lastRank = jsonLength;
       for(var name in jsonValue){
          cc.log(name);
          cc.log(jsonValue[name]);

          this.rankLabel = new cc.LabelTTF(lastRank + ‘:‘, fontDefBlueStroke);
          this.addChild(this.rankLabel);
          this.rankLabel.x = 100;
          this.rankLabel.y = nameStartPositionY;
          this.rankLabel.setAnchorPoint(cc.p(0, 0.5));

          this.nameLabel = new cc.LabelTTF(name, fontDefBlueStroke);
          this.addChild(this.nameLabel);
          this.nameLabel.x = size.width * 0.5 - 100;
          this.nameLabel.y = nameStartPositionY;
          this.nameLabel.setAnchorPoint(cc.p(0, 0.5));

          this.scoreLabel = new cc.LabelTTF(parseFloat(jsonValue[name]).toFixed(2), fontDefBlueStroke);
          this.addChild(this.scoreLabel);
          this.scoreLabel.x = size.width * 0.5;
          this.scoreLabel.y = nameStartPositionY;
          this.scoreLabel.setAnchorPoint(cc.p(0, 0.5));

          nameStartPositionY = nameStartPositionY + nameMargin;
          lastRank = lastRank - 1;
       }
    }

最后大家玩下这个使用了上面讲到的技术的html5游戏,用cocos2d js开发的。http://www.cgame360.com/halloweengame/

整个排行榜的PHP服务我已经打包成一个文件了,点击下面的链接下载。包含了predis,如果你有mac系统或者linux系统,安装好php,redis等等,把这个解压到网站根目录应该就可以用了的.我也是第一次使用redis,哪里用错请评论中指出,谢谢。

http://www.waitingfy.com/?attachment_id=1421

http://www.waitingfy.com/archives/1420

时间: 2024-08-10 00:00:12

休闲游戏中使用redis作为排行榜数据存储的相关文章

【转】休闲游戏如何登上下载排行榜?

研究结论: 1.休闲类手游不再以制作精良而取胜,各种奇葩玩法的简单游戏反而得到用户欢迎: 2.玩法简单,主题有噱头的手游不仅开发成本低,而且更易于快速复制,时间上快人一步: 3.精心制作但缺乏卖点的游戏被边缘化,预期让专家来定义好坏,不如让用户和市场来判定成败. 序:休闲游戏变 傻 了 曾经席卷全球的Flappy Bird 飞走 后,应用商店免费榜上各类稀奇古怪的奇葩游戏仍在不断涌现.目前美国App Store免费榜前十的应用中,有七个是玩法各异的休闲游戏. 美国APP Store免费榜游戏前五

Android中使用File文件进行数据存储

Android中使用File文件进行数据存储 上一篇学到使用SharedPerences进行数据存储,接下来学习一下使用File进行存储 我们有时候可以将数据直接以文件的形式保存在设备中, 例如:文本文件,图片文件等等 使用File进行存储操作主要使用到以下的 ①:public abstract FileInputStream openFileInput (String name) 这个主要是打开文件,返回FileInputStream ②:public abstract FileOutputS

【转】 [Unity3D]手机3D游戏开发:场景切换与数据存储(PlayerPrefs 类的介绍与使用)

http://blog.csdn.net/pleasecallmewhy/article/details/8543181 在Unity中的数据存储和iOS中字典的存储基本相同,是通过关键字实现数据存储与调用. 下面来介绍一下Unity用来存储数据的PlayerPrefs 类. 使用PlayerPrefs可以在在游戏会话中保持并访问玩家偏好设置. 在Mac OS X上PlayerPrefs存储在-/Library/PlayerPrefs文件夹, 名文unity/[companyname]\[pro

Android中常用的五种数据存储方式

第一种: 使用SharedPreferences存储数据 适用范围: 保存少量的数据,且这些数据的格式非常简单:字符串型.基本类型的值.比如应用程序的各种配置信息(如是否打开音效.是否使用震动效果.小游戏的玩家积分等),解锁口 令密码等 核心原理: 保存基于XML文件存储的key-value键值对数据,通常用来存储一些简单的配置信息.通过DDMS的File Explorer面板,展开文件浏览树,很明显SharedPreferences数据总是存储在/data/data/<package name

Redis教程4--Redis数据存储优化机制

1.zipmap优化hash: 前面谈到将一个对象存储在hash类型中会占用更少的内存,并且可以更方便的存取整个对象.省内存的原因是新建一个hash对象时开始是用zipmap来存储的.这个zipmap其实并不是hash table,但是zipmap相比正常的hash实现可以节省不少hash本身需要的一些元数据存储开销.尽管zipmap的添加,删除,查找都是O(n),但是由于一般对象的field数量都不太多.所以使用zipmap也是很快的,也就是说添加删除平均还是O(1).如果field或者val

Lucene索引过程中的内存管理与数据存储

Lucene的索引过程分两个阶段,第一阶段把文档索引到内存中:第二阶段,即内存满了,就把内存中的数据刷新到硬盘上.          倒排索引信息在内存存储方式 Lucene有各种Field,比如StringField,TextField,IntField,FloatField,DoubleField-,Lucene在处理的过程中把各种Field都处理成相应的byte[],以最本质的方式来看待各种Field的内容,统一了数据的存储形式. 在写入内存阶段,第一步就是需要理清各个类之间的关系. 在索

iOS中几种常用的数据存储方式

自己稍微总结了一下下,方便大家查看 1.write直接写入文件的方法 永久保存在磁盘中,可以存储的对象有NSString.NSArray.NSDictionary.NSData.NSNumber,数据全部存放在一个属性列表文件(*.plist文件)中, 具体步骤大致如下: 第一步:获得文件即将保存的路径: NSArray*documentPaths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,Y

[原创]游戏中的实时排行榜实现

目录 1. 前言 2. 排行榜分类 3. 思路 4. 实现 复合排序 4.1 等级排行榜 4.2 通天塔排行榜 4.3 坦克排行榜 5. 排名数据的动态更新 6. 取排行榜 7. Show The Code 1. 前言 前段时间刚为项目(手游)实现了一个实时排行榜功能, 主要特性: 实时全服排名 可查询单个玩家排名 支持双维排序 数据量不大, 大致在 1W ~ 50W区间(开服, 合服会导致单个服角色数越来越多). 2. 排行榜分类 按照排行主体类型划分, 主要分为: 角色 军团(公会) 坦克

iOS狂暴之路---iOS中应用的数据存储方式解析

一.前言 前面一篇文章中已经介绍了iOS应用中的视图控制器知识点,而本文不会按照常理来介绍View的知识点,而是先介绍iOS中的数据存储知识点,因为关于View的知识点太多了,后面会连续详细介绍一下.这篇先来看一下iOS中的数据存储功能分析.每一个iOS应用和Android应用一样,都有其对应的沙盒存储自己的数据,但是iOS和Android有一个区别就在于没有SD卡的概念了,也就说在iOS中应用的数据只能保存到自己的沙盒中.这也可以看出来iOS为了应用的安全考虑. 在开发Android的时候都知