遊戲架構

遊戲伺服器特徵

遊戲伺服器端,是一個會長期執行的程式,並且它還要服務於多個不定時,不定點的網路請求。

所以這類軟體的特點是要非常關注穩定性和效能。這類程式如果需要多個協作來提高承載能力,則還要關注部署和擴容的便利性;同時,還需要考慮如何實現某種程度容災需求。由於多程序協同工作,也帶來了開發的複雜度,這也是需要關注的問題。

功能約束,是架構設計決定性因素。基於遊戲領域的功能特徵,對伺服器端系統來說,有以下幾個特殊的需求:

  • 對於遊戲資料和玩家資料的儲存
  • 對玩家資料進行資料廣播和同步
  • 把一部分遊戲邏輯在伺服器上運算,做好驗證,防止外掛。

針對以上的需求特徵,在伺服器端,我們往往會關注對電腦記憶體和 CPU 的使用,以求在特定業務程式碼下,能儘量滿足承載量和響應延遲的需求。最基本的做法就是“空間換時間”,用各種快取的方式來以求得 CPU 和記憶體空間上的平衡。

在 CPU 和記憶體之上,是另外一個約束因素:網絡卡。網路頻寬直接限制了伺服器的處理能力,所以遊戲伺服器架構也必定要考慮這個因素。

遊戲伺服器架構要素

對於遊戲服務端架構,最重要的三個部分就是,如何使用 CPU、記憶體、網絡卡的設計:

  • 記憶體架構:主要決定伺服器如何使用記憶體,以最大化利用伺服器端記憶體來提高承載量,降低服務延遲。
  • 邏輯架構:設計如何使用程序、執行緒、協程這些對於 CPU 排程的方案。選擇同步、非同步等不同的程式設計模型,以提高伺服器的穩定性和承載量。可以分割槽分服,也可以採用世界服的方式,將相同功能模組劃分到不同的伺服器來處理。
  • 通訊模式:決定使用何種方式通訊。基於遊戲型別不同採用不同的通訊模式,比如 http,tcp,udp 等。

伺服器演化程序  

1、卡牌等休閒遊戲弱互動遊戲  

伺服器基於遊戲型別不同,所採用的架構也有所不同,我們先講一下簡單的模型,採用 http 通訊模式架構的伺服器:

伺服器

這種伺服器架構和我們常用的 web 伺服器架構差不多,也是採用 nginx 負載叢集支援伺服器的水平擴充套件,memcache 做快取。唯一不同的地點不同的在於通訊層需要對協議再加工和加密,一般每個公司都有自己的一套基於 http 的協議層框架,很少採用開源框架。

2、長連結遊戲伺服器  

長連線遊戲和弱聯網遊戲不同的地方在於,長連線中,玩家是有狀態的,伺服器可以時時和 client 互動,資料的傳送,不像弱聯網一般每次都需要重新建立一個連線,訊息傳送的頻率以及速度上都快於弱聯網遊戲。

 1) 第一代網遊伺服器(單執行緒無阻塞)

最早的遊戲伺服器是 1978 年,英國著名的財經學校 University of Essex 的學生 Roy Trubshaw 編寫了世界上第一個 MUD 程式,叫做《MUD1》。《MUD1》程式的原始碼在 ARPANET 共享之後,在全世界廣泛流行起來。不斷完善的 MUD1 的基礎上產生了開源的 MudOS(1991),成為眾多網遊的鼻祖。

MUD1 是一款純文字的世界,沒有任何圖片,但是不同計算機前的玩家可以在遊戲裡共同冒險、交流。與以往具有網路聯機功能的遊戲相比, MUD1 是第一款真正意義上的實時多人互動的網路遊戲,它最大的特色是能夠保證整個虛擬世界和玩家角色的持續發展——無論是玩家退出後重新登入還是伺服器重啟,遊戲中的場景、寶箱、怪物和謎題仍保持不變,玩家的角色也依然是上次的狀態。

MUD

MUD 中文版

MUDOS 使用單執行緒無阻塞套接字來服務所有玩家,所有玩家的請求都發到同一個執行緒去處理,主執行緒每隔 1 秒鐘更新一次所有物件(網路收發,物件狀態,重新整理地圖,重新整理 NPC)。使用者使用 Telnet 之類的客戶端用 Tcp 協議連線到 MUDOS 上,使用純文字進行遊戲,每條指令用回車進行分割。這樣的系統在當時每臺伺服器承載個 4000 人同時遊戲。從 1991 年的 MUDOS 釋出後,全球各地都在為他改進,擴充,推出新版本。

MUDOS 中游戲內容通過 LPC 指令碼進行定製,邏輯處理採用單執行緒 tick 輪詢,這也是第一款服務端架構模型,後來被應用到不同遊戲上。後續很多遊戲都是跟《UO》一樣,直接在 MUDOS 上進行二次開發,直到 如今,一些回合制遊戲,以及對運算量小的遊戲,依然採用這種伺服器架構。

