网络游戏服务器构架设计
網絡游戲服務器構架設計(一):前言
這篇blog題目涉及的范圍真大!以至于在這里需要先寫一篇前言把范圍縮小。選擇寫這樣一個系列的文章,主要是想給工作了兩年的自己一個交代,或者說是一個階段性的總結。兩年時間里,房價依然再漲,工資依然跑不贏CPI,某人依然在仰望星空。期間很多夢碎了,很多還在堅持著,生活過得波瀾不驚。而我也從剛畢業是的青澀逐步蛻變為“老油條”。不知道是一種悲哀、還是一種悲哀、還是一種悲哀....... 慶幸的是夢還在繼續,一顆倔強的心還在堅持。希望明天的明天被束縛的心能回到夢開始的地方!
?
==========================我只是條分割線========================
??? 作為本系列blog的開篇前言,本文主要明確網絡游戲服務器構架的設計目標,并作出一些限定。因為本系列所討論的服務器端構架只適用于部分網游,并不是一個通用的網游服務器構架。
?
設計目標:
?
??? 設計目標就這些多說無益,核心就是設計出能夠支持類似魔獸世界的大型MMORPG游戲的網游服務器端。具體的設計以及設計時的取舍、需要解決的問題等,會在后續的文章進行詳細的介紹。
網絡游戲服務器構架設計(二):刀劍Online - 連接負載服務器CLS
? ? 本文并沒有涉及什么逆向工程,只是拜讀刀劍Online服務器端主程的文章后[1],想結合自己的經驗談一談。
PS:由于題目范圍太大,本系列的前言做了一些限制。
?
一、網絡游戲服務器
??? 要想設計好網絡游戲服務器的構架,首先需要知道網絡游戲服務器在玩家游戲過程中發揮什么作用。就我個人的理解:網游服務器在玩家游戲過程中扮演上帝的角色。玩家在服務器制定的規則下進行游戲,服務器負責同步在線玩家之間的屬性、操作、狀態等等,最終在多個不同的客戶端呈現一個“統一”的游戲世界。
??? 所謂的服務器構架在本系列blog中,主要是指如何將服務器各部分合理的安排,以實現最初的功能需求。好的結構不是一蹴而就的,是通過需求的推動一步步的完善。而且每個設計者心中的標準不盡相同,所以我認為并沒有絕對優秀服務器構架。本系列文章中所謂的優秀構架是指各方面達到一種平衡(包括成本等的非技術因素)。
??? 下面先介紹刀劍Online的服務器構架(后續還可能有WOW、天龍等):
?
二、刀劍Online
????
圖1 刀劍Online服務器構架
?
??? 看了像素的技術總監魏華的文章[1],感覺有點意思。文章中所介紹的服務器構架并不復雜,但滿足一般MMORPG網游要求應該是綽綽有余了。按照魏華自己的話,這樣的服務器構架主要滿足能夠接受以下幾條限制的網絡游戲:
??? 服務器包括游戲中和游戲外兩大部分,這里主要討論游戲中的服務器構架,類似客戶端自動更新等的游戲外服務器不在本文的討論范圍。
??? 接下來對刀劍Online的服務器構架的各個部分進行詳細的分析,其中包含很多我自己的想法,很多內容都是我猜測的,因此不能“信以為真”。
?
2.1 連接負載服務器(Connection Load Server,CLS)
??? 游戲客戶端在游戲過程中實際上是和連接負載服務器(簡稱CLS)進行連接并做數據交互的。如文中[1]所訴,CLS主要的作用是:
- 把網絡連接和真正的游戲邏輯隔離開,降低游戲邏輯服務器處理網絡交互的負擔,同時提高游戲的安全性。有了CLS,刀劍Online的服務器對于玩家來說就是一個黑盒,如下圖:
圖2
- 使場景服務器(Zone Server)更為獨立——客戶端連接CLS而不是直接連接Zone Server的好處是:用戶切換場景服務器時,并不會導致原來的TCP連接斷開,從而使設計更為簡單和獨立。
- 提高發送廣播消息的效率,比如需要全服廣播時,游戲邏輯服務器只需要對CLS發送一條廣播指令,而向每個用戶的廣播工作由CLS完成。
- 完成客戶端數據交互的加密解密過程。
連接服務器的主要工作正如上述魏華談到的,但有幾點并沒有做出強調,下面本人結合平時的實際工作給出一些補充(不一定正確):
根據經驗:
??? CLS作為與client建立連接、進行數據交互的“Gate”,從程序角度來看CLS的代碼應該是最簡潔高效的。因為CLS主要負責與客戶端交互數據是的加密解密、以及數據搬運。而使用流加密算法RC4對整個數據流進行加密解密是很耗CPU的,因此代碼的高效在這個模塊是十分的重要。
??? 為了提高程序的運行效率,CLS程序往往會使用-O3的選項進行編譯,這無形中又對代碼的編寫有更高的要求。CLS一般會有自己的打包機制(控制發送頻率),因此常會使用TCP_NODELAY選項禁用Nagle算法。
??? CLS常會被分配到不同的物理機器上,因為操作系統在處理TCP包時,需要通過軟中斷來通知進程或者喚醒system call,在服務器十分繁忙的時候CPU可能處理不過來。解決辦法是:使用多核的服務器,然后把TCP的軟中斷平均分配到多個CPU。(一些操作系統默認只使用0號CPU來處理,Fedora Core release 2默認就是只使用0號CPU,較新的版本我沒有做研究)
??? CLS在做數據流的解密后,往往需要把數據包構造成內部服務器進程間通訊使用的protocol,這種protocol模塊要獨立,序列化和反序列化的接口要穩定,這樣以后需要更換協議模塊也不至于傷筋動骨。可以使用像google的protobuf這樣的開源協議,減少開發難度。
??? CLS負責建立和client的連接,多會使用多個CLS進程才能支撐1w的在線人數,因此在CLS前端一般會有負載均衡的程序,負責把建立連接的請求均勻的提交到各個CLS。
???有一個需要討論的問題:作為服務器端的“Gate”,只負責數據轉發的CLS是否需要對client發過來的數據進行完全的解密?或者只解密包頭,知道轉發的目的地即可?(RC4并沒有增加流的長度,因此可以只做部分解密)
? CLS只做部分解密:
好處:將耗費CPU資源的解密功能分攤到別的進程;各進程各服務可以在解密后用不同的方案來構造自己的protocol。
??CLS做完全解密:
好處:可以提前過濾部分無效的消息,只做部分解密也可以做提前過濾,但是這樣太過于依賴協議的設計;在CLS處做完全解密,則往后服務器端的之間的消息傳遞都是明文,利于抓包查錯;由于CLS的功能比較簡單,很容易通過加機器來進行擴展,因此計算放在CLS上是比較明智的選擇。
?
總結:
??? CLS是一個功能相對簡單但要求代碼簡潔高效的程序,在設計實現的時應該注重效率及代碼編寫規范。經過對比在CLS程序對數據流進行完全解密是利大于弊的,推薦使用這種方案。
網絡游戲服務器構架設計(三):刀劍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為例:
圖2 mangos游戲對象的class diagram[2]
???? 對于上面的類層次結構圖這里不做展開(留到本系列后面的文章中展開),這里引用mangos的游戲對象是想給讀者一個直觀的印象,游戲中的object在服務器端是個什么摸樣。類的層次是對游戲世界中的對象抽象后得到的結果,抽象需要“適度”:如果類的層次過深,維護起來困難,而且后期往往會導致基類過于臃腫、不堪重負;如果類的層次過淺,一些object的共性不能體現,很多代碼會重復出現在各個子類里,復用性差。
?
2.3.2 游戲世界(world)
??? 游戲世界在本文是泛指游戲對象所在的場景,以及附加在場景之上的地圖管理和對象管理,這里統稱游戲世界管理。
??? 如何進行游戲世界的管理是一個復雜的工程問題,網絡游戲常常會把整個游戲世界分為若干張地圖,每張地圖又會分為若干個區域進行管理。這若干張地圖能無縫的過渡就稱為無縫大地圖模式(類似WOW),如果像刀劍Online那樣只能通過傳送服務在兩張不同地圖之間穿梭的,稱為有縫地圖。而相對來說服務器端會比客戶端簡單一些,例:如果在客戶端看到的場景是下面這個樣子:
服務器端根據劃分區域的不同可能看到的地圖是這個樣子:
??? 這里服務器端用到了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上?這是個工程問題,同時也是個哲學問題.......
??? 刀劍Online應該采用的是第2中方式,把player放在Zone Server上。
?
2. 玩家對象player和world的關系
??? 如何處理玩家對象player和world的關系集中體現了服務器端的哲學,值得細細品味。處理兩者的關系在本人看來有兩種主要的模式:一種是以player的中心的“自我”模式;另一種是以游戲世界為中心的“上帝之手”模式。這兩種模式的哲學主要體現在加鎖模式上,接下來進行詳細介紹:
(一)player的“自我”模式
??? “自我”模式顧名思義,就是player以自我為中心,什么事都要親歷親為。以player使用物品為例:
??? 如上圖是一個完整的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直接沒有交互呢?交互就需要獲得或修改對方的數據,遇到這樣的問題“自我”模式怎么處理呢?請看下圖:
??? 如上圖: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發起攻擊”消息為例,服務器端的處理流程如下:
??? 從上圖中可以看出“上帝之手”模式的核心是world去完成這項任務:找到playerA和B,把他們都鎖住,然后交給技能模塊來進行傷害計算,最后把結果廣播出去。整個過程就像在玩war3這樣的RTS游戲,服務器就像一個神,以斜45度的上帝視角來觀察所有的玩家。
??? “上帝之手”模式的設計難點在于加鎖策略,因為需要對多個對象進行加鎖,加解鎖的順序不當容易產生死鎖。加鎖策略有很多種,這里不做具體的展開。只介紹一種最常用的方法,即對加鎖對象進行排序。游戲對象在產生時都會有一個唯一的id做標識,當lock ()函數能按照一種穩定的算法對這些id進行排序時,就可以避免死鎖。比如對游戲對象進行分類:player、npc、monster等,先對分類進行排序,再對對象的id進行排序。需要注意的是MMORPG游戲經常需要進行合服操作,為了保證合服后player的id不會重復,需要對player_id進行一些規劃。
總結:
優點:服務器程序扮演上帝的角色,可以獲得幾乎所有的信息,這對邏輯編寫帶來極大的便利。和“自我”模式一樣,最好把移動的相關邏輯獨立出來使用不同的鎖,以免照成過多的加鎖沖突和等待。“上帝之手”模式更容易進行整體規劃,更容易實現模塊化設計,降低程序的耦合度。
缺點:上帝也不是這么好演的,“上帝之手”模式對整體框架、接口設計、加鎖策略等有更高的要求,同時對編程人員的要求也會更高。
網絡游戲服務器構架設計(四):云風的軌跡
? ? 最近閑著沒事把云風的《開發筆記》看了個遍,希望能從大牛的開發軌跡中得到一些啟發。但可能是因為本人level太低,一遍看下來還是云里霧里,不甚明白。沒辦法只好再看一遍,希望能對他們的服務器端架構有個簡單的認識,這里同時做些筆記。
PS:本文是我個人對云風的開發筆記的讀后感,可能會有很多錯誤,慎入!
?
----------------------------------------華麗麗的分割線-------------------------------
?
一、服務器劃分原則??
??? 在現有的網絡游戲服務器端架構中,多是以功能和場景來劃分服務器結構的。負載均衡和集群暫且不在本文中討論(bigworld、atlas)。服務器劃分可以基于以下原則:
?
??? 接下來我們來看看云風的服務器架構是如何處理好以上幾點的。
圖1 服務器架構(此圖為本人猜測,可能有誤)
?
二、運行時的玩家數據
??? 網絡游戲服務器程序一項重要的工作就是根據client發過來的數據包,在服務器端模擬玩家的行為操作并把這些行為廣播出去。那么服務器程序在運行時就需要一些實體來保存玩家的數據,這些實體可以是一個類,也可以是一個線程,設計思想不同采用的實體差別也會很大。這里涉及服務器端設計的一個核心問題:運行時玩家數據的保存、修改及數據流向。
?
agent
??? 云風通過抽象實體agent來處理單個client的服務請求,agent和client是1:1的關系(見圖1)。agent是在gate程序后端,負責翻譯、轉發以及回應客戶端發過來的請求。agent的主要工作內容見云風的《開發筆記 (1)》。值得補充的是設計agent的主要優勢是:
把對單個 client 服務的代碼集中寫在 agent 服務中。由 agent 再和內部其它服務溝通。數據加載使用共享內存的方案,由 agent 向持久化模塊發出信號,做加載或純盤處理,通過共享內存得到結構化數據塊。[2]
??? agent相當于client在服務器上對應的實體,玩家的屬性和數據只能由agent來修改,別的服務只有讀權限。通過attach操作獲得數據(attach可能是通過服務器通訊框架skynet,也有可能直接mmap到共享內存sharedb上以獲得數據)。
??? agent的設計使得整個系統對玩家數據的修改只有一個輸入點,數據流十分的明確,易于維護。雖然這種設計可能會照成數據的多次復制,但是帶來的代碼維護和查錯上的便利是十分可觀的。
??? 如果把所有的agent放在同一個進程里,在編程該程序時還應該考慮到容錯問題,比如說(1)使用C++編寫這個程序,agent以類的形式存在,使用thread pool來處理收到的數據包,實際操作時thread的數量是會遠遠小于agent的數量的,數據包到達后會在隊列里等待thread調用agent的邏輯來處理。這是一種比較常見的設計方法,但要注意的是由于agent都放在一個進程里,程序的健壯性要求很高,一個進程core則會導致全服玩家掉線。而使用C++編寫也增加了宕進程的可能性……..你懂的。(2)使用java編寫,對于這種“中心節點”式架構來說可能是更好的選擇,起碼不是因為一個玩家的誤操作(可能使用外掛)導致全服玩家掉線。(3)云風似乎是使用lua coroutine來實現agent的相互隔離和協同工作的,這樣可以減少單一agent失敗對其他agent的影響(動態語言的好處)。
?
sharedb
??? sharedb在系統中的地位看上去像是database前端的cache,但就本人的理解sharedb的作用遠不止是一個數據緩存。
??? 和天龍八部的ShareMemory類似,sharedb也采用了定長的結構化數據(見《開發筆記 (6)》),通過共享內存來實現進程間的數據共享。sharedb的存在使得游戲邏輯處理和數據保存邏輯得到很好的隔離,游戲邏輯不用關心后端的數據是如何保存的,只要sharedb掛上定期存盤的服務,在接口定義明確的情況下,后端到底采用什么樣的數據庫變得不是那么重要,從而降低了系統的耦合度。
?
三、服務器底層框架skynet
??? skynet的設計思想見《Skynet 設計綜述》:
??? 我希望我們的游戲服務器(但 skynet 不僅限于用于游戲服務器)能夠充分利用多核優勢,將不同的業務放在獨立的執行環境中處理,協同工作。這個執行環境,最早的時候,我期望是利用 OS 的進程,后來發現,如果我們必定采用嵌入式語言,比如 Lua 的話,獨立 OS 進程的意義不太大。Lua State 已經提供了良好的沙盒,隔離不同執行環境。而多線程模式,可以使得狀態共享、數據交換更加高效。而多線程模型的諸多弊端,比如復雜的線程鎖、線程調度問題等,都可以通過減小底層的規模,精簡設計,最終把危害限制在很小的范圍內。這一點,Skynet 最終花了不到 3000 行 C 代碼來實現核心層的代碼,一個稍有經驗的 C 程序員,都可以在短時間理解,做維護工作。?
??? 做為核心功能,Skynet 僅解決一個問題:?
??? 把一個符合規范的 C 模塊,從動態庫(so 文件)中啟動起來,綁定一個永不重復(即使模塊退出)的數字 id 做為其 handle 。模塊被稱為服務(Service),服務間可以自由發送消息。每個模塊可以向 Skynet 框架注冊一個 callback 函數,用來接收發給它的消息。每個服務都是被一個個消息包驅動,當沒有包到來的時候,它們就會處于掛起狀態,對 CPU 資源零消耗。如果需要自主邏輯,則可以利用 Skynet 系統提供的 timeout 消息,定期觸發。?
??? Skynet 提供了名字服務,還可以給特定的服務起一個易讀的名字,而不是用 id 來指代它。id 和運行時態相關,無法保證每次啟動服務,都有一致的 id ,但名字可以。
??? 本人感覺skynet像一個發布訂閱的消息中間件(還沒看源碼,可能有誤),這種基于服務的即插即用式的框架給服務器端帶來很大的可擴展性,同時也使得各模塊之間獨立清晰,具有良好的可維護性。但是這里有個疑問,服務都以so的形式掛在skynet上,那么這些服務從哪里獲取玩家、怪物、NPC等object的數據?是從skynet中獲得還是直接從sharedb中獲得,出于性能的考慮是不是要把skynet和sharedb部署在同一臺物理主機上?這樣一來就會增加設計和具體邏輯的耦合度。看了《Skynet 集群及 RPC》,感覺skynet上的服務是要通過skynet來獲得玩家的數據,這樣操作會不會導致數據被復制很多次,不知道最終的效率是否受到影響?
?
四、gate
??? 滿足服務器劃分原則里的第一點:分離游戲中占用系統資源(cpu,內存,IO等)較多的功能,獨立成服務器。
??? gate的主要工作可以參見本系列blog的第二篇《網絡游戲服務器構架設計(二):刀劍Online - 連接負載服務器CLS》
?
五、場景管理器
??? 主要用于管理靜態場景和動態副本,比如agent登錄時查詢自己所在場景對應的服務器地址。
?
六、場景服務器
??? 場景服務器的內容我沒有從《開發筆記》中得到太多的信息(可能level太低),更多的是以功能模塊的形式寫,比如AOI。不過其中有一點比較新穎的是云風認為player的位置、動作狀態,戰斗數值狀態等都是場景的一部份,應該保存在場景中而不是agent中。本節有所更正見(八)補充。
??? 據我的猜測,場景服務器應該會負責:
??? 需要注意的是場景服務器修改的一些數據應該以什么樣的頻率通知agent呢?比如player的位置信息,該信息是完全保存在場景數據里還是說agent里也有一份?
?
七、總結
??? 本文是一篇云風《開發筆記》系列blog的讀后感,所述內容均是本人的猜測,雖恐貽笑大方,但也希望能拋磚引玉。收筆忐忑ing!
?
八、補充:
? (1) 云風在微薄上的回復是:“我們最終采用的是單進程多線程, 每線程上一個 lua state 的結構. sharedb 是用來線程間數據交換的. gate 和 sharedb 以及 loader 和 agent map 一樣, 都是 skynet 下的獨立服務, 以 so 掛接進去的. 后來的商品交易, 掉落品分配也是 skynet 下的服務. ”
? (2) 關于場景服務器,云風已經給出完整的說明,見《開發筆記(14)》
??? 場景服務分成兩個部分,一是副本管理器,二是地圖服務。在角色數據上,記錄有角色應該屬于的地圖。agent 向地圖所屬的副本管理器查詢,得到他所屬的地圖服務地址。便可以把自己注冊到具體地圖上。?
??? 地圖服務管理了所有其中的角色 id ,以及若干 npc 。他的義務在于把讓這些 id 對應的 agent 相互了解。但具體邏輯則放在每個 agent 服務上。每個 agent 自己所屬進程 attach 其它 id ,可以獲取其他對象的狀態。
? .........
? (3) 風哥在《開發筆記(25)》中已經提到最終使用單進程多線程的模式。看來簡單設計是有共識的:-)
總結
以上是生活随笔為你收集整理的网络游戏服务器构架设计的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: nasm 用xmm寄存器做矩阵相加溢出了
- 下一篇: JavaScript放大镜插件magni