當我設計遊戲服務器時,我在想些什麼?(1)

機緣巧合的機會,我有幸可以從頭開始設計一個遊戲的服務器。中間遇到不少歡聲笑語和悲傷淚水,這裏分享一下。
html

我以前所在項目組的遊戲服務器架構以下圖:程序員

這款遊戲是一款MMO的端遊,GateWay網關的任務是接受客戶端的鏈接,而後經過分發策略,把玩家丟進GameSvr上去,以後玩家的全部請求都直接發給GameSvr,由GameSvr處理了。固然這裏的分發策略跟通常的web服務器是不一樣的,web服務器通常會作成無狀態的服務器,也就是對於客戶端來講請求到達哪個服務器都沒有關係,都可以被處理,可是遊戲服務器大多都是有狀態的,web的通常分發策略是作一個負載均衡,只要保證服務器的總體壓力沒問題就已是一個好的架構了,可是遊戲服務器因爲存在狀態須要維護,因此一般都沒辦法作簡單的負載均衡。舉個例子,因爲咱們是MMO遊戲,因此在服務器邏輯上存在地圖的概念,比如魔獸世界的遊戲裏不一樣的玩家出生的地圖是不同的,並且你從一張地圖下線後,下次上線的地點通常來講都會是你上次下線的地點,而根據遊戲歷程和策劃想要營造的遊戲效果來看,遊戲裏不一樣地圖之間的壓力一般來講是不同的,好比主城的玩家就一般比一張偏遠的野外地圖的玩家要多,而遊戲服務器的壓力一般都出如今玩家彙集的時候,例如大量IO的壓力。因此從設計人員的角度來看,通常會把不一樣的地圖分配到單獨的進程中去,因此GameSvr一般會按照地圖來進行劃分,那麼GameSvr進程天生就具有了本身的狀態,由於兩個GameSvr進程已經就不同了,玩家是在哪一個地圖,就只能把玩家丟到哪一個GameSvr上去。固然,這只是咱們採起的一種作法,其餘人還有不一樣的作法,不過大體是相似的。web


我曾仔細考量過上面那個遊戲單個GameSvr的承載能力,一般在1000人左右就已經開始吃緊了,這點也比較好理解,根據這個服務器的架構,幾乎全部的玩家邏輯都在同一個進程內被處理,只有一些公共的邏輯被分配給Relay進程處理,這裏的Relay進程承擔兩個做用,一個是作不一樣的GameSvr間的通訊轉發,另外一個是它自身也會維護遊戲裏的公共邏輯,什麼叫公共邏輯呢?好比聊天、幫會、排行榜等等,這些不跟地圖相關的遊戲邏輯,又被多個GameSvr共享的部分就被抽離到Relay進程上,其實這裏有兩種作法,一種是讓每一個GameSvr進程本身去維護這份公共邏輯,但顯然這會浪費GameSvr進程僅剩的很少的壓力負載。可是把邏輯放入Relay進程也帶來一些問題,好比在遊戲研發中會發現,愈來愈多的邏輯須要放進Relay進程,致使Relay進程愈來愈龐大,好比咱們遊戲中的幫會數據和全局定時器的維護就由Relay進程來維護的,可是程序員在寫功能的時候常常就直接一個RPC調用來使用Relay的功能,由於這很方便,並且從現有架構的設計來看也是合理的,咱們當時的relay的rpc接口設計的是同步的接口,因而當rpc激增的時候,整個遊戲邏輯就變卡了。sql


Relay的龐大致使兩個問題,一個是單點故障的機率變高了,另外一個就是Relay的性能低下會致使整個遊戲服務器變卡。咱們的服務器有段時間在天天晚上10點左右就開始卡,只要涉及到公共邏輯的部分就幾乎沒法使用,例如沒法聊天、沒法進行幫會操做等等,最後排查問題的結果就發現是遊戲中的某些功能把邏輯寫在Relay上,到10點進行了一個十分複雜的計算,致使Relay阻塞在那裏,而沒法響應GameSvr發來的請求。我是在遊戲的開發末期加入這個項目的,當時Relay的體積仍是比較小的,你們都刻意的去儘可能避免遊戲邏輯放在Relay上,可是當遊戲真正進入運營期以後,deadline的限定,你們不得不把邏輯放Relay上,由於這是最快的實現方式。我眼看着Relay一步步的胖起來,在我離開這個項目的時候,Relay上的代碼已經滿目蒼夷了。數據庫