伺服器架構

第一代伺服器架構圖

執行緒模型

執行緒模型

 2) 第二代網遊伺服器(分割槽分服)

2000 年左右,隨著圖形介面的出現,遊戲更多的採用圖形介面與使用者互動。此時隨著線上人數的增加和遊戲資料的增加,伺服器變得不抗重負。於是就有了分服模型。分服模型結構如下:

分服模型

分服模型是遊戲伺服器中最典型,也是歷久最悠久的模型。在早期伺服器的承載量達到上限的時候,遊戲開發者就通過架設更多的伺服器來解決。這樣提供了很多個遊戲的“平行世界”,讓遊戲中的人人之間的比較,產生了更多的空間。其特徵是遊戲伺服器是一個個單獨的世界。每個伺服器的帳號是獨立的,每臺伺服器使用者的狀態都是不一樣的,一個服就是一個世界,大家各部牽扯。

後來遊戲玩家呼籲要跨服打架,於是出現了跨服戰,再加上隨著遊戲的執行,單個伺服器的遊戲活躍玩家越來越少,所以後期就有了伺服器的合併以及遷移,慢慢的以伺服器的開放、合併形成了一套成熟的運營手段。目前多數遊戲還採用分服的結構來架設伺服器,多數頁遊還是採用這種模式。

執行緒排程

分服雖然可以解決伺服器擴充套件的瓶頸,但單臺伺服器在以前單執行緒的方式來執行,沒辦法充分利用伺服器資源,於是又演變出了以下兩種執行緒模型

非同步 – 多執行緒:基於每個場景(或者房間),分配一個執行緒。每個場景的玩家同屬於一個執行緒。遊戲的場景是固定的,不會很多,如此執行緒的數量可以保證不會不斷增大。每個場景執行緒,同樣採用 tick 輪詢的方式,來定時更新該場景內的(物件狀態,重新整理地圖,重新整理 NPC)資料狀態。玩家如果跨場景的話,就採用投遞和通知的方式,告知兩個場景執行緒,以此更新兩個場景的玩家資料。

多程序:由於單程序架構下,總會存在承載量的極限,越是複雜的遊戲,其單程序承載量就越低,因此一定要突破程序的限制,才能支撐更復雜的遊戲。多程序系統的其他一些好處:能夠利用上多核 CPU 能力、更容易進行容災處理。

多程序系統比較經典的模型是“三層架構”,比如,基於之前的場景執行緒再做改進,把網路部分和資料庫部分分離為單獨的程序來處理,邏輯程序專心處理邏輯任務,不合 IO 打交道,網路 IO 和磁碟 IO 分別交由網路程序和 DB 程序處理。

3) 第三代網遊伺服器

之前的網遊伺服器都是分割槽分服,玩家都被劃分在不同的伺服器上,每臺伺服器執行的邏輯相同,玩家不能在不同伺服器之間互動。想要更多的玩家在同一世界,保持玩家的活躍度,於是就有了世界服模型了。世界服型別也有以下 3 種演化:

一型別(三層架構)

閘道器部分分離成單端的 gate 伺服器,DB 部分分離為 DB 伺服器,把網路功能單獨提取出來,讓使用者統一去連線一個閘道器伺服器,再有閘道器伺服器轉發資料到後端遊戲伺服器。而遊戲伺服器之間資料交換也統一連線到網管進行交換。所有有 DB 互動的,都連線到 DB 伺服器來代理處理。

二型別(cluster)

有了一型別的經驗,後續肯定是拆分的越細,效能越好,就類似現在微服務,每個相同的模組分佈到一臺伺服器處理,多組伺服器叢集共同組成一個遊戲服務端。一般地,我們可以將一個組內的伺服器簡單地分成兩類:場景相關的(如:行走、戰鬥等)以及場景不相關的(如:公會聊天、不受區域限制的貿易等)。經常可以見到的一種方案是:gate 伺服器、場景伺服器、非場景伺服器、聊天管理器、AI 伺服器以及資料庫代理伺服器。如下模型:

以上中我們簡單的講下伺服器的三種類型功能:

  • 場景伺服器:它負責完成主要的遊戲邏輯,這些邏輯包括:角色在遊戲場景中的進入與退出、角色的行走與跑動、角色戰鬥(包括打怪)、任務的認領等。場景伺服器設計的好壞是整個遊戲世界伺服器效能差異的主要體現,它的設計難度不僅僅在於通訊模型方面,更主要的是整個伺服器的體系架構和同步機制的設計。
  • 非場景伺服器:它主要負責完成與遊戲場景不相關的遊戲邏輯,這些邏輯不依靠遊戲的地圖系統也能正常進行,比如公會聊天或世界聊天,之所以把它從場景伺服器中獨立出來,是為了節省場景伺服器的 CPU 和頻寬資源,讓場景伺服器能夠儘可能快地處理那些對遊戲流暢性影響較大的遊戲邏輯。
  • 閘道器伺服器: 在型別一種的架構中,玩家在多個地圖跳轉或者場景切換的時候採用跳轉的模式,以此進行跳轉不同的伺服器。還有一種方式是把這些伺服器的節點都通過閘道器伺服器管理,玩家和閘道器伺服器互動,每個場景或者伺服器切換的時候,也有閘道器伺服器統一來交換資料,如此玩家操作會比較流暢。

