淺談網(wǎng)絡(luò)游戲內(nèi)存數(shù)據(jù)庫的設(shè)計(jì)(1)
首先,網(wǎng)絡(luò)游戲的數(shù)據(jù)在數(shù)據(jù)庫中是以表的形式保存的,每個(gè)玩家的數(shù)據(jù)占用其中的一行或幾行.以玩家基本屬性為例:
基本表: chainfo
表結(jié)構(gòu):chaid,chaname,hp,mp,maxhp,maxmp ...
為此,內(nèi)存數(shù)據(jù)庫將建立針對(duì)行集和行數(shù)據(jù)的抽象。為了提高查詢的效率,在內(nèi)存中建立一個(gè)大的hash-table,hash-table中只支持兩種數(shù)據(jù)結(jié)構(gòu):變長的list和定長
的array.list用以表示集,array表示數(shù)據(jù)行.根據(jù)建立的邏輯索引,數(shù)據(jù)庫中的一個(gè)表,在hash-table中可能會(huì)存放在多處.以玩家任務(wù)表為例:
chaid,missionid ...
chaid和missionid一起建立了一個(gè)唯一的數(shù)據(jù)庫索引,但可以為它建立兩個(gè)邏輯索引,chaid和chaid,missionid.
這樣,當(dāng)用戶上線時(shí)(假設(shè)用戶id為1001),將導(dǎo)入所有chaid==1001的行,在hash-table中建立一個(gè)list,這個(gè)list中的每個(gè)元素都是一個(gè)array,每個(gè)array表示一個(gè)任
務(wù)記錄行,list就是這個(gè)玩家所有任務(wù)的集合,如果游戲邏輯需要獲取這個(gè)玩家的任務(wù)列表,可以通過以下key獲取"mission,chaid,1001".當(dāng)然僅有一個(gè)行集是不夠的,
因?yàn)楫?dāng)用戶的某個(gè)任務(wù)數(shù)據(jù)變動(dòng)時(shí),必須遍歷list,找到正確的array再將變動(dòng)更新到正確的array中.而網(wǎng)絡(luò)游戲中最頻繁的就是更新操作了,為了提高效率,為每一個(gè)
任務(wù)行建立一個(gè)邏輯索引"chaid,missionid",將任務(wù)對(duì)應(yīng)的數(shù)據(jù)行也存放在hash-table中,這樣如果1001號(hào)玩家希望改變他的2號(hào)任務(wù)的數(shù)據(jù),則可以發(fā)key="mission,
chaid,missionid,1001,2"后跟變更數(shù)據(jù).來改變2號(hào)任務(wù)的數(shù)據(jù).
下面貼出代碼片段,介紹核心的存儲(chǔ)數(shù)據(jù)結(jié)構(gòu).
- 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;
- };
首先是基本的數(shù)據(jù)元素,也就是array可以存放的元素類型,分別是4種整型,double,字符串和二進(jìn)制數(shù)據(jù).
- 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 row
- typedef 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 data
- void db_array_release(db_array_t*);
- //get/set one element of the db row
- basetype_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 set
- typedef 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都實(shí)現(xiàn)了引用計(jì)數(shù),這樣當(dāng)所有引用都釋放時(shí),可以被正
確的銷毀。這里要注意的是,一個(gè)array可能被存放在多個(gè)list中,這樣,當(dāng)一個(gè)數(shù)據(jù)行被刪除時(shí),必須讓所有的list知道這個(gè)數(shù)據(jù)已經(jīng)無效。我的做法不是在array被刪
除時(shí)通知所有的list刪除對(duì)應(yīng)的array,而是通過db_array_clear,清除array中存放的有效數(shù)據(jù)。然后通過一個(gè)算法,定期對(duì)數(shù)據(jù)占用最多的list執(zhí)行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_t
- void global_table_shrink(global_table_t);
然后是hash_table的定義,只向外提供三個(gè)操作接口,分別是查找,刪除和添加.對(duì)于添加操作,如果最開始往一個(gè)hash slot添加的是一個(gè)array,當(dāng)再次往這個(gè)slot添加
一個(gè)array時(shí),將會(huì)自動(dòng)將slot中的元素從array提升為list,并將兩個(gè)array都添加到這個(gè)list中.
下面是一些測(cè)試代碼:
- #include <stdio.h>
- #include "global_table.h"
- int main()
- {
- global_table_t gtb = global_table_create(1024);
- db_array_t a1 = db_array_create(3);
- db_array_t a2 = db_array_create(3);
- db_array_t a3 = db_array_create(3);
- db_array_t a4 = 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);
- //test search
- db_list_t l = (db_list_t)global_table_find(gtb,"kenny");
- printf("the row size of kenny(a db_list_t):%d\n",db_list_size(l));
- printf("element of a1:key(kenny1):");
- db_array_t _a = (db_array_t)global_table_find(gtb,"kenny1");
- int i = 0;
- for( ; i < 3; ++i)
- {
- basetype_t b = db_array_get(_a,i);
- printf("%d ",basetype_get_int32(b));
- }
- printf("\n");
- printf("element of a2:key(kenny2):");
- _a = (db_array_t)global_table_find(gtb,"kenny2");
- i = 0;
- for( ; i < 3; ++i)
- {
- basetype_t b = db_array_get(_a,i);
- printf("%d ",basetype_get_int32(b));
- }
- printf("\n");
- printf("element of a3:key(kenny3):");
- _a = (db_array_t)global_table_find(gtb,"kenny3");
- i = 0;
- for( ; i < 3; ++i)
- {
- basetype_t b = db_array_get(_a,i);
- printf("%d ",basetype_get_int32(b));
- }
- printf("\n");
- printf("element of a4:key(kenny4):");
- _a = (db_array_t)global_table_find(gtb,"kenny4");
- i = 0;
- for( ; i < 3; ++i)
- {
- basetype_t b = db_array_get(_a,i);
- printf("%d ",basetype_get_int32(b));
- }
- printf("\n");
- db_array_release(&a4);
- global_table_remove(gtb,"kenny4");
- /* shrink will cause the refcount of a4 reduce to zero,
- * then a4 will be destroyed
- */
- db_list_shrink(l);
- printf("the row size of kenny(a db_list_t),after remove and shrink: %d\n",db_list_size(l));
- db_array_release(&a1);
- db_array_release(&a2);
- db_array_release(&a3);
- printf("destroy global table,this will cause all element destroyed\n");
- global_table_destroy(>b);
- return 0;
- }
本篇僅僅介紹了核心的數(shù)據(jù)結(jié)構(gòu),后端的數(shù)據(jù)庫交互策略,網(wǎng)絡(luò)前端,備份處理和分布式多緩存將在后面慢慢介紹.
代碼地址:https://github.com/sniperHW/kendylib dbcahce目錄
原文鏈接:http://www.cnblogs.com/sniperHW/archive/2012/08/11/2634052.html
【編輯推薦】