随笔 - 119  文章 - 290  trackbacks - 0

博客搬家了哦,请移步
叫我abc

常用链接

留言簿(12)

随笔分类

我的博客

搜索

  •  

积分与排名

  • 积分 - 299319
  • 排名 - 84

最新评论

阅读排行榜

 

需求

表面上看,就是服务器崩溃重启后,还能将关键数据恢复到崩溃前的那一刻,达到这种效果,角色的金钱、装备和道具都能恢复到崩溃前的状态,不会有严重的损失。

先假定所谓的关键数据都有哪些,以及这些数据有什么特性:

1.         角色基本属性,比如姓名、年龄、性别、HPMPmoney什么的。

属性数据频繁修改,读写性强。

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----

其中sizedata buffer的长度;

data buffer是应用户请求分配的内存,其地址作为分配结果的返回值;

type id则表明了这段内存的用途。

 

通过MemRecord,一个分配器的基本管理元素就有了。

每次用户请求内存,都相应的分配一个MemRecord,包含被请求的内存,和记录这段内存的大小和用途;

每次用户释放内存,只要对内存指针做一下偏移,就能得到相应的MemRecord,然后把MemRecord放入空闲队列,留待下次请求分配。

 

MemRecord里最重要的就是type id,直接关系到是否能从共享内存中恢复分配器的状态。系统目前保留2id值:

0-       表示这段内存是主内存,在没有空闲的MemRecord的情况下,用户请求的内存都从这里分配出去;

1-       表示这段内存是已经分配过并被释放了的,标记为1MemRecord总是放进空闲队列中。

 

其他id值,则是留给用户使用的,用户必须指明他所请求的内存是什么类型/用途,以便于崩溃以后的数据恢复。

比如,struct Itemtype id可以是10struct Chatype id可以是11

 

通过MemRecordtype id,分配器就可以从共享内存中恢复状态,并把已分配给用户的内存交给用户,由用户恢复数据的逻辑意义。

2.      MemChunk

服务器上的角色和物品数量不少,因此共享内存通常都会一下子分配得比较大,比如200M。但是并没有必要一开始就把200M的文件映射到进程的内存空间上,我们可以每次只映射一小部分,如20M,这每次20M的部分,就是MemChunk

一个MemChunk最初会被初始化成一个type id值为0MemRecord,即主内存,随后用户不断的申请内存的分配和释放,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_writetest_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 阅读(6101) 评论(8)  编辑 收藏 引用

FeedBack:
# re: 崩溃后重启,用共享内存恢复你的数据 2008-11-12 22:53 肥仔
反正都是基于文件系统,直接写文件不行吗?没有与其他进程共享内存通讯,为什么要MappingFile呢?难道速度有优势?  回复  更多评论
  
# re: 崩溃后重启,用共享内存恢复你的数据 2008-11-13 01:19 Fox
很诱人的功能,先MARK一个,明天好好研究研究:D  回复  更多评论
  
# re: 崩溃后重启,用共享内存恢复你的数据 2008-11-13 08:57 LOGOS
@肥仔
你可以试一下写文件的,呵呵
我没试过  回复  更多评论
  
# re: 崩溃后重启,用共享内存恢复你的数据 2008-11-13 12:49 Jeason Zhao
共享一个文件写入性能实在很低,而且弄得操作系统的IO频繁,
文章提出的方案的确有意思,JAVA有外部的缓冲实现,这个貌似等同的
不同之处在于共享内存和Socket访问的区别  回复  更多评论
  
# re: 崩溃后重启,用共享内存恢复你的数据 2008-11-13 12:50 陈梓瀚(vczh)
mapping file爆快,其中一个优点。  回复  更多评论
  
# re: 崩溃后重启,用共享内存恢复你的数据 2008-11-13 15:22 饭中淹
可以用保护线程
把崩溃的信息记录下来,顺便把未保存的数据保存下  回复  更多评论
  
# re: 崩溃后重启,用共享内存恢复你的数据 2008-11-13 19:36 imdavid
# re: 崩溃后重启,用共享内存恢复你的数据 2008-11-13 12:50 陈梓瀚(vczh)
mapping file爆快,其中一个优点。


哈,我觉得也是.


  回复  更多评论
  
# re: 崩溃后重启,用共享内存恢复你的数据 2010-09-26 10:37 true
共享内存毕竟是一种进程间通讯技术,如果将对内存的操作全部转为对共享内存的操作,恐怕会有数量级的性能损耗,如果有dbproxy的话,可以缩短持久化数据的时间,也仅仅是将数据通过tcp连接发送给dbproxy,小概率的短时间回档我觉得可以接受的,当然最好是不崩溃,或者崩溃后不丢失数据。你们线上系统使用共享内存的效果如何?  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理