游戏服务器之内存数据库redis客户端应用(上)

本文主要介绍游戏服务器的对redis的应用。介绍下redis c++客户端的一些使用。

存储结构设计:

(1)装备道具的redis存储结构为例(Hashes存储类型)

存储结构为: key : EQUIPMENTBAG角色id frield: 装备位置 value:装备信息

存储一个装备道具到redis(使用hset 命令)

一次存储玩家的装备背包里的所有道具(使用命令hmset)

一次获取一个玩家的装备包裹的所有道具(一次获取键的所有field和value(使用命令hgetall))

(2)角色基础属性的redis存储结构为例(字符串存储类型)

存储结构:key:BASE角色id ,value: 角色基础信息

获取一个角色基础属性 (使用命令 get)

存储一个角色基础属性(使用命令set)

(3)过期时间

设置过期时间 30天.访问时需要判断key 是否还存在。

本文目录:

1、redis命令介绍

(1)基本命令

(2)应用介绍

2、redis存储结构之应用解析

3、写入redis的应用

(1)存储一个装备道具到redis(使用hset 命令)

存储结构 key : EQUIPMENTBAG角色id frield: 装备位置 value:装备信息

key : EQUIPMENTBAGplayerId frield: pos value:CBagItem

(2)一次存储玩家的装备背包里的所有道具(使用命令hmset)

(3)存储一个角色的基础信息(使用命令set)

存储结构:key BASE角色id ,value 角色基础信息

4、读取redis的应用

(1)一次获取一个玩家的装备包裹的所有道具(一次获取键的所有field和value(使用命令hgetall))

(2)读取角色的基础信息(使用命令get)

存储结构:key BASE角色id,value 角色基础信息

5、redis客户端池

6、第三方的库接口

(1)redis客户端对象

(2)接口函数

(3)定义的异常

本文内容:

1、redis命令介绍

介绍一下redis客户端的接口对应使用到的redis的原生命令。

(1)基本命令

命令类型有:

(1)String 常用命令: set,get,decr,incr,mget 等。 (2)Hash 常用命令: hget,hset,hgetall 等。 (3)List 常用命令: lpush,rpush,lpop,rpop,lrange等。 (4)Set 常用命令: sadd,spop,smembers,sunion 等。 (5)Sorted set 常用命令: zadd,zrange,zrem,zcard等

参考:http://blog.csdn.net/chenjiayi_yun/article/details/18887757

(2)应用介绍

常用命令: hget,hset,hgetall、hmset 、mset 等。 应用场景: 我们简单举个实例来描述下Hash的应用场景,比如我们要存储一个用户信息对象数据,包含以下信息: 用户ID,为查找的key, 存储的value用户对象包含姓名name,年龄age,生日birthday 等信息, 如果用普通的key/value结构来存储,主要有以下2种存储方式: (1) 第一种方式将用户ID作为查找key,把其他信息封装成一个对象以序列化的方式存储,需要一次次地发送和返回。 如:set u001 "李三,18,20010101" 这种方式的缺点是,增加了序列化/反序列化的开销,并且在需要修改其中一项信息时,需要把整个对象取回,并且修改操作需要对并发进行保护,引入CAS等复杂问题。 (2) 第二种方法是这个用户信息对象有多少成员就存成多少个key-value对儿,用用户ID+对应属性的名称作为唯一标识来取得对应属性的值,不需要一次次地设置,可以一次设置多个,但命令信息有些冗余。 如:mset user:001:name "李三 "user:001:age18user:001:birthday "20010101" 虽然省去了序列化开销和并发问题,但是用户ID为重复存储,如果存在大量这样的数据,内存浪费还是非常可观的。 (3)第三个,那么Redis提供的Hash很好的解决了这个问题,Redis的Hash实际是内部存储的Value为一个HashMap,并提供了直接存取这个Map成员的接口, 如:hmset user:001 name "李三" age 18 birthday "20010101" 也就是说,Key仍然是用户ID, value是一个Map,这个Map的key是成员的属性名,value是属性值,这样对数据的修改和存取都可以直接通过其内部Map的Key(Redis里称内部Map的key为field), 也就是通过 key(用户ID) + field(属性标签) 就可以操作对应属性数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题。很好的解决了问题。 这里同时需要注意,Redis提供了接口(hgetall)可以直接取到全部的属性数据,但是如果内部Map的成员很多,那么涉及到遍历整个内部Map的操作,由于Redis单线程模型的缘故,这个遍历操作可能会比较耗时,而另其它客户端的请求完全不响应,这点需要格外注意。

参考:http://www.cnblogs.com/stephen-liu74/archive/2012/04/16/2370212.html

2、redis存储结构之应用解析

如果是一个键对应一个值并且多个field和多个value的值,如整个背包的道具(键是角色id),可使用hash存储结构。

如果内容是一个键对应一个value,如角色属性,可考虑使用string存储结构。

hash存储结构(应用于装备背包道具)

HSET key field value

O(1)

为指定的Key设定Field/Value对,如果Key不存在,该命令将创建新Key以参数中的Field/Value对,如果参数中的Field在该Key中已经存在,则用新值覆盖其原有值。

1表示新的Field被设置了新值,0表示Field已经存在,用新值覆盖原有值。

HGET key field

O(1)

返回指定Key中指定Field的关联值。

返回参数中Field的关联值,如果参数中的Key或Field不存,返回nil。

HEXISTSkey field

O(1)

判断指定Key中的指定Field是否存在。

1表示存在,0表示参数中的Field或Key不存在。

HLEN key

O(1)

获取该Key所包含的Field的数量。

返回Key包含的Field数量,如果Key不存在,返回0。

HGETALLkey

O(N)

时间复杂度中的N表示Key包含的Field数量。获取该键包含的所有Field/Value。其返回格式为一个Field、一个Value,并以此类推。

Field/Value的列表。

HMSET key field value [field value ...]

O(N)

时间复杂度中的N表示被设置的Field数量。逐对依次设置参数中给出的Field/Value对。如果其中某个Field已经存在,则用新值覆盖原有值。如果Key不存在,则创建新Key,同时设定参数中的Field/Value。

参考:http://www.cnblogs.com/stephen-liu74/archive/2012/02/15/2352932.html

string存储结构(应用于角色属性)

GET key

O(1)

获取指定Key的Value。如果与该Key关联的Value不是string类型,Redis将返回错误信息,因为GET命令只能用于获取string Value。

与该Key相关的Value,如果该Key不存在,返回nil。

SET key value

O(1)

设定该Key持有指定的字符串Value,如果该Key已经存在,则覆盖其原有值。

总是返回"OK"。

参考:http://www.cnblogs.com/stephen-liu74/archive/2012/02/13/2349815.html

3、写入redis的客户端应用

装备道具结构:

struct CBagItem  
{  
int m_nID;  //物品ID 
int m_nCount; //物品个数 默认一 
//物品的可变属性 
int m_nUsedHole;  //能用孔的个数 
int m_GremId[MAXGREMNUM];  //孔里面放置的宝石id 目前武器最多有5个孔 
int m_nDeadTime;  //使用的结束时间 
bool m_IsDead;    //是否到期 
bool m_IsBand;//是否帮定 
int m_augmentLevel; //强化等级 
int m_score; //装备评分 
public:  
strengthen m_nStreng;//装备属性 
int m_nCurNum;//当前已有进度 
int m_nUpNum; //下一等级升级所需数量 
};  

(1)存储一个装备道具到redis(使用hset 命令)

void CRWRedisClientOperator::add_equip_to_redis(CGamePlayer* player,int pos,CBagItem* bagItem)  
{  
if(NULL == player||NULL == bagItem))  
{  
return;  
}  
int playerId = player->get_player_base()->m_player_id;  
//实际是以玩家id和装备关键字来作为key。例如  :  EQUIPMENTBAG%d 
char tmpBuf[256];  
memset(tmpBuf,0,sizeof(tmpBuf));  
sprintf(tmpBuf,"EQUIPMENTBAG%d",playerId);  
string key(tmpBuf);  
 
memset(tmpBuf,0,sizeof(tmpBuf));  
sprintf(tmpBuf,"%d",pos);  
string frield(tmpBuf);  
memset(tmpBuf,0,sizeof(tmpBuf));  
//这里可以直接用道具bagItem的地址是因为CBagItem类型里面的成员都是原子类型的,如果有容器(stl或其他的容器),则需要自己提供一个序列化的函数。 
memcpy(tmpBuf,(void*)bagItem,sizeof(CBagItem));  
string value;  
value.assign(tmpBuf,sizeof(CBagItem));//这里是把一个道具的内存存到一个字符串类型(string)的内存里,最大的长度是256字节,不可以超过这个长度。 
 
CRWRedisClient redisClient;  
redis::client* tmpRedisClient = redisClient.get_redis_client();//这里是redis客户端的一个实例池,做了个简单的封装 
if(NULL == tmpRedisClient)  
{  
return;  
}  
try 
{  
tmpRedisClient->hset(key,frield,value);//key : EQUIPMENTBAGplayerId  frield: pos value:CBagItem 
//设置过期时间 30天,需要设置redis内存数据的期限,鉴于内存受限 
tmpRedisClient->expire(key,3600*24*30);  
}  
catch (redis::redis_error & e)  
{  
cerr << "got exception: " << e.what() << endl << "FAIL" << endl;  
return;  
}  
}  

(2)一次存储一个玩家的装备背包里的所有道具(使用命令hmset)

存储结构:

key : EQUIPMENTBAGplayerId frield: pos value:CBagItem