再來談談這個架構的其餘部分,好比Bishop是用來進行咱們遊戲內交易數據的統計和驗證的模塊,主要是和公司的交易系統進行對接,AccoutSvr的做用是處理遊戲內帳戶的相關操做,好比創建帳戶,新建角色等等,它的主要做用實際上是爲了保證遊戲數據的惟一性,例如角色名的惟1、寵物名的惟一等等。接着是Mysql的部分,雲風曾經在博客裏說過遊戲服務器中數據庫的做用應該只是一種備選方案,http://blog.codingnow.com/2014/03/mmzb_db.html 也就是說若是存在理想的遊戲服務器,永不宕機,永不維護,那麼實際上數據庫是不須要的部分,數據庫承擔的是一個遊戲恢復和容錯的機制,我很贊同雲風的這種說法,而一旦不須要去考慮數據庫的部分,那麼遊戲設計其實減掉了不少容錯的作法。關於數據庫的部分我後面還會談到。後端


而後是這個架構的擴展性,對於遊戲服務器來講,擴展一般有三種,一種是開新區、另外一種是合服、最後纔是因爲服務器壓力頂不住而擴展邏輯進程。上面咱們這個端遊的服務器模塊從設計上就不去考慮開新服的機制,單套服務器架構基本只支持一個區,因此在開新區的時候,作法是部署同一套服務器架構,利用客戶端更新服務器的地址來實現不一樣服的架構;合服的狀況要更加複雜,由於在設計上沒有考慮多服的設計,因此不一樣服之間的數據能夠說是絕不相關,那麼在合併服務器的時候必然存在一些衝突數據須要處理,不光是一些玩家數據,甚至還有一些遊戲邏輯數據,例如我以前作過一個功能,是實現一個全服的玩家聯賽,最後每一個區會產生一個冠軍,而後冠軍的雕像會被放置到遊戲中的主城中展現一個月,可是後來有兩個區合併了,那麼到底展現誰的雕像,由於不管你展現哪個區的,玩家都會不滿,固然,這種事情通常須要策劃去考慮而後解決,可是從程序上來講,確實存在這種須要特別處理的地方。在合服的時候,另外一個問題是以前保證的角色名不重複是利用的各自區的accsvr,一般accsvr保證唯一性的作法是使用同一個數據庫來保存須要排重的信息,可是對於不一樣的網絡運營商,這個數據庫極可能沒辦法統一塊兒來,這樣一來,當兩個處於不一樣網絡中的區須要合併的時候就出現了難以解決的問題,好比咱們有一次把電信和網通的兩個區進行合併,原本這兩個區各自確實可以保證角色名惟一,可是當跨運營商合併的時候卻會發現衝突。服務器


而後是服務器壓力,不知道是因爲當年設計這個架構的人沒有考慮到仍是當時不存在這樣的問題,因此服務器橫向擴展的能力是十分弱的,對於新開的副本,relay進程會根據gamesvr的壓力來選擇把新副本開在哪一個gamesvr進程上,但對於常駐地圖,倒是寫死在配置表裏,綁定固定的gamesvr,而relay又沒有提供動態添加gamesvr的功能,這致使了若是在服務器運行期間出現了單個服務器進程壓力,除了重啓服務器,幾乎沒有任何辦法。這個在運營期暴露的很明顯,因爲GameSvr按照地圖進行劃分,當開新區的時候,玩家大量涌入同一張地圖,致使單個GameSvr壓力出現峯值,但這個時候卻沒辦法動態的擴展進程進行分壓,而致使服務器宕掉。在後期的運營中這樣的狀況家常便飯。網絡


後來我還聽到過不少關於頁遊的架構,頁遊服務器總體上跟端遊思路是相似的,因爲客戶端的一般通訊方式再也不像端遊那樣採用原生tcp鏈接,而是使用http等短鏈接的協議,因此在客戶端鏈接的部分設計上更加靈活,並且大部分頁遊的遊戲邏輯沒有端遊那麼複雜, 在客戶端的表現力有限的狀況下,基本上總體的遊戲服務器設計要更加精簡,因此我看到一般服務器後端會更加偏重於如何進行橫向擴展。上面我提到的架構擴展性差的問題,對比頁遊的滾服方式,體現的最明顯了。架構


這就是關於我上一個遊戲服務器的樣子了,下一篇開始我本身的服務器設計。負載均衡

相關文章
相關標籤/搜索