• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            Daly的游戲人生

            2012年11月30日 #

            服務(wù)器程序常見bug總結(jié)

               最近整理了過去一年發(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



            posted @ 2012-11-30 14:14 Daly 閱讀(2352) | 評論 (5)編輯 收藏

            2012年8月5日 #

            網(wǎng)游服務(wù)器多進(jìn)程架構(gòu)的思考

                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... ...
            參考資料
            [1] 當(dāng)webgame邂逅erlang.  明朝網(wǎng)絡(luò)的慶亮。 http://www.slideshare.net/qingliangcn/webgameerlang-8241397#btnNext
            [2] 陳杰談網(wǎng)游服務(wù)器后端技術(shù).  西山居陳杰的ppt, 講多進(jìn)程架構(gòu)下的尋路算法 http://timyang.net/architecture/game-backend/
            [3] nosql ecosystem. 13節(jié)講述分布式系統(tǒng)的數(shù)據(jù)一致性問題
            [4] 結(jié)構(gòu)化數(shù)據(jù)的共享內(nèi)存, 云風(fēng) http://blog.codingnow.com/2011/12/dev_note_6.html

            posted @ 2012-08-05 17:01 Daly 閱讀(4268) | 評論 (3)編輯 收藏

            2012年7月17日 #

            網(wǎng)絡(luò)游戲不同類型的技術(shù)分類

                不同的游戲類型需要有不同的技術(shù)設(shè)計,尤其服務(wù)器端,沒有一個通用游戲引擎可以適應(yīng)所有類型。所以大部分游戲的服務(wù)器端引擎都是根據(jù)產(chǎn)品需求手工打造,商業(yè)引擎通常也得經(jīng)過別扭的折騰改造才能用得比較好(比如Bigworld ^_^ )。 本文對常見的幾種網(wǎng)絡(luò)游戲的服務(wù)端技術(shù)做一個技術(shù)特點的分類。
                約束性能和容量規(guī)模的因素歸納起來是:數(shù)據(jù)共享域,消息廣播域,運算共享域。這幾個因素涉及的對象數(shù)量會直接影響了架構(gòu)涉及,下面分別來說。
             
                 分類特征:
                 玩家數(shù)據(jù)總量,同時在線數(shù)
                 這里是指,玩家登錄后,需要從多大規(guī)模的數(shù)據(jù)中讀取自己的數(shù)據(jù)。social game全局共享的角色數(shù)據(jù)(統(tǒng)一世界),隨著玩家數(shù)量增多線性增長,往往單機儲存不能滿足需求,需要分布式儲存技術(shù)。而對于傳統(tǒng)MMORPG,由于是分服(服務(wù)器之間是平衡世界,角色數(shù)據(jù)相互獨立),相當(dāng)于天然地用服務(wù)器id做了數(shù)據(jù)分區(qū)(而不需要像分布式儲存那樣考慮分區(qū)算法的擴展性問題),除了登錄信息,沒有全局?jǐn)?shù)據(jù),單機儲存即可解決問題。
                 AOI范圍與頻率
                 這里指的是游戲過程中,即時廣播涉及的對象數(shù)量以及消息密度,比如同屏玩家數(shù)。競技和休閑類游戲通常比較小,而傳統(tǒng)MMORPG通常較大。
                 范圍和頻率還影響client間傳輸消息的方式,通常有兩種:client間直傳(P2P); 中心服轉(zhuǎn)發(fā)。從延時來看,廣域網(wǎng)游戲這兩者區(qū)別到不大(前提是中心服不卡,其實可以看做一個路由器)。如果廣播范圍 x 頻率較大,中心服的帶寬成本很高(嗯嗯,帶寬是很貴的)。如果采用純P2P,則需要考慮客戶端作弊問題。當(dāng)然還可以用混合的方法,即中心服監(jiān)督下的P2P,比如有些競技游戲在服務(wù)端也做一層校驗,查出外掛的可能,當(dāng)然這個不容易實現(xiàn)得好。 大部分情況下,服務(wù)器監(jiān)督一切,最省心。

                 AOI跳轉(zhuǎn)方式
                 是指在不同運算共享域和廣播域直接切換的方式,分為無縫和跳轉(zhuǎn)點。舉個例子,休閑游戲和競技游戲通常是玩家主動點擊進(jìn)入或退出房間/頻道,不同房間/頻道分隔了廣播域。而即時制MMORPG(比如WOW)區(qū)域之間是無邊界的。這兩者區(qū)別影響運算性能的擴展性,即實現(xiàn)多進(jìn)程處理的技術(shù)難度。對于前者可以很容易實現(xiàn)多進(jìn)程分擔(dān)處理任務(wù)。后者的無縫AOI,要實現(xiàn)多進(jìn)程的話,在邊界處需要較復(fù)雜的進(jìn)程數(shù)據(jù)同步技術(shù)。
                 實時性/同步要求
                 網(wǎng)絡(luò)延時的前提下,同步方案主要是用戶體驗和數(shù)據(jù)正確性之間的權(quán)衡。競技和動作游戲強調(diào)打擊感和位置準(zhǔn)確性,需要很高的同步要求。很多游戲采用幀同步方案,即一旦對應(yīng)幀數(shù)據(jù)未到,卡住整個客戶端(dota的等待連線)。也有采用運動補償?shù)姆绞?也稱追影),即客戶端預(yù)判,當(dāng)和服務(wù)端位置不一致時,通過加速等方式平滑追上。為了減少延時帶來的影響,一部分計算放在客戶端,關(guān)鍵計算等待服務(wù)器返回。在等待服務(wù)器返回結(jié)果的過程中,通常結(jié)合美術(shù)和技術(shù)手段"欺騙"玩家的視覺(比如起手動作),達(dá)到較好的體驗。

            Social game
                 玩法:策略經(jīng)營類,好友互動
                 玩家數(shù)量大,冷數(shù)據(jù)總量大 (海量玩家同一交互域),同時在線高;
                 AOI范圍中。頻率低。消息在好友之間分發(fā)(好友數(shù)量一般在一百以內(nèi))
                 實時性/同步要求:低,不需要實時。運算較簡單(看成是海量數(shù)據(jù)的CURD應(yīng)用)
                 技術(shù)特點:跟微博,QQ群等傳統(tǒng)互聯(lián)網(wǎng)應(yīng)用比較接近:數(shù)據(jù)量大,AOI范圍中,實時要求低。主要難點在于讀寫規(guī)模大,總數(shù)據(jù)量大,cache熱度不明顯(無明顯熱數(shù)據(jù))。
                 性能擴展:依賴于分布式儲存和讀寫技術(shù),與一般社交網(wǎng)絡(luò)技術(shù)類似。由于需要預(yù)先加好友,設(shè)定好友數(shù)量上限可以限制數(shù)據(jù)廣播的規(guī)模。

            休閑棋牌類游戲
                 玩家數(shù)量大;冷數(shù)據(jù)總量大;但登錄后通常進(jìn)入房間。
                 房間之間分隔了廣播域,游戲局之間玩家互相獨立(除了聊天頻道)。意味著較容易根據(jù)房間和游戲分服/進(jìn)程,性能擴展容易。
                 AOI范圍低(一局游戲的幾個人),實時性一般。運算一般(棋牌算法計算)。
                 架構(gòu)上通常分前端(登錄和房間邏輯)和后端(具體一局游戲),分服設(shè)計較容易。通常一個前端要對應(yīng)很多種不同類型的后端邏輯(各種類型游戲),需要制定一個容易開發(fā)和接入的框架。
                 容量擴展:由于游戲局的獨立性,分進(jìn)程/分服做運算擴展比較簡單。 通過分區(qū)分房間限制了數(shù)據(jù)廣播規(guī)模。

            競技類游戲
                 玩法:dota, FPS, 格斗動作類
                 AOI范圍低(10人以下),交互頻率高
                 實時交互和同步要求極高(技術(shù)難點)
                 容量擴展:通常與休閑類一樣,先進(jìn)入房間(有些叫頻道)限制數(shù)據(jù)廣播規(guī)模。不同房間互相獨立,因此也較容易通過增加進(jìn)程/服務(wù)器分散運算規(guī)模。

            即時制MMORPG
                 通常技能有cooldown, 玩家之間可以穿插(沒有動態(tài)碰撞檢測),同步要求低于動作類和dota網(wǎng)游。單服同時在線人數(shù)有限(1w人左右),邏輯復(fù)雜, IO通常單機就可搞定。AOI通常是運算瓶頸,要提高容量就要分進(jìn)程或分線程。對于區(qū)域間無縫世界,在邊界處的對象,由于互相可見且可戰(zhàn)斗,分管兩個區(qū)域的進(jìn)程間需要較復(fù)雜的同步機制。比如bigworld用的是對象代理技術(shù),即在原區(qū)域是real對象,對端區(qū)域建立一個ghost(代理對象), 對real對象的所有狀態(tài)改變即時同步到對端進(jìn)程,反之對ghost操作也同步到real。也就是說玩家A在兩個進(jìn)程都有自己的副本,且都可寫,需要借鑒分布式技術(shù)中,多Writer的數(shù)據(jù)一致性設(shè)計。
                 對MMORPG來說,單服人數(shù)越高,游戲的社區(qū)性和人氣感就越強, 但人數(shù)越多,就越容易卡住服務(wù)器。現(xiàn)在都是多核的世界,基本上都是多線程/進(jìn)程的架構(gòu)了,多writer/reader, 數(shù)據(jù)同步,鎖這些是常見技術(shù)考量點。一般功能模塊交互性不強,分進(jìn)程/線程難度不大,但AOI這塊分進(jìn)程要比較折騰。

                 題外話
                 這兩年公司的校園招聘,程序員的title是虛擬世界架構(gòu)師(汗 -_-!)。真正的虛擬世界應(yīng)該是:數(shù)據(jù)規(guī)模大,AOI范圍大,實時交互。以上還沒有一種游戲同時符合幾個特征,都不同程度通過分區(qū)/分服/分房間/分場景/分頻道分隔了單個進(jìn)程的處理規(guī)模,單臺服務(wù)器的數(shù)據(jù)規(guī)模和帶寬規(guī)模。當(dāng)然,即使技術(shù)上可行,玩家腦子同時能處理的對象數(shù)比電腦要差多了(呃,試想數(shù)萬人同處一個場景,然后走來走去,這個有游戲性可言?),這時候瓶頸不在服務(wù)器,超密集的角色,客戶端的渲染效率變成瓶頸。

            posted @ 2012-07-17 09:45 Daly 閱讀(2850) | 評論 (5)編輯 收藏

            2012年7月2日 #

            替代系統(tǒng)malloc/new--選擇合適的內(nèi)存跟蹤方案

             
            替代系統(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
                參考: http://linux.die.net/man/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)有什么兼容性問題,運行得挺好的。
             
            Reference
            [1] <不要重載全局operator new>

            [2] effective c++條款50:了解new和delete的合理替換時機

            [3] 游戲引擎中的內(nèi)存分配策略
            [4] 更好的內(nèi)存管理jemalloc
            [5] tcmalloc官網(wǎng)(gperftools)

            posted @ 2012-07-02 13:01 Daly 閱讀(7462) | 評論 (0)編輯 收藏

            2012年7月1日 #

            基于binlog的游戲數(shù)據(jù)儲存引擎

                最近組內(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ì)念一下。


            posted @ 2012-07-01 18:05 Daly 閱讀(2220) | 評論 (6)編輯 收藏

            2010年6月18日 #

            Nginx源碼學(xué)習(xí)(2) ---- 模塊化及配置

                 摘要:   閱讀全文

            posted @ 2010-06-18 16:15 Daly 閱讀(2492) | 評論 (0)編輯 收藏

            山寨版小游戲~~~

                 摘要: 實習(xí)做的兩個小游戲,山寨版的雷電和網(wǎng)絡(luò)對戰(zhàn)的休閑游戲。  閱讀全文

            posted @ 2010-06-18 13:38 Daly 閱讀(804) | 評論 (1)編輯 收藏

            Nginx源碼學(xué)習(xí)(1) ---- 基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)

                 摘要: nginx的基礎(chǔ)數(shù)據(jù)額結(jié)構(gòu)分析  閱讀全文

            posted @ 2010-06-18 12:27 Daly 閱讀(3775) | 評論 (1)編輯 收藏

            2010年5月26日 #

            高效的Timer實現(xiàn)

                 摘要: 很多程序都需要處理一系列定時事件, 本文就見過的程序中,幾種實現(xiàn)Timer的方法。用到的數(shù)據(jù)結(jié)構(gòu)一般有鏈表, 堆, RB樹,hash table等,還有一些比較優(yōu)化的方法。  閱讀全文

            posted @ 2010-05-26 20:44 Daly 閱讀(4331) | 評論 (2)編輯 收藏

            2010年5月2日 #

            資源和內(nèi)存管理學(xué)習(xí)總結(jié)

                 摘要: 總結(jié)了幾種資源和內(nèi)存管理的實現(xiàn)思路。包括buddy算法,STL中的allocator實現(xiàn)思路,游戲中的資源管理  閱讀全文

            posted @ 2010-05-02 00:02 Daly 閱讀(2254) | 評論 (1)編輯 收藏

            僅列出標(biāo)題  下一頁
            免费观看成人久久网免费观看| 久久天天婷婷五月俺也去| 国产精品成人久久久| 久久AⅤ人妻少妇嫩草影院| 久久精品www| 7国产欧美日韩综合天堂中文久久久久| 天天躁日日躁狠狠久久| 亚洲AV无码成人网站久久精品大| 久久婷婷是五月综合色狠狠| 欧美精品乱码99久久蜜桃| 九九精品久久久久久噜噜| 久久久久久国产精品美女| 久久国语露脸国产精品电影| 午夜久久久久久禁播电影| 久久亚洲精精品中文字幕| 久久久精品一区二区三区| 久久se精品一区二区影院| 色偷偷88欧美精品久久久| 久久人人爽人人爽人人片av麻烦| 久久夜色精品国产网站| 91精品婷婷国产综合久久| 亚洲人成无码www久久久| 99久久精品国产一区二区 | 综合久久一区二区三区 | 亚洲精品无码久久久影院相关影片| 无码精品久久久久久人妻中字| 久久精品亚洲日本波多野结衣| 亚洲午夜久久久精品影院| 国产精品久久久久久久人人看| 亚洲午夜久久久久久噜噜噜| A级毛片无码久久精品免费| 精品久久久久久久国产潘金莲| 丁香狠狠色婷婷久久综合| 怡红院日本一道日本久久| 亚洲婷婷国产精品电影人久久| 久久久久无码精品国产不卡| 久久天天躁狠狠躁夜夜不卡 | 一本色道久久88加勒比—综合| 久久久久99这里有精品10| 好久久免费视频高清| 久久久久高潮综合影院|