通過這種型別伺服器架構,因為壓力分散了,效能會有明顯提升,負載也更大了,包括目前一些大型的 MMORPG 遊戲就是採用此架構。不過每增加一級伺服器,狀態機複雜度可能會翻倍,導致研發和找 bug 的成本上升,這個對開發組挑戰比較大,沒有經驗,很容出錯。

三型別(無縫地圖)

魔獸世界的中無縫地圖,想必大家印象深刻, 整個世界的移動沒有像以往的遊戲一樣,在切換場景的時候需要 loading 等待,而是直接行走過去,體驗流暢。

現在的遊戲大地圖採用無縫地圖多數採用的是 9 宮格的樣式來處理,由於地圖沒有魔獸世紀那麼大,所以採用單臺伺服器多程序處理即可,不過類似魔獸世界這種大世界地圖,必須考慮 2 個問題:

1、多個地圖節點如何無縫拼接,特別是當地圖節點比較多的時候,如何保證無縫拼接

2、如何支援動態分佈,有些區域人多,有些區域人少,保證伺服器資源利用的最大化

為了解決這個問題,比較以往按照地圖來切割遊戲而言,無縫世界並不存在一塊地圖上面的人有且只由一臺伺服器處理了,此時需要一組伺服器來處理,每臺 Node 伺服器用來管理一塊地圖區域,由 NodeMaster(NM)來為他們提供總體管理。更高層次的 World 則提供大陸級別的管理服務。

Node

一個 Node 所負責的區域,地理上沒必要連線在一起,可以統一交給一個 Node 去管理,而這些區塊在地理上並沒有聯絡在一起的必要性。一個 Node 到底管理哪些區塊,可以根據遊戲實時執行的負載情況,定時維護的時候進行更改 NodeMaster 上面的配置。

物件的無縫遷移

無縫遷移

玩家 A、B、C 分別代表 3 種不同的狀態,以及不同的遷移方式,我們分別來看。

  • 玩家 A: 玩家 A 在 node1 地圖伺服器上,由 node1 控制,如果遷移到 node2 上,需要將其資料複製到 node2 上,然後從 node1 移除。
  • 玩家 B:玩家 B 在 node1 和 node2 中間,此時由 node1 和 node2 維護,若是從 node1 行走到 node2 的過程中,會向 1 請求,同時向 2 請求,待全部移動過去了再移除。
  • 玩家 C:玩家 C 在 node2 地圖伺服器上,由 node2 控制,如果遷移到 node1 上,需要將其資料複製到 node1 上,然後從 node2 移除。

具體魔獸世界伺服器的分析,篇幅過多,我們以後再聊。

3、房間伺服器(遊戲大廳)

房間類玩法和 MMORPG 有很大的不同,在於其線上廣播單元的不確定性和廣播數量很小。而且需要匹配一臺房間伺服器讓少數人進入一個伺服器。

這一類遊戲最重要的是其“遊戲大廳”的承載量,每個“遊戲房間”受邏輯所限,需要維持和廣播的玩家資料是有限的,但是“遊戲大廳”需要維持相當高的線上使用者數,所以一般來說,這種遊戲還是需要做“分服”的。典型的遊戲就是《英雄聯盟》這一類遊戲了。而“遊戲大廳”裡面最有挑戰性的任務,就是“自動匹配”玩家進入一個“遊戲房間”,這需要對所有線上玩家做搜尋和過濾。

玩家先登入“大廳伺服器”,然後選擇組隊遊戲的功能,伺服器會通知參與的所有遊戲客戶端,新開一條連線到房間伺服器上,這樣所有參與的使用者就能在房間伺服器裡進行遊戲互動了。

以上就是目前遊戲伺服器的演化程序,由於所涉及的內容太多,關於伺服器的相關網路 IO 以及記憶體模型都沒有介紹,以後有機會再具體講講這一部分。

作者介紹

wier,樂元素leader軟體工程師。從2010年起從事遊戲開發,經歷過頁遊和手遊兩個遊戲發展期,期間曾帶領團隊開發過山寨機上第一款偷菜遊戲,如今專注於二次元遊戲領域及伺服器技術研究,運維了一個遊戲公眾號,期待用自己的一點努力和貢獻,推進遊戲社群的前進。個人微訊號ID:wierWu(新增時請註明‘InfoQ’) ;微信公眾號:大碼侯(cool_wier)

文章來自微信公眾號:高效開發運維