網絡游戲服務器構架設計(三):刀劍Online - 總控服務器、場景服務器


    上一篇《網絡游戲服務器構架設計(二)》介紹了刀劍Online的連接負載服務器CLS,博友提出質疑“說得不夠詳細,比如你怎么,場景服務器怎么才算一個場景服務器,場景服務器切換怎么處理不斷線后連接另一個場景的,還有很多細節問題沒有說到”,本篇就來介紹游戲服務器最為核心的部分:游戲邏輯服務器,同時也回答了這位博友的問題。

PS:本篇的文章結構主要分兩個部分,前半部分(2.2節)介紹刀劍Online如何實現游戲邏輯服務器,后半部分(2.3節)為本人結合實際工作對這套服務器構架做出的一些展開解釋及補充,主要對設計思想進行分析。精彩在后面哦!

 

-------------------------------------------我只是條分割線--------------------------------------------

 

先來回顧一下刀劍Online的總體構架圖:

 

 

 

2.2 游戲邏輯服務器

    顧名思義,就是和游戲具體邏輯相關的服務器(這應是一個統稱)。這塊是網游服務器端的核心部分,不同的游戲差別會很大。在刀劍中,游戲邏輯服務器分為兩部分:總控服務器和場景服務器。

 

 

2.2.1 總控服務器(Master Server,MS)

    關於總控服務器的作用,刀劍Online的主程是這么解釋的:

    總控服務器(以下簡稱MS)的作用之一是負責玩家在具體游戲內容之外的操作(即.玩家進入場景服務器之前地操作)。如:登錄、注銷、各種角色操作(創建、刪除、選擇)等等。

    MS和所有地場景服務器都保持連接,這樣它就成為各個場景服務器間的樞紐,當需要一些跨場景服務器的操作或者需要訪問別的場景服務器數據的時候,指令都先發給MS,然后MS根據需要再轉發給相應地場景服務器或者直接發給相應的用戶,並進行后續地協調工作。

    比如:在場景服務器1上的用戶A希望向游戲中的用戶B發出一條添加好友的請求,則場景服務器1向MS發送添加好友指令並附帶了用戶B的名字,MS查找發現有B這樣的用戶,則直接把指令發給CLS,然后由CLS轉發給B用戶;如果沒有發現B用戶則直接通知A未發現B。

    又比如:在場景服務器1上的用戶A點中了傳送點,將要傳到場景X,場景服務器1發現X場景並不在自己的管轄范圍內,於是發送轉移指令給MS,MS查找發現場景X在場景服務器2上,於是先發送用戶A的離開指令給場景服務器1,讓用戶退回到MS上,然后再發送用戶A的進入指令給場景服務器2,並說明用戶將要進入的場景為X,這樣一次跨服務器的場景轉移就完成了。[1]

 

2.2.2 場景服務器(Zone Server,ZS)

    關於場景服務器的作用,刀劍Online的主程是這么解釋的:

    場景服務器(以下簡稱ZS)就是具體負責游戲場景的服務器。玩家選擇人物開始游戲之后就進入了這種服務器(即開始游戲之后CLS把所有玩家的操作指令都轉給ZS)。

    玩家的各種操作的邏輯都是由ZS完成的,同時,ZS也要負責各個場景以及場景中的NPC和場景中各個物品的邏輯運行。

    每款游戲的真正游戲性的核心就是這些ZS。它的具體細節我就不過多的講述了,各個游戲的具體內容應該都不相同。不過有幾個原則是共同的:

    一、是要高效。如果ZS對游戲邏輯的處理效率低,會直接影響玩家同時在線的數量,並導致游戲中的玩家感覺很“卡”,這是除了網絡延時之外第二個會造成游戲 “卡”的地方。提高效率的方法除了對代碼進行優化外,就是要使用高效的腳本系統,直接把腳本轉化為程序代碼編譯到程序中去也不失為一個辦法。

    二、是要有災難恢復機制,就是當ZS發生非法操作時(只要不停電)能夠恢復出非法操作時各個用戶的數據。這個在游戲運營初期服務器尚不穩定的時候非常重要。雖然我們也可以通過加快用戶存盤間隔的方式(比如把每10分鍾存一次盤改為每1分鍾存一次盤),但是這會成倍加重數據庫負擔,同時也不能避免由於用戶剛好在存盤間隔的時候獲得了重要的金錢或道具而導致丟失的情況。刀劍采取的方法是在申請用戶關鍵數據對象的時候通過包裝的函數從共享內存中申請,這樣即便ZS非法操作了,共享內存並不會消失,在重新啟動的時候就可以從共享內存中恢復出程序非法時的用戶數據。當然恢復的時候也需要對用戶數據進行一些校驗,以免把已經被破壞的數據存入數據庫。[1]

 

 

