需求
表面上看,就是服务器崩溃重启后,还能将关键数据恢复到崩溃前的那一刻,达到这种效果,角色的金钱、装备和道具都能恢复到崩溃前的状态,不会有严重的损失。
先假定所谓的关键数据都有哪些,以及这些数据有什么特性:
1. 角色基本属性,比如姓名、年龄、性别、HP、MP和money什么的。
属性数据频繁修改,读写性强。
2. 角色物品,比如装备、道具、技能和宠物等。
物品数据有归属性,总是属于某个角色,或者无主被删除。这种数据的归属性容易转移,经常需要分配和释放。
这些关键数据的数据结构通常是这样子的:
struct Item
{
int id;
int belong;
int data[32];
};
struct Cha
{
int id;
int atb[ 128 ];
Item* item_head;
};
能满足易于修改,易于分配和释放,并能像上面的数据结构那样组织起来,只有内存能做到。但是,在崩溃重启后,普通内存属于不可恢复资源,因此就要考虑共享内存了。
共享内存
共享内存其实是文件,所以会有“指针变量改成偏移变量更合适”的这种想法,但是我期望能像操纵普通内存上的数据一样操纵共享内存上的数据,指针是必须使用的。
另外,在需求上,角色的属性数据和角色的物品数据都是数以千计、面临不断的分配和释放的,而在文件上管理和维护离散的小块的数据空间,不能像对付普通内存那样直接。
因此,要想用共享内存实现上述需求,需要一个方案。
方案
要灵活的分配和释放空间,就要打造一个基于共享内存的内存分配器,不仅如此,还必须能在崩溃后从共享内存文件中重建这个分配器。
1. MemRecord
分配器用来记录一次内存分配的相关信息,其结构如下:
----size | type id | data buffer----
其中size是data buffer的长度;
data buffer是应用户请求分配的内存,其地址作为分配结果的返回值;
type id则表明了这段内存的用途。
通过MemRecord,一个分配器的基本管理元素就有了。
每次用户请求内存,都相应的分配一个MemRecord,包含被请求的内存,和记录这段内存的大小和用途;
每次用户释放内存,只要对内存指针做一下偏移,就能得到相应的MemRecord,然后把MemRecord放入空闲队列,留待下次请求分配。
MemRecord里最重要的就是type id,直接关系到是否能从共享内存中恢复分配器的状态。系统目前保留2个id值:
0- 表示这段内存是主内存,在没有空闲的MemRecord的情况下,用户请求的内存都从这里分配出去;
1- 表示这段内存是已经分配过并被释放了的,标记为1的MemRecord总是放进空闲队列中。
其他id值,则是留给用户使用的,用户必须指明他所请求的内存是什么类型/用途,以便于崩溃以后的数据恢复。
比如,struct Item的type id可以是10,struct Cha的type id可以是11。
通过MemRecord和type id,分配器就可以从共享内存中恢复状态,并把已分配给用户的内存交给用户,由用户恢复数据的逻辑意义。
2. MemChunk
服务器上的角色和物品数量不少,因此共享内存通常都会一下子分配得比较大,比如200M。但是并没有必要一开始就把200M的文件映射到进程的内存空间上,我们可以每次只映射一小部分,如20M,这每次20M的部分,就是MemChunk。
一个MemChunk最初会被初始化成一个type id值为0的MemRecord,即主内存,随后用户不断的申请内存的分配和释放,MemChunk就分成了一连串的MemRecord集合。
一个分配器的重建,其实就是旗下所有MemChunk的重建;而MemChunk的重建,就是区分不同的MemRecord的过程。
3. FileHeader
再次强调共享内存其实是一个文件,既然在文件上有组织的保存数据,最好相应的有一个文件头描述这种组织。
在当前方案下,文件头至少要包含以下几条,以便于从整个共享内存中重建分配器:
1. 文件大小
2. 每个MemChunk的大小
3. 已经分配出来的MemChunk的数量
因为FileHeader的基址和大小都是固定的,一开始就可以读取的,因此是保存分配器整体信息的不二选择。
实验程序
这一篇的表达能力有限,我也看出来自己完全没法把这个东西讲解清楚,所以,代码才是最好的表述。
点击这里下载代码
以下的程序按其启动顺序逐个简述:
1. test_daemon,共享内存守护进程。启动后创建一块200M的共享内存,然后死循环;
2. test_write,启动后读取200M的共享内存,然后创建一批角色数据和物品数据,并随机的删除这些角色和物品;
3. test_read,启动后读取200M的共享内存,并从中恢复test_write分配出来的角色和物品。
test_write和test_read在最后都dump了当前的角色和物品到文件上,比较2个文件就能判断方案是否正确了。
遇上的问题
1. 字节对齐问题
说来惭愧,计算MemRecord中到最后一个变量m_data前的大小,我居然用sizeof(MemRecord)-1,结果出错了。
因为存在字节对齐,所以大小应该是m_data的偏移量才对。
2. MapViewOfFile的偏移量对齐问题
如果不是用到multi view,根本不用关心这个问题。每一个view的起始偏移地址,不是随意的,必须是某个值的整数倍,我这里是65536。具体的值,通过SYSTEM_INFO. dwAllocationGranularity读取。
posted on 2008-11-12 21:27
LOGOS 阅读(6151)
评论(8) 编辑 收藏 引用