最近整理了過去一年發(fā)生過的bug,包含跟其他項目組程序朋友交流的例子, 都是大家發(fā)生過的真實營運事故。
游戲服務(wù)器程序,很多bug的原因都是共通的。抽象出了以下10點啟示, 作為checklist, 寫下來以后寫程序review時自檢:
1. 安全邊界問題 對于有界限的東西(數(shù)值,buffer空間,隊列或一切對象容器),一定要考慮越界判斷。
啟示:用snprint, strncpy等限制長度. 永遠(yuǎn)都要考慮超過邊界的情況
數(shù)值加法和乘法:考慮上限溢出;
減法:考慮負(fù)數(shù); 除法,判斷分母
2. 輸入?yún)?shù)非法
case1: 扣錢邏輯,減去一個負(fù)數(shù),變成了加錢。
case2: int型大負(fù)數(shù)相加,負(fù)溢出變成大正數(shù)
啟示:test case要全覆蓋輸入?yún)?shù)范圍, 處理各種可能的情況
3. 上下文改變錯誤
共享變量/全局變量被外部改變,這似乎很常見,而且有時很隱蔽。在異步回調(diào)的情況下更常見。
check A變量
call func_B()
....
A變量被func_B改變了, 但繼續(xù)信任A變量check的結(jié)果。
啟示:白盒復(fù)查代碼時,注意檢查調(diào)用后的變化。
減少共享變量和全局變量的使用
外部接口調(diào)用后,注意共享變量的更新和恢復(fù)
啟示:在最接近執(zhí)行的地方,檢查上下文變量。不信任調(diào)用者,如果效率不關(guān)鍵,多一遍冗余檢查沒有壞處
4. 執(zhí)行中斷
動態(tài)腳本拋異常,或者引擎層面的EINTR中斷信號,都有可能中斷代碼執(zhí)行,需要考慮函數(shù)的重入性問題。
啟示:要檢查一致性,有些邏輯不允許多次被執(zhí)行(比如發(fā)獎勵),需要有狀態(tài)變量確保只執(zhí)行1次(避免出刷bug)
推廣到異步環(huán)境(多線程,多進(jìn)程,各種回調(diào)),事務(wù)的中斷也有一個重入性問題,解決方法也只有一個:用一個唯一可辨認(rèn)的狀態(tài)變量,保證某些邏輯不會被多次執(zhí)行(比如購物應(yīng)用中,用唯一訂單號來識別,狀態(tài)改變是一次性的,當(dāng)邏輯運行多次,也不會重復(fù)加物品,或者重復(fù)扣錢了)
5. 終止條件問題--死循環(huán)
case: 異步環(huán)境中,RPC遠(yuǎn)程調(diào)用,調(diào)用成環(huán),邏輯一直不結(jié)束。
啟示:while或遞歸的終止條件,邏輯全覆蓋檢查,避免死循環(huán)。較深層次的互相調(diào)用,要注意是否出現(xiàn)了遞歸,是否有可能死循環(huán)。
6. 關(guān)聯(lián)數(shù)據(jù)操作的不一致
例子:Employee對象有company變量, Company中有employee變量,
如果操作改變其中一方,而另一方?jīng)]有改變,則造成數(shù)據(jù)不一致。
(數(shù)據(jù)庫表可以指定constrain, 關(guān)聯(lián)表刪除, 但代碼變量中需要程序員自己實現(xiàn))
雙向引用的數(shù)據(jù)一致性問題,要特別注意。
為什么要雙向引用?為了查找效率,而避免遍歷其中一方.
這個問題本質(zhì)是數(shù)據(jù)一致性問題,編程中遇到的很多bug也歸結(jié)到這個問題,比如野指針,就是因為數(shù)據(jù)結(jié)構(gòu)相互引用的操作不一致造成的。
處理這個問題,個人經(jīng)驗是,他們的attach,detach操作盡可能在同一個模塊,不要分散在多個地方隨意修改,所有修改都集中在同一級接口做。
同理適用于new, delete, malloc, free這些分配,釋放,都集中在同一層的接口/模塊文件中做,debug起來也容易;非常反感在一個地方new, 然后不知道哪個模塊去delete, 很容易泄漏或者野指針, 無論如何,想辦法傳遞這些指針,一直傳到分配他所在的模塊文件中釋放,而且new和delete的接口代碼要靠近,方便查找問題。
7. 涉及多玩家,防止筆誤傳錯參數(shù)
經(jīng)典錯誤: foreach(uid in team) some_func(usernum, xxx)
經(jīng)典錯誤:有usernum和target兩個對象,調(diào)用函數(shù)搞混了。review時要仔細(xì)檢查
8. 特殊分支忘了return
異常判斷等if分支忘了return。導(dǎo)致邏輯繼續(xù)往下走。這屬于筆誤問題,測試期間未必能留意的到。
9. 異步返回沒清變量
對于異步操作,如果在返回時清變量,這時如果不能保證把變量清掉(比如期間玩家下線無法離線修改該變量),就會出刷。
啟示:對于已獎勵標(biāo)記,一定要保證各種情況下領(lǐng)獎后能正確記錄。
10. 瞬爆容量上限
case1: 網(wǎng)絡(luò)待發(fā)送隊列,因為瞬間大量請求,塞滿拋異常,導(dǎo)致流程受影響。
case2: 大量連接請求,listen的accept沒有規(guī)定單次讀事件的accept,用了while(true), 導(dǎo)致爆機
在listen fd的讀事件回調(diào)中, 通常會accept所有新的連接請求,如果用while(true)而不設(shè)一個上限,就有可能被攻擊(想象一下客戶端也用一個死循環(huán)來做connect)。
一方面要限制單次接受的socket次數(shù), 另外各個狀態(tài)要有超時機制,踢掉不尋常的連接,以防被攻擊占盡資源。
case3: 異步情況下,要限制操作者連續(xù)頻繁的操作。(比如在請求入口處增加最少時間間隔限制,避免玩家狂點,形成雪崩效應(yīng))
(同時要考慮用戶體驗,不要讓玩家死等,可以做一個提示跳轉(zhuǎn),或者等候的動畫)
參考資料:
附上最近看的一篇文章
<Writing-reliable-online-game-services> 作者曾是魔獸爭霸和星際爭霸,battle.net的開發(fā)者,
里面講的point也是游戲里經(jīng)常遇到的可靠性問題。
http://www.codeofhonor.com/blog/wp-content/uploads/2012/04/Patrick-Wyatt-Writing-reliable-online-game-services.pdf
by Daly
網(wǎng)游服務(wù)器程序優(yōu)化要解決的最主要矛盾無非就是在保證流暢游戲體驗(響應(yīng)時間在可接受范圍)的前提下,容納更多的玩家,當(dāng)然還要保證開發(fā)的便捷性。一個靠譜的MMOG游戲服務(wù)器基本上都是多線程或多進(jìn)程的架構(gòu), 利用多個CPU核把串行處理變成并行處理,以容納更大的并發(fā)玩家規(guī)模。
然而并行處理程序會使開發(fā)的復(fù)雜度增加,一不小心很容易出一些詭異bug。為什么這樣說呢?實際環(huán)境的大部分程序,函數(shù)的執(zhí)行結(jié)果與狀態(tài)數(shù)據(jù)相關(guān)(外部狀態(tài),全局?jǐn)?shù)據(jù)),并且函數(shù)執(zhí)行可能會改變這些狀態(tài)。如果把處理模塊拆成多進(jìn)程,進(jìn)程間的這些狀態(tài)數(shù)據(jù)的一致性和處理時序,會影響到結(jié)果的正確性。多進(jìn)程狀態(tài)數(shù)據(jù)的管理,讀寫和同步更新機制,便是本文要探討的主要問題。
如果函數(shù)能變成無狀態(tài)的(結(jié)果只與輸入?yún)?shù)相關(guān)),則分拆成多進(jìn)程毫無壓力。于是業(yè)界開始探討erlang這種函數(shù)式編程語言,并有已有實際游戲項目(參看:http://www.qingliangcn.com/) 。不過筆者覺得,erlang的無狀態(tài),本質(zhì)上是把狀態(tài)數(shù)據(jù)通過函數(shù)參數(shù)傳遞,這樣意味著頻繁而大量的數(shù)據(jù)復(fù)制和傳遞,是否更適合于MMORPG開發(fā)很難說,本文不予討論,可見文章末尾參考資料。下面探討一下狀態(tài)數(shù)據(jù)在多進(jìn)程之間的問題。
為了容易描述,整個架構(gòu)如下圖
G
client <--->║ <------> A
║ <------> B
其中G表示接入網(wǎng)關(guān),負(fù)責(zé)把client協(xié)議分發(fā)到內(nèi)網(wǎng)對應(yīng)處理進(jìn)程,A,B是負(fù)責(zé)不同功能的處理進(jìn)程,client表示客戶端,玩家狀態(tài)數(shù)據(jù)只有個v和w兩個。用reqA,reqB分別表示client對A, B的處理請求,respA, respB表示A,B返回給client的處理結(jié)果。
游戲邏輯大部分情況下需要保證狀態(tài)數(shù)據(jù)的強一致性,基于過期的數(shù)據(jù)進(jìn)行處理會得到錯誤的結(jié)果(分布式數(shù)據(jù)一致性的工程問題見文末的參考資料)。舉個有點蹩腳的例子,假設(shè)client先后發(fā)出reqA, reqB兩個請求,reqA是換武器,reqB是發(fā)起攻擊,變量v是攻擊輸出量(dps)。reqB在reqA之后發(fā)出,攻擊理應(yīng)是按穿上武器后的dps數(shù)值來計算的。但多進(jìn)程情況下,卻有可能reqB先于reqA處理(比如A進(jìn)程很忙),這時reqB的邏輯會基于還沒穿上裝備時的變量v來計算結(jié)果。下面分別討論幾種解決數(shù)據(jù)一致性問題的方案。
模式一:共享內(nèi)存
適合于單機多進(jìn)程或多線程的模式。
優(yōu)點:數(shù)據(jù)只有一份,可以保證強一致性。
缺點:進(jìn)程無法擴展到多臺服務(wù)器;
需要加鎖,加鎖相當(dāng)于把處理串行化,還是有可能被某一個較忙的進(jìn)程卡住。如果精心設(shè)計和劃分?jǐn)?shù)據(jù),減少鎖的粒度可以提高性能,但細(xì)粒度的鎖(設(shè)計成類似MySQL的行級鎖),在涉及多個玩家數(shù)據(jù)的交互邏輯時,稍有不慎又容易導(dǎo)致死鎖。隨手寫一個:
假設(shè)進(jìn)程A和B同樣執(zhí)行以下類似的邏輯
foreach( user in mapA) {
lock(user);
lock(user‘s friend);
do_something();
unlock(user's friend);
unlock(user_id);
}
由于遍歷的是map, 進(jìn)程A和B中的user順序有可能交叉, 假設(shè)交叉的兩個user互為friend,就可能死鎖了。
參考資料[4]采用了這種模式的方案。
模式二:狀態(tài)數(shù)據(jù)只由一個進(jìn)程管理
把狀態(tài)數(shù)據(jù)根據(jù)游戲邏輯進(jìn)行劃分,比如變量v只由A讀寫, 變量w只由B讀寫。假如A邏輯需要用到w,則通過異步請求B獲取w。
優(yōu)點:保證強一致性;數(shù)據(jù)只有一份,無需進(jìn)程間復(fù)制更新。
缺點:異步請求增加了響應(yīng)時間(嗯,又從并行變成了串行); 異步寫起來的代碼有點ugly,到處是callback, 回來要檢查上下文,不然又是詭異bug.
適用范圍:如果狀態(tài)數(shù)據(jù)能比較好的劃分(即絕大多數(shù)情況下,某個數(shù)據(jù)只會在某個進(jìn)程的邏輯中用到),用這種方案比較適合,因為簡單。比如玩家位置只由AOI進(jìn)程管理,玩家好友由聊天進(jìn)程管理。
模式三:多個writer, 類似MVCC方案
這是完全的分布式設(shè)計。每個進(jìn)程有自己版本的狀態(tài)數(shù)據(jù),進(jìn)程間可互相同步更新, 狀態(tài)數(shù)據(jù)v分別在A,B都有一份。互相update時,根據(jù)版本信息進(jìn)行merge。
這種方案不能保證強一致性,而且merge時會有可能發(fā)生沖突,需要邏輯開發(fā)者仲裁這種沖突(比如按時間先后)。不同于互聯(lián)網(wǎng)應(yīng)用,游戲需要較強的數(shù)據(jù)一致性和實時性,這種方案比較復(fù)雜且不太可控。
模式四:Master-Slave模式
這個是對模式二的一個擴展,某個狀態(tài)數(shù)據(jù)還是只由一個進(jìn)程進(jìn)行寫操作,但其他進(jìn)程會維持一份cache進(jìn)行讀操作,比如變量v由進(jìn)程A管理,v的更新會同步到進(jìn)程B,進(jìn)程B邏輯如果要用到v,直接讀自己的cache就可以了。對于變量v
特點:這種方式也是不能保證強一致性,只能保證最終一致性。作為模式二的補充,有些數(shù)據(jù)不需要保證更新時序,根據(jù)過期數(shù)據(jù)進(jìn)行處理也可以接受(這個是代價,需要權(quán)衡玩家體驗),可以采取這種方式。而對于不能接受的,走模式二。某些需求reqA,reqB雖然先后發(fā)出,如果respA還沒反饋回來的話,即使邏輯上reqB先于reqA處理,在玩家體驗上也是可以接受的。比如reqA穿裝備, 然后reqB攻擊,但是respA還沒返回,客戶端還是看作是沒穿上裝備,這時候按照老的屬性計算攻擊值是可接受的。廣域網(wǎng)幾百毫秒的延遲,reqB要晚于reqA + respA這種概率很小了,如果真的發(fā)生,服務(wù)器已經(jīng)很卡了。
又比如聊天進(jìn)程,reqA離開場景,然后reqB發(fā)聊天消息往當(dāng)前場景頻道,需要知道當(dāng)前場景的玩家列表(假設(shè)場景玩家列表在AOI進(jìn)程管理),如果reqB先到達(dá)聊天進(jìn)程,拿到舊的場景玩家列表, 那么這個廣播就不準(zhǔn)確了。這種不一致性的代價可以忍受的話就沒問題(在這個聊天欄例子,在跳場景的瞬間發(fā)錯人了也可以忍),實際情況,進(jìn)程間通信幾個毫秒,發(fā)生這種處理時序反轉(zhuǎn)的幾率其實非常小了。
綜上,如果要設(shè)計多進(jìn)程結(jié)構(gòu),個人比較推崇模式四。這時又引申出幾個問題:狀態(tài)數(shù)據(jù)如何合理劃分?何時更新?同步給誰?
如何劃分?
有些功能很好劃分。比如聊天進(jìn)程,狀態(tài)數(shù)據(jù)只與好友列表有關(guān),這個需求可以忍受過期數(shù)據(jù),好友關(guān)系由主進(jìn)程修改,同步到聊天進(jìn)程。玩家position, 由AOI進(jìn)程管理,修改同步到主進(jìn)程,主進(jìn)程幾乎沒有需要用到position的邏輯。
但有些數(shù)據(jù)就可能很糾結(jié),比如背包數(shù)據(jù)。玩家交易,在線獎勵,戰(zhàn)斗都需要修改背包物品數(shù)據(jù),而且必須保證強一致性,否則就可能出現(xiàn)丟失或物品復(fù)制,該由誰做這個數(shù)據(jù)的管理者呢?如果AOI進(jìn)程管理,物品使用效果可以馬上生效,但是交易和在線獎勵也需要驗證背包物品,這些邏輯也放到AOI進(jìn)程么,如果放,則又牽扯出更多的變量,如果不放,則需要退化成模式2的異步請求。如果放主進(jìn)程,則使用物品后產(chǎn)生的效果不能立刻同步到AOI進(jìn)程。可以經(jīng)過仔細(xì)對比,AOI與背包數(shù)據(jù)交互的頻率遠(yuǎn)高于主進(jìn)程,于是背包數(shù)據(jù)可由AOI進(jìn)程管理。
何時更新?
兩種選擇:一有修改立馬發(fā)送更新給其他進(jìn)程;隊列buffer住所有更新,定時送出去(比如每2秒同步一次);既然是無法保證強一致性,后者性能容易優(yōu)化些。比如AOI進(jìn)程中的位置信息變化很頻繁,但主進(jìn)程對位置實時性不敏感(比如只用于持久化,掉線重上后的位置恢復(fù)),則更新間隔可以長一些,否則會有頻繁而大量的位置數(shù)據(jù)更新;定時更新也利于同步間隔內(nèi)數(shù)據(jù)修改的合并,減少同步量。
同步給誰?
某類數(shù)據(jù)有修改時,需要通知哪些進(jìn)程,意味著要維持一個映射表。可以在編碼階段,在數(shù)據(jù)定義時靜態(tài)寫死某類數(shù)據(jù)要通知哪一類功能進(jìn)程; 也可以在運行期設(shè)計成pub-sub模式(或者叫observer模式), 動態(tài)增刪訂閱者。筆者覺得前者可控一點,因為進(jìn)程要用到哪些數(shù)據(jù),在編碼階段是可以清楚規(guī)劃的,根據(jù)這個原則把數(shù)據(jù)劃分成一個個模塊,比如玩家數(shù)據(jù)分為基本角色屬性,avatar, 位置/朝向, 好友數(shù)據(jù).... 然后決定歸屬。
多進(jìn)程可以提升系統(tǒng)并發(fā)規(guī)模,但同時有各種異步調(diào)用和數(shù)據(jù)一致性問題,帶來的代價就是bug的風(fēng)險增加(尤其團隊水平不能保證個個都很高的情況下,一個菜鳥程序員就夠受了,還很難跟蹤),開發(fā)難度增大。這個需要仔細(xì)profile和實驗確定瓶頸在哪,真的跑滿CPU或者卡IO才有必要分出去,想當(dāng)然的把模塊拆分很多進(jìn)程,設(shè)計看上去很優(yōu)雅也很牛逼,往往是麻煩的開始 ——> 開發(fā)效率降低,出bug意味著啥?加班,加班,深夜運維的奪命追魂call... ...
參考資料
替代系統(tǒng)自帶的malloc/new原因無非兩個:
reason 1. 做內(nèi)存profile或查找問題
reason 2. 自定義的分配方案提高性能
不過文章[1]中說明了,替代全局new不是一個好做法. 其實要達(dá)到以上兩點目的,筆者認(rèn)為用valgrind工具鏈就可以了。
解決方案:
1. 用valgrind和massif
valgrind的memcheck做內(nèi)存泄露和bug的查找, 里面的massif工具包做內(nèi)存性能profile, 足矣。比自己山寨的一個profiler要好。
注意:tcmalloc目前還不能很好支持valgrind, 實測中jemalloc可以
2. linux下C的程序可以用wrap的方式(相當(dāng)于python的decorator)
編譯加上選項:gcc -Wl,-wrap,malloc
可以做到對malloc這個函數(shù),linker會調(diào)用__wrap_malloc代替之, 若要調(diào)用原來的malloc函數(shù)__real_malloc
缺點:依賴于編譯器支持; 對c++的new不起作用 --> 不實用
啟示:這個方法作為function裝飾器,對于調(diào)試別的問題倒有幫助。(例如不改變函數(shù)的情況下,wrap一層,輸出些調(diào)試信息)
3. 用__malloc_hook
#include <malloc.h>
void *(*__malloc_hook)(size_t size, const void *caller);
缺點:依賴GNU編譯工具鏈; 容易死循環(huán)(想利用原有malloc,要參考例子中,把原__malloc_hook變量保存起來使用,并恢復(fù)現(xiàn)場)
4. LD_PRELOAD注入.so ,替代原
環(huán)境變量LD_PRELOAD指定程序運行時優(yōu)先加載的動態(tài)連接庫,這個動態(tài)鏈接庫中的符號優(yōu)先級是最高的。標(biāo)準(zhǔn)C的各種函數(shù)都是存放在libc.so.6的文件中,在程序運行時自動鏈接。使用LD_PRELOAD后,自己編寫的malloc的加載順序高于glibc中的malloc,這樣就實現(xiàn)了替換。用法 LD_PRELOAD=" ./mymalloc.so"
缺點:在生產(chǎn)環(huán)境不現(xiàn)實。因為LD_PRELOAD相當(dāng)于庫注入,有安全性問題,是必須禁止的。(生產(chǎn)環(huán)境很多時候用-static連接)
5. 用宏或另外的函數(shù)替代new/malloc
比如定義一個宏或者指定的函數(shù),規(guī)定所有的分配釋放都調(diào)用他。這樣相當(dāng)于給項目引入了額外的代碼規(guī)則(而且是一立項就要遵循這個規(guī)則,否則該方法無效),不能很自然的new/delete, 如果分配和釋放調(diào)用得不一致,會產(chǎn)生問題的。某產(chǎn)品組就是用宏,然后加上__FILE__, __LINE__之類的信息。
有時候valgrind的效率是個問題(尤其生產(chǎn)環(huán)境),這種方案有其價值所在, 就是代碼看上去比較ugly罷了
用宏的例子:
#define _New(Type, Catergory) (Type*)MyMemController::New((new Type), #Type, 1, sizeof(Type), Catergory, __FILE__, __LINE__, false)
#define _NewArray(Type, N, Catergory) (Type*)MyMemController::New((new Type[N]), #Type, N, sizeof(Type)*(N), Catergory, __FILE__, __LINE__, true)
MALLOC的替代品:
自己寫一個malloc其實很復(fù)雜,要考慮線程安全等各種問題,性能到頭來可能更差。google 的tcmalloc, facebook使用的jemalloc. 多線程下性能較好,可以考慮使用。
缺點:筆者嘗試過。tcmalloc不能正確用valgrind,只能用自帶gperftools(運行中會core)
jemalloc可以使用valgrind,不過還沒完全驗證是否都準(zhǔn)確。
tcmalloc相關(guān):
在64位系統(tǒng)上要裝libunwind, 對x86-64架構(gòu)使用還有些問題
源碼包的INSTALL文檔里面也提到了這個問題。
CAUTION: if you install libunwind from the url above, be aware that
you may have trouble if you try to statically link your binary with
perftools: that is, if you link with 'gcc -static -lgcc_eh ...'.
This is because both libunwind and libgcc implement the same C++
exception handling APIs, but they implement them differently on
some platforms. This is not likely to be a problem on ia64, but
may be on x86-64.
主要是64位機frame-pointer的影響, 他的profile工具里的backtrace用libunwind這個庫,這個庫又有版本問題,各種囧啊....
筆者試過系統(tǒng)x86-64, freebsd,用靜態(tài)鏈接。實際用了一下,問題很多很折騰,等他fix了再說吧.
windows下可以參考:
jemalloc暫時未發(fā)現(xiàn)有什么兼容性問題,運行得挺好的。
最近組內(nèi)發(fā)表一篇小論文,是關(guān)于改進(jìn)游戲儲存系統(tǒng)的IO性能思路。老大原來早有相同的想法,并且已經(jīng)實現(xiàn)了大部分模塊,后來和老大一同努力,新的儲存引擎終于逐步完善。在外服環(huán)境跑了兩個多月,性能和可靠性得到了明顯的提升。具體的細(xì)節(jié)就不方便發(fā)表了,實踐證明,用binlog來做MMORPG的數(shù)據(jù)儲存是行得通的。
幾個事實:
1. 磁盤IO的瓶頸在尋道,順序?qū)懶阅鼙入S機寫性能高一個數(shù)量級。
目前典型硬盤的順序?qū)懭胨俣却蠹s是60MB/s , 而尋道時間在5~8ms (200次/秒)。可以看到硬盤IO的主要瓶頸在于磁頭尋道,也就是隨機寫。在linux開發(fā)服(非虛擬機,Xeon 3.0G 4核/16G內(nèi)存)上做了一個benchmark。
順序?qū)?/span>50MB: 700ms
寫5000個文件,每個10KB(共50MB): 12秒
10000次隨機寫,每次1KB(共10MB): 21秒
2. 游戲數(shù)據(jù)都是K-V數(shù)據(jù),關(guān)系查詢需求極少;k-v數(shù)據(jù)的update很頻繁(實測是每玩家每5秒一次修改)
3. MMORPG單服的玩家同時在線數(shù)量是10K級別, 這個數(shù)量級可以有效估算binlog的規(guī)模,使得方案可行。
一般MMORPG系統(tǒng)的存盤策略: 定時存盤。就是過一段時間(比如5分鐘)把在線有修改過的玩家數(shù)據(jù),整個snapshot存下去(mysql也好,文件系統(tǒng)也好)。這樣有兩個主要問題:一到保存點,IO隨機寫暴增,玩家卡機;如果系統(tǒng)down機, 數(shù)據(jù)就會有幾分鐘的回檔。而性能和數(shù)據(jù)可靠性兩則是矛盾的,存盤間隔過小,玩家卡機,過大,故障后數(shù)據(jù)回檔時間長。需知現(xiàn)在的MMORPG,貴價武器價值都成千上萬RMB,數(shù)據(jù)可靠性對游戲營運影響還是很大的。
so, 可以用定制的binlog來記錄玩家數(shù)據(jù),也就是說,不記錄整個snapshot,而是每個k-v變化時記錄opcode馬上寫入binlog文件, binlog的格式根據(jù)游戲情況可以高度定制,盡量減少空間。由于是順序?qū)懀阅芸梢苑浅8摺H绻鹍own機,可以根據(jù)binlog來恢復(fù),基本上沒有回檔。不過要解決一個問題:binlog增長過大 --> 崩潰恢復(fù)時間過程 & binlog文件本身損壞的風(fēng)險增大 & 磁盤空間用光。因此binlog需要有rotate機制, rotate的時候需要存一次在線玩家數(shù)據(jù)的snapshot, 這樣舊的binlog就可以存到遠(yuǎn)處或者丟棄。rotate的過程中需要考慮恢復(fù)時玩家數(shù)據(jù)一致性和完備性等等一系列細(xì)節(jié)問題,后來一一解決了。
這是最近做的成就感的事。幾年沒寫blog了,筆記都記在evernote里,最近又想在公開的地方寫點東西,發(fā)個文紀(jì)念一下。
摘要: 很多程序都需要處理一系列定時事件, 本文就見過的程序中,幾種實現(xiàn)Timer的方法。用到的數(shù)據(jù)結(jié)構(gòu)一般有鏈表, 堆, RB樹,hash table等,還有一些比較優(yōu)化的方法。
閱讀全文