2.3 設計思想分析

    游戲邏輯服務器主要負責匯總所有在線的client發來的各種操作、狀態等數據包,經過一系列的處理后有選擇的廣播給需要的client,從而給所有在線的玩家呈現一個“統一”的世界。

    優秀的邏輯服務器架構需要優秀的設計思想,而優秀的設計思想又源於對游戲虛擬世界的適度抽象。抽象可以看作一個工程問題,同時也可以看作一個哲學問題,這正是游戲開發的魅力所在。本系列blog將圍繞這種抽象一步步展開,結合不同的項目來詮釋網絡游戲服務器端的設計思想(希望能做到....)。

    站在服務器端的角度對游戲虛擬世界進行抽象,首先要弄清楚構造虛擬世界需要些什么?讓我們來想象一下吧(以下內容參照了《盜夢空間》-“Inception”),先來看一段視頻:

    Inception是我非常喜歡的影片,第一次看到這一段的時候,就感覺非常像游戲設計。今天能把它寫下來也算沒浪費幾十塊的電影票錢。

    影片中飾演the architect(造夢師)的艾倫·佩姬(女),正在接受萊昂納多(飾演the extractor-盜夢者)的訓練。萊昂納多說道:“Remember you are the dreamer you built this world。”,“I'm the subject my mind populates it”。值得關注的兩個名詞:world,subject。這就是我們要討論的主題。那么構造網絡游戲的虛擬世界需要些什么呢?其實萊昂納多已經替我回答了這個問題:“We create and perceive out world simultaneously. You create the world of the dream, We bring the subject into that dream, and they fill it with their subconscious.”。world就相當於游戲里的場景,而subject就是一個個在線的玩家(player)。

    游戲世界(world,這里的world泛指游戲世界及地圖,見2.3.2)及游戲對象(object,包括player)是構造網絡游戲服務器端時,需要關注的兩個重點。如何處理好world和object的關系和地位直接影響到服務器端的構架。

 

2.3.1 游戲對象(object)

    先來看游戲里一般會有那些object,以Mangos為例:

class_diagram

圖2 mangos游戲對象的class diagram[2]

     對於上面的類層次結構圖這里不做展開(留到本系列后面的文章中展開),這里引用mangos的游戲對象是想給讀者一個直觀的印象,游戲中的object在服務器端是個什么摸樣。類的層次是對游戲世界中的對象抽象后得到的結果,抽象需要“適度”:如果類的層次過深,維護起來困難,而且后期往往會導致基類過於臃腫、不堪重負;如果類的層次過淺,一些object的共性不能體現,很多代碼會重復出現在各個子類里,復用性差。

 

2.3.2 游戲世界(world)

    游戲世界在本文是泛指游戲對象所在的場景,以及附加在場景之上的地圖管理和對象管理,這里統稱游戲世界管理。

    如何進行游戲世界的管理是一個復雜的工程問題,網絡游戲常常會把整個游戲世界分為若干張地圖,每張地圖又會分為若干個區域進行管理。這若干張地圖能無縫的過渡就稱為無縫大地圖模式(類似WOW),如果像刀劍Online那樣只能通過傳送服務在兩張不同地圖之間穿梭的,稱為有縫地圖。而相對來說服務器端會比客戶端簡單一些,例:如果在客戶端看到的場景是下面這個樣子:

yy4

服務器端根據划分區域的不同可能看到的地圖是這個樣子:

yy5

    這里服務器端用到了tile-based的方式來管理地圖,這種tile的方式在2d游戲中十分的常見(打格子),而很多3d網游的服務器端為了減少運算量也采用這種tile模式划分區域進行管理。如果對地圖管理有興趣請閱讀雲風的blog《用四叉樹管理散布在平面上的對象》、《碰撞檢測》,本文先不做詳細的展開。

 

 

2.3.3 游戲對象和游戲世界的關系

    如何處理游戲對象和游戲世界的關系和地位,是影響服務器端架構的最直接因素。為了體會到這點,下面將以刀劍Online為例,分析玩家對象(player,游戲對象中最為重要的部分)和游戲世界的關系對整個服務器構架設計的影響。

 

