需求
表面上看,就是服務(wù)器崩潰重啟后,還能將關(guān)鍵數(shù)據(jù)恢復(fù)到崩潰前的那一刻,達(dá)到這種效果,角色的金錢、裝備和道具都能恢復(fù)到崩潰前的狀態(tài),不會有嚴(yán)重的損失。
先假定所謂的關(guān)鍵數(shù)據(jù)都有哪些,以及這些數(shù)據(jù)有什么特性:
1. 角色基本屬性,比如姓名、年齡、性別、HP、MP和money什么的。
屬性數(shù)據(jù)頻繁修改,讀寫性強(qiáng)。
2. 角色物品,比如裝備、道具、技能和寵物等。
物品數(shù)據(jù)有歸屬性,總是屬于某個角色,或者無主被刪除。這種數(shù)據(jù)的歸屬性容易轉(zhuǎn)移,經(jīng)常需要分配和釋放。
這些關(guān)鍵數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)通常是這樣子的:
struct Item
{
int id;
int belong;
int data[32];
};
struct Cha
{
int id;
int atb[ 128 ];
Item* item_head;
};
能滿足易于修改,易于分配和釋放,并能像上面的數(shù)據(jù)結(jié)構(gòu)那樣組織起來,只有內(nèi)存能做到。但是,在崩潰重啟后,普通內(nèi)存屬于不可恢復(fù)資源,因此就要考慮共享內(nèi)存了。
共享內(nèi)存
共享內(nèi)存其實(shí)是文件,所以會有“指針變量改成偏移變量更合適”的這種想法,但是我期望能像操縱普通內(nèi)存上的數(shù)據(jù)一樣操縱共享內(nèi)存上的數(shù)據(jù),指針是必須使用的。
另外,在需求上,角色的屬性數(shù)據(jù)和角色的物品數(shù)據(jù)都是數(shù)以千計(jì)、面臨不斷的分配和釋放的,而在文件上管理和維護(hù)離散的小塊的數(shù)據(jù)空間,不能像對付普通內(nèi)存那樣直接。
因此,要想用共享內(nèi)存實(shí)現(xiàn)上述需求,需要一個方案。
方案
要靈活的分配和釋放空間,就要打造一個基于共享內(nèi)存的內(nèi)存分配器,不僅如此,還必須能在崩潰后從共享內(nèi)存文件中重建這個分配器。
1. MemRecord
分配器用來記錄一次內(nèi)存分配的相關(guān)信息,其結(jié)構(gòu)如下:
----size | type id | data buffer----
其中size是data buffer的長度;
data buffer是應(yīng)用戶請求分配的內(nèi)存,其地址作為分配結(jié)果的返回值;
type id則表明了這段內(nèi)存的用途。
通過MemRecord,一個分配器的基本管理元素就有了。
每次用戶請求內(nèi)存,都相應(yīng)的分配一個MemRecord,包含被請求的內(nèi)存,和記錄這段內(nèi)存的大小和用途;
每次用戶釋放內(nèi)存,只要對內(nèi)存指針做一下偏移,就能得到相應(yīng)的MemRecord,然后把MemRecord放入空閑隊(duì)列,留待下次請求分配。
MemRecord里最重要的就是type id,直接關(guān)系到是否能從共享內(nèi)存中恢復(fù)分配器的狀態(tài)。系統(tǒng)目前保留2個id值:
0- 表示這段內(nèi)存是主內(nèi)存,在沒有空閑的MemRecord的情況下,用戶請求的內(nèi)存都從這里分配出去;
1- 表示這段內(nèi)存是已經(jīng)分配過并被釋放了的,標(biāo)記為1的MemRecord總是放進(jìn)空閑隊(duì)列中。
其他id值,則是留給用戶使用的,用戶必須指明他所請求的內(nèi)存是什么類型/用途,以便于崩潰以后的數(shù)據(jù)恢復(fù)。
比如,struct Item的type id可以是10,struct Cha的type id可以是11。
通過MemRecord和type id,分配器就可以從共享內(nèi)存中恢復(fù)狀態(tài),并把已分配給用戶的內(nèi)存交給用戶,由用戶恢復(fù)數(shù)據(jù)的邏輯意義。
2. MemChunk
服務(wù)器上的角色和物品數(shù)量不少,因此共享內(nèi)存通常都會一下子分配得比較大,比如200M。但是并沒有必要一開始就把200M的文件映射到進(jìn)程的內(nèi)存空間上,我們可以每次只映射一小部分,如20M,這每次20M的部分,就是MemChunk。
一個MemChunk最初會被初始化成一個type id值為0的MemRecord,即主內(nèi)存,隨后用戶不斷的申請內(nèi)存的分配和釋放,MemChunk就分成了一連串的MemRecord集合。
一個分配器的重建,其實(shí)就是旗下所有MemChunk的重建;而MemChunk的重建,就是區(qū)分不同的MemRecord的過程。
3. FileHeader
再次強(qiáng)調(diào)共享內(nèi)存其實(shí)是一個文件,既然在文件上有組織的保存數(shù)據(jù),最好相應(yīng)的有一個文件頭描述這種組織。
在當(dāng)前方案下,文件頭至少要包含以下幾條,以便于從整個共享內(nèi)存中重建分配器:
1. 文件大小
2. 每個MemChunk的大小
3. 已經(jīng)分配出來的MemChunk的數(shù)量
因?yàn)?/span>FileHeader的基址和大小都是固定的,一開始就可以讀取的,因此是保存分配器整體信息的不二選擇。
實(shí)驗(yàn)程序
這一篇的表達(dá)能力有限,我也看出來自己完全沒法把這個東西講解清楚,所以,代碼才是最好的表述。
點(diǎn)擊這里下載代碼
以下的程序按其啟動順序逐個簡述:
1. test_daemon,共享內(nèi)存守護(hù)進(jìn)程。啟動后創(chuàng)建一塊200M的共享內(nèi)存,然后死循環(huán);
2. test_write,啟動后讀取200M的共享內(nèi)存,然后創(chuàng)建一批角色數(shù)據(jù)和物品數(shù)據(jù),并隨機(jī)的刪除這些角色和物品;
3. test_read,啟動后讀取200M的共享內(nèi)存,并從中恢復(fù)test_write分配出來的角色和物品。
test_write和test_read在最后都dump了當(dāng)前的角色和物品到文件上,比較2個文件就能判斷方案是否正確了。
遇上的問題
1. 字節(jié)對齊問題
說來慚愧,計(jì)算MemRecord中到最后一個變量m_data前的大小,我居然用sizeof(MemRecord)-1,結(jié)果出錯了。
因?yàn)榇嬖谧止?jié)對齊,所以大小應(yīng)該是m_data的偏移量才對。
2. MapViewOfFile的偏移量對齊問題
如果不是用到multi view,根本不用關(guān)心這個問題。每一個view的起始偏移地址,不是隨意的,必須是某個值的整數(shù)倍,我這里是65536。具體的值,通過SYSTEM_INFO. dwAllocationGranularity讀取。
posted on 2008-11-12 21:27
LOGOS 閱讀(6295)
評論(8) 編輯 收藏 引用