网游内存数据库的设计(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 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 <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目录

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

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

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏知识分享

51单片机开发板(W25Q16学习)

---恢复内容开始--- 今天测试开发板的W25Q16(16Mbit--Flash)写一篇文章备忘一下 先说一下容量的单位 计算机存储单位一般用B,KB、MB、...

3474
来自专栏北京马哥教育

运维工程师笔试真题:美团点评 2017 春招真题

1974
来自专栏帮你学MatLab

Robotics System Toolbox路径规划

filePath = fullfile(fileparts(which('PathPlanningExample')),'data','exampleMaps....

2312
来自专栏哲学驱动设计

性能优化总结(二):聚合SQL

    本篇主要讲如何使用一句较复杂的SQL来加载整个聚合对象,以达到最小化数据库连接次数。主要是解释其中的原理。 LazyLoad及其缺点     相信越来越...

2126
来自专栏杨建荣的学习笔记

通过编程控制CPU利用率(r4笔记第69天)

今天想起一个几年前学习过的程序,是在《编程之美》中提到的,是作为当时微软的面试题,写一个程序来控制CPU的利用率保持在50%,进一步延伸,能够写出程序来画出CP...

3425
来自专栏雨过天晴

原 提取微信公众平台模板消息字段

1874
来自专栏文渊之博

SQL优化技巧--远程连接对象引起的CTE性能问题

背景    最近SSIS的开发过程中遇到几个问题。其中使用CTE时,遇到一个远程连接对象,结果导致严重的性能问题,为了应急我就修改了代码。   之前我写了一篇介...

1797
来自专栏Golang语言社区

网游内存数据库的设计(1)

网络游戏的数据变动比较频繁,如果每次数据变动都刷往后端数据库,会导致数据库不负重荷。在游戏逻辑和数据库间提供一层缓冲服务,有利于减轻这重压力. 首先,网络游戏的...

3716
来自专栏进击的程序猿

如何组织PHP中的异常

本文的主题是怎么组织php的异常?在大型项目中异常往往被我们忽略,但是如果前期没有很好的规划好,越到项目后期,重构的成本会越大。

1391
来自专栏Java社区

Java核心技术讲解学习

1443

扫码关注云+社区

领取腾讯云代金券