网络游戏的数据变动比较频繁,如果每次数据变动都刷往后端数据库,会导致数据库不负重荷。在游戏逻辑和数据库间提供一层缓冲服务,有利于减轻这重压力. 首先,网络游戏的数据在数据库中是以表的形式保存的,每个玩家的数据占用其中的一行或几行.以玩家基本属性为例: 基本表: chainfo 表结构:chaid,chaname,hp,mp,maxhp,maxmp ... 为此,内存数据库将建立针对行集和行数据的抽象。为了提高查询的效率,在内存中建立一个大的hash-table,hash-table中只支持两种数据结构:变长的list和定长 的array.list用以表示集,array表示数据行.根据建立的逻辑索引,数据库中的一个表,在hash-table中可能会存放在多处.以玩家任务表为例: chaid,missionid ... chaid和missionid一起建立了一个唯一的数据库索引,但可以为它建立两个逻辑索引,chaid和chaid,missionid. 这样,当用户上线时(假设用户id为1001),将导入所有chaid==1001的行,在hash-table中建立一个list,这个list中的每个元素都是一个array,每个array表示一个任 务记录行,list就是这个玩家所有任务的集合,如果游戏逻辑需要获取这个玩家的任务列表,可以通过以下key获取"mission,chaid,1001".当然仅有一个行集是不够的, 因为当用户的某个任务数据变动时,必须遍历list,找到正确的array再将变动更新到正确的array中.而网络游戏中最频繁的就是更新操作了,为了提高效率,为每一个 任务行建立一个逻辑索引"chaid,missionid",将任务对应的数据行也存放在hash-table中,这样如果1001号玩家希望改变他的2号任务的数据,则可以发key="mission, chaid,missionid,1001,2"后跟变更数据.来改变2号任务的数据. 下面贴出代码片段,介绍核心的存储数据结构. enum{ INT8 = 0, INT16, INT32, INT64, DOUBLE, STRING, BINARY, }; typedef struct basetype { int8_t type;//the real type void *data; }*basetype_t;struct db_type_string { struct basetype base; int32_t size; };struct db_type_binary { struct basetype base; int32_t size; }; 首先是基本的数据元素,也就是array可以存放的元素类型,分别是4种整型,double,字符串和二进制数据. enum{ DB_LIST = 1, DB_ARRAY, }; typedef struct db_element { struct refbase ref; int32_t hash_index;//index in global_table int8_t type; }*db_element_t; db_element_t db_element_acquire(db_element_t,db_element_t);void db_element_release(db_element_t*);//represent a db rowtypedef struct db_array { struct db_element base; int32_t size; basetype_t* data; }*db_array_t; db_array_t db_array_create(int32_t size); db_array_t db_array_acquire(db_array_t,db_array_t);void db_array_clear(db_array_t);//clear the datavoid db_array_release(db_array_t*);//get/set one element of the db rowbasetype_t db_array_get(db_array_t,int32_t index);void db_array_set(db_array_t,int32_t index,basetype_t);struct db_node { list_node next; db_array_t array; };//represent db row settypedef struct db_list { struct db_element base; struct link_list *l; }*db_list_t; db_list_t db_list_create(); db_list_t db_list_acquire(db_list_t,db_list_t);void db_list_release(db_list_t*); int32_t db_list_append(db_list_t,db_array_t); int32_t db_list_size(db_list_t); int32_t db_list_shrink(db_list_t); 然后是array和list的定义,他们都继承自db_element_t,而hash_table中的元素正是db_element_t.array和list都实现了引用计数,这样当所有引用都释放时,可以被正 确的销毁。这里要注意的是,一个array可能被存放在多个list中,这样,当一个数据行被删除时,必须让所有的list知道这个数据已经无效。我的做法不是在array被删 除时通知所有的list删除对应的array,而是通过db_array_clear,清除array中存放的有效数据。然后通过一个算法,定期对数据占用最多的list执行shrink,以销毁失效的 array. typedef struct global_table *global_table_t; global_table_t global_table_create(int32_t initsize);void global_table_destroy(global_table_t*); db_element_t global_table_find(global_table_t,const char *key); int32_t global_table_remove(global_table_t,const char *key); int32_t global_table_add(global_table_t,const char *key,db_element_t e);//collect unused db_element_tvoid global_table_shrink(global_table_t); 然后是hash_table的定义,只向外提供三个操作接口,分别是查找,删除和添加.对于添加操作,如果最开始往一个hash slot添加的是一个array,当再次往这个slot添加 一个array时,将会自动将slot中的元素从array提升为list,并将两个array都添加到这个list中. 下面是一些测试代码: #include |
|
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系
[邮箱地址] 删除
|