1. 玩家對象player的構建

    刀劍Online把游戲邏輯服務器分為總控服務器(Master Server,MS)和場景服務器(Zone Server,ZS,本文提到的游戲世界多指ZS),那么在client成功登陸server后,服務器端應該在哪構建player對象呢?在MS上?還是在ZS上?這是個工程問題,同時也是個哲學問題.......

  1. 如果把player(以及其他的游戲對象)放在Master Server上:也就是說所有登陸的玩家的數據都會在MS的內存中有一份映像,MS將保存着player最新的數據。這么做的好處是player的數據統一,從而使同服玩家的一些交互變得十分方便,比如同服玩家組隊、交友等不需要知道玩家在那個ZS里。切換場景服務器時也不需要把player數據從一個ZS拷貝到另一個ZS,只需要把player身上記的ZS信息修改一下;player在MS上構建的缺點也是顯而易見的,所有中心節點式的系統都會遇到單點不可靠的問題,player的信息都保存在MS上,假如有1w人在線,內存占用就是一個很大的問題(畢竟單個進程的內存使用還是有限制的,實際開發中曾經遇到占用十幾G內存的進程......相當可怕),而如果一個player借住外掛發送一些非法消息給MS,錯誤處理不當時很有可能會core進程,這樣一來整個服務器的玩家都會掉線。還有一些和地圖相關的邏輯會很難操作,比如player與場景中怪物進行PK時,由於player的數據保存在MS,因此ZS需要做一個Attach操作告知MS某個player和某個monster進行戰斗,MS進行完傷害計算后發AttachResult給ZS,最后由ZS再廣播給相關的客戶端,如圖:image 如圖中標注的(1)~(4)的步驟,展示了這個過程,編程操作起來還是挺麻煩的。
  2. 如果把player放在Zone Server上:這是一種比較常見的方式,player和其他游戲對象在場景服務器中構造,隸屬於Zone Server。由ZS來掌管player從編程角度講好處多多:可以直接獲得player的信息,對於場景內交互十分便利,場景內的操作不需要多余的數據傳輸。而在需要跨場景操作時只需用MS中轉一下即可;但是這種方式缺點同樣是很致命的,player在Zone Server上構造就失去數據放在中心節點的簡潔,player的信息常常需要在ZS、MS和DB上進行同步,這種“三體同步”的痛苦只有做過的人才能體會。比如:幾個獨立的服務器之上需要增加一個跨服服務器,這幾個普通服務器的玩家可以進入這個跨服服務器進行pk。那么就需要將player先從普通服務器ZS退到MS,由普通服務器的MS把player的數據發給跨服MS,最后再由跨服MS發送到跨服的ZS。如果還涉及DB則會更加的復雜。ZS、MS、DB相互獨立,如果沒有強制規定,這種同步往往是沒有方向的:player的最新的數據一般在ZS上,但是有些交互不能在ZS上完成,那么可以有兩種選擇:(1)ZS把player數據發給MS,由MS進行操作,操作過程中ZS不能修改player的相關數據。(2)把player的最新數據存盤,然后由MS讀盤取player的數據然后進行操作,操作過程中ZS還不能改變player的相關數據。不管哪種方法開發的邏輯多了以后都不好維護,從此維護人員都變成怨婦.......

    刀劍Online應該采用的是第2中方式,把player放在Zone Server上。

 

2. 玩家對象player和world的關系

    如何處理玩家對象player和world的關系集中體現了服務器端的哲學,值得細細品味。處理兩者的關系在本人看來有兩種主要的模式:一種是以player的中心的“自我”模式;另一種是以游戲世界為中心的“上帝之手”模式。這兩種模式的哲學主要體現在加鎖模式上,接下來進行詳細介紹:

(一)player的“自我”模式

    “自我”模式顧名思義,就是player以自我為中心,什么事都要親歷親為。以player使用物品為例:

image

    如上圖是一個完整的player使用物品的流程。“自我”模式體現在上圖的(1)位置,在(1)中首先取到服務器中對應的player,加鎖后用player調用自己的處理函數進行處理。由於(1)相當於處理客戶端發來的請求的入口處,因此在這里進行加鎖和解鎖操作是十分合適的,此后的(2)~(5)player在調用自己的處理函數時,編程人員完全不需要考慮加鎖的問題。

    所謂的“自我”模式,其實就是指在服務器端對player的操作其實都是player自己去完成的,“”自己去把事情做完。處理邏輯時“”(自己的player實例)不會主動去鎖住對方的player實例,因此一個player不能修改另一個player的數據,world也不能修改player的數據。world和player的關系是“獨立”的,player身上記着world的信息(相當於“我屬於哪個世界”),world里存放着player的實例,但是它們直接不能直接修改對方的數據。這么做是為了避免死鎖,使得加鎖解鎖變得簡單而統一(如上圖)。

    “自我”模式有加鎖解鎖的便利,但是一個網絡游戲怎么可能player和player、player和world直接沒有交互呢?交互就需要獲得或修改對方的數據,遇到這樣的問題“自我”模式怎么處理呢?請看下圖:

image

    如上圖:client A向服務器發送“向玩家B發起攻擊”的消息,服務器端client A對應的實例Player A收到消息后,發現需要與player B進行交互,根據“自我”模型的限定,player A不能直接修改player B的血量等信息,這時player A需要做的是將自己的信息打包成一個msg結構然后push_back到消息隊列message queue並指定player B作為接收者。在message queue上將這個msg轉發給player B前,會先調用lock(playerB)將B鎖住,接着把msg傳給player B進行處理,在處理完畢后再調用unlock(playerB)。lock和unlock都在統一的地方調用,加鎖解鎖十分簡潔,playerB處理msg消息時不用關心任何加鎖問題。在上圖的(2)中,player B進行傷害計算后將傷害消息廣播給周圍的玩家。

    注意:“自我”模式下,傷害計算是在受攻擊方進行的。player之間、player和world之間都是通過消息傳遞進行交互的。

總結:

優點:“自我”模式的優點集中體現在加鎖上,編程人員在編寫具體邏輯時不用擔心加鎖問題,也不用費盡心思來避免死鎖,因為加鎖都在消息入口處統一做了,同時不會主動去對別的player實例進行加鎖操作。使用這種模式時,最好把移動的相關邏輯獨立出來使用不同的鎖,以免照成過多的加鎖沖突和等待。

缺點:“自我”模式的缺點也是十分明顯的,每個player實例作為不同的“我”獨立存在,需要交互時只能通過消息隊列來傳遞信息,極大的增加了交流的成本(內存、CPU占用率都會增加)。一個player往往不能即時的取到別的player的數據,這樣一來很多的計算都只能做延后處理,比如傷害計算就只能放在被攻擊者的player上,使得很多需要做先驗判斷的技能實現起來變得復雜,這類技能只能靠釋放技能的player,發送請求消息給其他player,然后再由其他player把自己的信息通過msg queue發給釋放技能的player。異步處理方式往往會但來更多的煩惱——需要增加很多錯誤判斷、錯誤處理以及超時處理等等。在線人數高的時候,message queue的容量以及所占用的內存也是需要考慮的問題。

    引用狄更斯的:“It was the best of times, it was the worst of times”。本人依葫蘆畫瓢:“自我”模式是一種nb的設計,也是一種sb的設計.......

 

(二)world的“上帝之手”模式

    “上帝之手”模式是以world為中心(類似war3),以地圖為單位進行划分,player、NPC、monster等游戲對象都隸屬於world。在world的掌控之下,就像有一只上帝之手在撥弄着這些小玩意。以client A向服務器發送“向玩家B發起攻擊”消息為例,服務器端的處理流程如下:

image

    從上圖中可以看出“上帝之手”模式的核心是world去完成這項任務:找到playerA和B,把他們都鎖住,然后交給技能模塊來進行傷害計算,最后把結果廣播出去。整個過程就像在玩war3這樣的RTS游戲,服務器就像一個神,以斜45度的上帝視角來觀察所有的玩家。

    “上帝之手”模式的設計難點在於加鎖策略,因為需要對多個對象進行加鎖,加解鎖的順序不當容易產生死鎖。加鎖策略有很多種,這里不做具體的展開。只介紹一種最常用的方法,即對加鎖對象進行排序。游戲對象在產生時都會有一個唯一的id做標識,當lock ()函數能按照一種穩定的算法對這些id進行排序時,就可以避免死鎖。比如對游戲對象進行分類:player、npc、monster等,先對分類進行排序,再對對象的id進行排序。需要注意的是MMORPG游戲經常需要進行合服操作,為了保證合服后player的id不會重復,需要對player_id進行一些規划。

總結:

優點:服務器程序扮演上帝的角色,可以獲得幾乎所有的信息,這對邏輯編寫帶來極大的便利。和“自我”模式一樣,最好把移動的相關邏輯獨立出來使用不同的鎖,以免照成過多的加鎖沖突和等待。“上帝之手”模式更容易進行整體規划,更容易實現模塊化設計,降低程序的耦合度。

缺點:上帝也不是這么好演的,“上帝之手”模式對整體框架、接口設計、加鎖策略等有更高的要求,同時對編程人員的要求也會更高。

 

 

References:

[1]像素軟件技術總監--魏華:《刀劍Online服務器構架分析》

[2]張昆:《Mangos服務器的游戲對象和AI系統》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM