網游內存數據庫的設計(1)


網絡游戲的數據變動比較頻繁,如果每次數據變動都刷往后端數據庫,會導致數據庫不負重荷。在游戲邏輯和數據庫間提供一層緩沖服務,有利於減輕這重壓力.

首先,網絡游戲的數據在數據庫中是以表的形式保存的,每個玩家的數據占用其中的一行或幾行.以玩家基本屬性為例:

基本表: 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 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都實現了引用計數,這樣當所有引用都釋放時,可以被正

確的銷毀。這里要注意的是,一個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_t
void           global_table_shrink(global_table_t);

然后是hash_table的定義,只向外提供三個操作接口,分別是查找,刪除和添加.對於添加操作,如果最開始往一個hash slot添加的是一個array,當再次往這個slot添加

一個array時,將會自動將slot中的元素從array提升為list,並將兩個array都添加到這個list中.

下面是一些測試代碼:

#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(&gtb);
    
    return 0;
}

本篇僅僅介紹了核心的數據結構,后端的數據庫交互策略,網絡前端,備份處理和分布式多緩存將在后面慢慢介紹.

 代碼地址:https://github.com/sniperHW/kendylib dbcahce目錄

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM