kavin

浅谈网络游戏内存数据库的设计(1)

kavin 运维技术 2022-11-19 402浏览 0

首先,网络游戏的数据在数据库中是以表的形式保存的,每个玩家的数据占用其中的一行或几行.以玩家基本属性为例:

基本表: 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,
};

typedefstructbasetype
{
int8_ttype;//therealtype
void*data;
}*basetype_t;

structdb_type_string
{
structbasetypebase;
int32_tsize;
};

structdb_type_binary
{
structbasetypebase;
int32_tsize;
};

首先是基本的数据元素,也就是array可以存放的元素类型,分别是4种整型,double,字符串和二进制数据.

enum
{
DB_LIST=1,
DB_ARRAY,
};

typedefstructdb_element
{
structrefbaseref;
int32_thash_index;//indexinglobal_table
int8_ttype;
}*db_element_t;

db_element_tdb_element_acquire(db_element_t,db_element_t);
voiddb_element_release(db_element_t*);


//representadbrow
typedefstructdb_array
{
structdb_elementbase;
int32_tsize;
basetype_t*data;
}*db_array_t;


db_array_tdb_array_create(int32_tsize);
db_array_tdb_array_acquire(db_array_t,db_array_t);
voiddb_array_clear(db_array_t);//clearthedata
voiddb_array_release(db_array_t*);


//get/setoneelementofthedbrow
basetype_tdb_array_get(db_array_t,int32_tindex);
voiddb_array_set(db_array_t,int32_tindex,basetype_t);

structdb_node
{
list_nodenext;
db_array_tarray;
};

//representdbrowset
typedefstructdb_list
{
structdb_elementbase;
structlink_list*l;

}*db_list_t;

db_list_tdb_list_create();
db_list_tdb_list_acquire(db_list_t,db_list_t);
voiddb_list_release(db_list_t*);
int32_tdb_list_append(db_list_t,db_array_t);
int32_tdb_list_size(db_list_t);
int32_tdb_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.

typedefstructglobal_table*global_table_t;

global_table_tglobal_table_create(int32_tinitsize);
voidglobal_table_destroy(global_table_t*);


db_element_tglobal_table_find(global_table_t,constchar*key);
int32_tglobal_table_remove(global_table_t,constchar*key);
int32_tglobal_table_add(global_table_t,constchar*key,db_element_te);

//collectunuseddb_element_t
voidglobal_table_shrink(global_table_t);

然后是hash_table的定义,只向外提供三个操作接口,分别是查找,删除和添加.对于添加操作,如果最开始往一个hash slot添加的是一个array,当再次往这个slot添加

一个array时,将会自动将slot中的元素从array提升为list,并将两个array都添加到这个list中.

下面是一些测试代码:

#include<stdio.h>
#include"global_table.h"


intmain()
{
global_table_tgtb=global_table_create(1024);

db_array_ta1=db_array_create(3);
db_array_ta2=db_array_create(3);
db_array_ta3=db_array_create(3);
db_array_ta4=db_array_create(3);

db_array_set(a1,0,basetype_create_int32(10));
db_array_set(a1,1,basetype_create_int32(11));
db_array_set(a1,2,basetype_create_int32(12));

db_array_set(a2,0,basetype_create_int32(20));
db_array_set(a2,1,basetype_create_int32(21));
db_array_set(a2,2,basetype_create_int32(22));

db_array_set(a3,0,basetype_create_int32(30));
db_array_set(a3,1,basetype_create_int32(31));
db_array_set(a3,2,basetype_create_int32(32));

db_array_set(a4,0,basetype_create_int32(40));
db_array_set(a4,1,basetype_create_int32(41));
db_array_set(a4,2,basetype_create_int32(42));

global_table_add(gtb,"kenny",(db_element_t)a1);
global_table_add(gtb,"kenny",(db_element_t)a2);
global_table_add(gtb,"kenny",(db_element_t)a3);
global_table_add(gtb,"kenny",(db_element_t)a4);
global_table_add(gtb,"kenny1",(db_element_t)a1);
global_table_add(gtb,"kenny2",(db_element_t)a2);
global_table_add(gtb,"kenny3",(db_element_t)a3);
global_table_add(gtb,"kenny4",(db_element_t)a4);


//testsearch
db_list_tl=(db_list_t)global_table_find(gtb,"kenny");

printf("therowsizeofkenny(adb_list_t):%d\n",db_list_size(l));

printf("elementofa1:key(kenny1):");
db_array_t_a=(db_array_t)global_table_find(gtb,"kenny1");
inti=0;
for(;i<3;++i)
{
basetype_tb=db_array_get(_a,i);
printf("%d",basetype_get_int32(b));
}
printf("\n");

printf("elementofa2:key(kenny2):");
_a=(db_array_t)global_table_find(gtb,"kenny2");
i=0;
for(;i<3;++i)
{
basetype_tb=db_array_get(_a,i);
printf("%d",basetype_get_int32(b));
}

printf("\n");

printf("elementofa3:key(kenny3):");
_a=(db_array_t)global_table_find(gtb,"kenny3");
i=0;
for(;i<3;++i)
{
basetype_tb=db_array_get(_a,i);
printf("%d",basetype_get_int32(b));
}

printf("\n");

printf("elementofa4:key(kenny4):");
_a=(db_array_t)global_table_find(gtb,"kenny4");
i=0;
for(;i<3;++i)
{
basetype_tb=db_array_get(_a,i);
printf("%d",basetype_get_int32(b));
}

printf("\n");

db_array_release(&a4);
global_table_remove(gtb,"kenny4");
/*shrinkwillcausetherefcountofa4reducetozero,
*thena4willbedestroyed
*/
db_list_shrink(l);

printf("therowsizeofkenny(adb_list_t),afterremoveandshrink:%d\n",db_list_size(l));

db_array_release(&a1);
db_array_release(&a2);
db_array_release(&a3);



printf("destroyglobaltable,thiswillcauseallelementdestroyed\n");
global_table_destroy(&gtb);

return0;
}

本篇仅仅介绍了核心的数据结构,后端的数据库交互策略,网络前端,备份处理和分布式多缓存将在后面慢慢介绍.

代码地址:https://github.com/sniperHW/kendylibdbcahce目录

继续浏览有关 其他数据库 的文章
发表评论