void CRWRedisClientOperator::player_equipbag_insert_db_redis(CGamePlayer* player)  
{  
 if(NULL == player)  
    {  
 return;  
    }  
    CPlayerBag* bag = player->get_player_bag();  
 if(NULL == bag)  
    {  
 return;  
    }  
 //玩家装备道具的map存储到strPairVec,然后调用接口void hmset( const string_type & key, const string_pair_vector & field_value_pairs )发送到redis 服务器 
    map<int,CBagItem*>* tmpMap = bag->get_equip_map();  
    map<int,CBagItem*>::iterator iter = tmpMap->begin();  
 
 //以hash格式插入redis的元素 
    vector<pair<string,string> > strPairVec;  
 for(;iter != tmpMap->end();iter++)  
    {  
        CBagItem* equipItem = iter->second;  
 if(NULL == equipItem)  
        {  
 return;  
        }  
 //设置frield值,道具id 
 char frieldBuf[64];  
        memset(frieldBuf, 0, sizeof(frieldBuf));  
        sprintf(frieldBuf,"%d",iter->first);  
        string frield(frieldBuf);  
 
 //设置value,道具二进制信息 
 char valueBuf[256];  
        memset(valueBuf, 0, sizeof(valueBuf));  
        memcpy(valueBuf,equipItem,sizeof(CBagItem));  
        string value;  
        value.assign(valueBuf,sizeof(CBagItem));  
        strPairVec.push_back(pair<string,string>(frield,value));  
    }  
 //通过playerId得到key值 EQUIPMENTBAGplayerId 
 int playerId = player->get_player_base()->m_player_id;  
 if(0 == playerId)  
    {  
 return;  
    }  
 char tmpBuf[64];  
    memset(tmpBuf,0,64);  
    sprintf(tmpBuf,"EQUIPMENTBAG%d",playerId);  
    string key(tmpBuf);  
 
    CRWRedisClient redisClient;  
    redis::client* tmpRedisClient = redisClient.get_redis_client();  
 if(NULL == tmpRedisClient)  
    {  
 return ;  
    }  
 try 
    {  
 //使用命令hmset 设置 key : EQUIPMENTBAGplayerId  frield: pos value:CBagItem 
        tmpRedisClient->hmset(key,strPairVec);  
 //设置过期时间 30天 
        tmpRedisClient->expire(key,60*60*24*30);  
    }  
 catch (redis::redis_error & e)  
    {  
        cerr << "got exception: " << e.what() << endl << "FAIL" << endl;  
 return ;  
    }  
}  

原文发布于微信公众号 - Golang语言社区(Golangweb)

原文发表时间:2016-08-22

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大前端_Web

NodeJS学习三之API

版权声明:本文为吴孔云博客原创文章,转载请注明出处并带上链接,谢谢。 https://blog.csdn.net/wkyseo/articl...

1263
来自专栏逆向技术

C++反汇编第六讲,认识C++中的Try catch语法,以及在反汇编中还原

      C++反汇编第六讲,认识C++中的Try catch语法,以及在反汇编中还原 我们以前讲SEH异常处理的时候已经说过了,C++中的Try catch...

23510
来自专栏Android中高级开发

Android并发编程 开篇

该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索...

942
来自专栏Java学习网

Java Web中Request对象的52个方法—即查即用

Request表示HttpServletRequest对象,它包含了有关浏览器请求的信息,并且提供了几个用于获取cookie, header, 和session...

2728
来自专栏Java编程技术

基于rxjava的生产消费模型

最近在看springcloud的熔断机制的实现,发现底层使用的rxjava实现,就看了下rxjava的使用,发现rxjava使用可也便捷实现前面讲解的定时生产与...

902
来自专栏FreeBuf

Python黑客学习笔记:从HelloWorld到编写PoC(上)

本系列文章适合CS在读学生和万年工具党,本文会在英文原文的基础上做些修改,并适当增加些解释说明。 ? 本篇包含原文的前几部分: 0x0 – Getting St...

29210
来自专栏你不就像风一样

Java多线程编程核心技术(三)多线程通信

通过本节可以学习到,线程与线程之间不是独立的个体,它们彼此之间可以互相通信和协作。

1248
来自专栏木木玲

设计模式 ——— 状态模式

1212
来自专栏炉边夜话

JNI设计实践之路

本文为在 32 位 Windows 平台上实现 Java 本地方法提供了实用的示例、步骤和准则。本文中的示例使用 Sun公司的 Java Development...

1783
来自专栏Java学习网

Java中使用Hibernate系列之过滤器(filters)学习

Hibernate3新增了对某个类或者集合使用预先定义的过滤器条件(filter criteria)的功能。过滤器条件相当于定义一个 非常类似于类和各种集合上的...

2216

扫码关注云+社区

领取腾讯云代金券