深入淺出 node.js 遊戲服務器開發 1——基礎架構與框架介紹

遊戲服務器概述

沒開發過遊戲的人會覺得遊戲服務器是很神祕的東西。但事實上它並不比 web 服務器複雜,無非是給客戶端提供網絡請求服務,本質上它只是基於長連接的 socket 服務器。當然在邏輯複雜性、消息量、實時性方面有更高的要求。

遊戲服務器是複雜的 socket 服務器。

如果說 web 服務器的本質是 http 服務器,那麼遊戲服務器的本質就是 socket 服務器。 它利用 socket 通訊來實現服務器與客戶端之間的交互。事實上有不少遊戲是直接基於原生 socket 來開發的。 相對於簡單的 socket 服務器,它承受着更加煩重的任務:

  • 後端承載着極複雜的遊戲邏輯。
  • 網絡流量與消息量巨大,且實時性要求極高。
  • 通常一臺 socket 服務器無法支撐複雜的遊戲邏輯,因此在 socket 服務器的背後還有一個服務器羣。

爲什麼純粹的 socket 服務器還不夠好?

很多 web 應用不會基於原生的 http 服務器搭建,一般都會基於某類應用服務器(如 tomcat)搭建,而且還會利用一些開發框架來簡化 web 開發。 同樣,一般遊戲服務器的開發都會在 socket 服務器上封裝出一套框架或類似的應用服務器。爲什麼使用原生的 socket 接口開發不夠好呢?

  • 抽象程度。原生的 socket 抽象程度過低,接口過於底層,很多機制都需要自己封裝,如 Session、filter、請求抽象、廣播等機制都要自已實現,工作量很大,容易出錯,且有很多的重複勞動。
  • 可伸縮性。高可伸縮性需考慮很多問題,消息密度、存儲策略、進程架構等因素都需要考慮。用原生的 socket 要達到高可伸縮性,需要在架構上花費大量的功夫,而且效果也未必能達到開源框架的水準。
  • 服務端的監控、管理。很多服務器的數據需要監控,例如消息密度、在線人數、機器壓力、網絡壓力等,如果採用原生 socket,所有這些都要自己開發,代價很大。

用框架來簡化遊戲服務器開發

一個好的框架可以大大簡化遊戲服務器的工作。除了遊戲自身的邏輯外,大部分的工作都可以用框架來解決。服務端的抽象,可伸縮性,可擴展性這些問題都可以通過框架來解決。 遊戲服務器框架也承擔了應用服務器的功能。可以把框架看成容器,只要把符合容器標準的代碼扔進去,容器就運行起來了。它自然具備了抽象能力、可伸縮性和監控、管理等能力。

遊戲服務器框架介紹

在開源社區裏充斥了數不清的 web 服務器框架,遊戲客戶端的框架和庫也有一大堆,但唯獨遊戲服務器框架少之又少,零星有一些類庫,但完整的解決方案几乎沒有。我們只好從商用的解決方案中拿出一些框架進行類比:

Sun RedDwarf

RedDwarf 是唯一一個能找到的完整的開源遊戲服務器框架,由 sun 出品。可惜在它合併到 Oracle 以後已經停止開發了。 在設計上,RedDwarf 是個分佈式架構,它在分佈式數據存儲和任務管理上投入了太多精力,而且做的過於理想化,如動態任務遷移功能的實現非常複雜,但實際應用中根本用不到。而在可伸縮性和性能的設計上不太理想。因此 RedDwarf 夭折了。

SmartfoxServer

SmartfoxServer 是由意大利的一家遊戲公司 gotoAndPlay() 推出的商用遊戲服務器。 它是基於 java 開發的,與 web 應用服務器如 Tomcat 看上去很類似。Smartfox 支持各種客戶端,且有一些成功案例。它在服務端封裝和監控管理方面實現得很完善。 但在可伸縮性上並不是太理想,儘管 Smartfox 也支持 Cluster 模式,但它的擴展方式是基於 jvm 內存複製的。也沒有實現傳統 MMORPG 基於場景分區的解決方案。 Smartfox 有免費版本,但完全不開源。而且它的免費版本 (達不到高併發用戶要求) 很大程度是爲了吸引開發者最終購買它的收費版本。不限在線人數的收費版本價格達到 3500 美刀。

BigWorld

Bigworld 是澳大利亞 Bigworld 公司開發的全套 3d MMORPG 遊戲解決方案,解決方案包含了客戶端和服務端。Bigworld 功能非常強大,在動態負載均衡和容錯性做了很多工作。可擴展性非常強大。 它的缺點是過於重量級,對硬件要求高,且價格非常昂貴。Bigworld 是專門爲 3d MMORPG 遊戲定製,但並不適用於中小型遊戲的開發。

Pomelo

Pomelo 是網易於 2012 年 11 月推出的開源遊戲服務器。它是基於 node.js 開發的高性能、可伸縮、輕量級遊戲服務器框架。 它的主要優勢有以下幾點:

  • 開發模型快速、易上手,基於 Convention over configuration 的原則,讓代碼達到最大的簡化。
  • 架構的可伸縮性和可擴展性好,pomelo 在服務器擴展和應用擴展上實現得非常方便。
  • 輕量級,雖然是分佈式架構,但啓動非常迅速,佔用資源少。
  • 參考全面,框架不僅提供了完整的中英文檔,還提供了完整的 MMO demo 代碼 (客戶端 html5),可以作爲很好的開發參考。

Pomelo 目前的主要缺點是推出時間尚短,一些功能還在完善中,支持的客戶端類型還有限,目前已支持 HTML5、ios、android、untiy3d 等 4 類客戶端,未來還會支持更多的客戶端類型。

遊戲服務器的可伸縮性探討

不管是 web 應用還是遊戲服務器,可伸縮性始終是最重要的指標,也是最棘手的問題,它涉及到系統運行架構的搭建,各種優化策略。 只有把可伸縮性設計好了,遊戲的規模、同時在線人數、響應時間等參數才能得到保證。

爲什麼遊戲服務器的可伸縮性遠遠不及 web?

相比 web 應用幾乎無限擴展的架構(前提是架構設計得好),遊戲服務器的可伸縮性相比就着差遠了。那麼是哪些因素導致遊戲無法達到 web 應用的擴展能力呢? 說明:本文提到的 web 應用不包括類似於聊天這樣的高實時 web 應用,高實時 web 可認爲是一種邏輯較簡單的遊戲。

長連接和響應實時性

web 應用都是基於 request/response 的短連接模式。佔用的資源要比一直 hold 長連接的遊戲服務器要少很多。Web 應用能使用短連接模式的原因如下:

  • 通訊的單向性,普通 web 應用一般只有拉模式
  • 響應的實時性要求不高,一般 web 應用的響應時間在 3 秒以內都算響應比較及時的。

而遊戲應用只能使用長連接,原因如下:

  • 通訊的雙向性,遊戲應用不僅僅是推拉模式,而且推送的數據量要遠遠大於拉的數據量
  • 響應的實時性要求極高,一般遊戲應用要求推送的消息實時反映,而實時響應的最大時間是 100ms。

在高併發長連接服務的解決方案中,目前除了傳統的 C 語言(過於重量級)實現,用的最多的是 erlang 與 node.js。兩者的性能指標差不多,而 node.js 在易用性方面毫無疑問勝出太多。

最近微博上看到時 go 的能撐起 100 萬的併發連接,node.js 也能達到同樣的數據, Node.js w/1M concurrent connections! 有 node.js 的長連接數據,它佔用了 16G 內存,但 CPU 還遠沒跑滿。

交互的相鄰性與分區策略

普通的 web 應用在交互上沒有相鄰性的概念,所有用戶之間的交互都是平等,交互頻率也不受地域限制。 而遊戲則不然,遊戲交互跟玩家所在地圖(場景)上的位置關係非常大,如兩個玩家在相鄰的地方可以互相 PK 或組隊打怪。這種相鄰的交互頻率非常高,對實時性的要求也非常高,這就必須要求相鄰玩家在分佈在同一個進程裏。 於是就有了按場景分區的策略,如圖所示:

深入淺出node.js遊戲服務器開發1——基礎架構與框架介紹

一個進程裏可以有一個場景,也可以有多個場景。這種實現帶來了以下問題:

  • 遊戲的可伸縮性受到場景進程的限制,如果某個場景過於煩忙可能會把進程撐爆,也就把整個遊戲撐爆。
  • 場景服務器是有狀態的,每個用戶請求必鬚髮回原來的場景服務器。服務器的有狀態帶來一系列的問題:場景進程的可伸縮,高可用性等都比不上 web 服務器。目前只能通過遊戲服務器的隔離來緩解這些問題。

廣播

遊戲中廣播的代價是非常大的。玩家的輸入與輸出是不對等的,玩家自己簡單地動一下,就需要將這個消息實時推送給所有看到這個玩家的其他玩家。 假如場景裏面人較少,廣播發送的消息數還不多,但如果人數達到很密集的程度,則廣播的頻度將呈平方級增長。如圖所示:

深入淺出node.js遊戲服務器開發1——基礎架構與框架介紹

假如場景中 1000 個玩家,每人發 1 條消息,如果需要其它玩家都看到的話,消息的推送量將高達 1,000,000 條,這足以把任何服務器撐爆。

解決這個問題的方案:

  • 減少消息數量 --- 消息只發送給能看到的玩家。玩家能看到的只是屏幕的大小,而不是整張地圖的大小,這樣推送消息的時候可以只推給對自己的狀態感興趣的玩家。這個可以用 AOI(area of interested)算法來實現,在 pomelo 的庫 pomelo-aoi 中實現了簡單的燈塔算法。
  • 分擔負載,將消息推送的進程與具體的邏輯進程分離。如圖:

深入淺出node.js遊戲服務器開發1——基礎架構與框架介紹

這樣廣播邏輯與具體的進程邏輯就不會相互影響了,而且由於只有後端的場景服務器是有狀態的,前端負責廣播的服務器還是無狀態的,因此前端服務器可以無限擴展。

實時 Tick

實時遊戲的服務端一般都需要一個定時 tick 來執行定時任務,爲了遊戲的實時性,一般要求這個 tick 時間在 100ms 之內。這些任務包括以下邏輯:

  • 遍歷場景中的實體 (包括玩家、怪物等),進行定時操作,如移動、復活、消失等邏輯。
  • 定期補充場景中被殺掉的怪的數量。
  • 定期執行 AI 操作,如怪物的攻擊、逃跑等邏輯。

由於實時 100ms 的限制,這個實時 tick 的執行時間必須要遠少於 100ms,因此單進程內很多數據都會受到限制。

  • 場景內實體的數量受限制,因爲要遍歷所有實體
  • 注意更新的算法,所有的算法,包括 AI 在內都要在幾十毫秒全部完成
  • 注意 GC,full GC 最好永遠不要發生。一般 full GC 的時間都會高於 100ms,幸好 node.js 在內存少於 500M 時表現良好,只有小 GC。因此一定要控制內存大小。
  • 儘量分進程,進程的粒度越少,出現 tick 超時或 full GC 的可能越少。在多核時代裏,CPU 是最廉價的資源。

高可伸縮的運行架構

經過以上這些分析。我們可以得到現在的運行架構,如下圖:

深入淺出node.js遊戲服務器開發1——基礎架構與框架介紹

運行架構說明:

  • 客戶端通過 websocket 長連接連到 connector 服務器羣。
  • connector 負責承載連接,並把請求轉發到後端的服務器羣。
  • 後端的服務器羣主要包括按場景分區的場景服務器 (area)、聊天服務器 (chat) 和狀態服務器等 (status),這些服務器負責各自的業務邏輯。真實的案例中還會有各種其它類型的服務器。
  • 後端服務器處理完邏輯後把結果返回給 connector,再由 connector 廣播回給客戶端。 master 負責統一管理這些服務器,包括各服務器的啓動、監控和關閉等功能。

這個運行架構符合了剛纔提到的幾個伸縮性原則:

  • 前後端進程分離,把承載連接和廣播的壓力盡量分出去。
  • 進程的粒度儘量小,把功能細分到各個服務器
  • 按場景分區

前面提到 4 個遊戲服務器框架,只有 bigworld 和 pomelo 符合這樣的架構,當然 bigworld 實現的還要更復雜。 現在的問題是,這個運行架構是個分佈式架構,而且並不簡單,那就帶來以下問題:

  • 需要多少的代碼來實現這樣的運行架構?
  • 服務器類型、數量管理和擴展有點複雜,該怎麼管理?
  • 服務器之間會有一堆的相互 rpc 調用,實現起來怎麼簡化?
  • 分佈式的開發和調試並不容易,消耗資源量過大,過於重量級,多進程 bug 定位困難,該怎麼解決? Pomelo 和 node.js 將很輕鬆地幫我們解決這些難題,我們下一節將討論。

node.js、pomelo 與遊戲服務器

Node.js 的特點與遊戲服務器極其符合。列舉如下:

  • 對網絡 IO 的處理能力,node.js 生來就是爲 IO 而生的,而遊戲服務器剛好是網絡密集型的應用。
  • 單線程的應用模型,node.js 的單線程處理能力遠比其它語言強大,而單線程處理遊戲邏輯是最簡單,最不容易出錯,而且不可能出現死鎖、鎖競爭的情況。
  • 語言與輕量的開發模型。Javascript 語言已經不是昔日的吳下阿蒙,它不僅由於腳本語言的輕量、簡單帶來了開發效率的提升。還可以與一些類型的客戶端共享部分代碼,如 html5,unity3d 的 js 客戶端等。
  • 語言的動態性帶來了很多框架設計的便利,如設計 DSL,實現 Convention over configuration。儘管這方面比 ruby 稍差,但在 pomelo 框架中使用已經足夠好了。

Pomelo 是基於 node.js 搭建的遊戲服務器框架,它在靈活性、擴展能力,輕量級調試方面具有無可比擬的優勢。我們先簡單回答第三章最末的幾個問題:

  • 用 pomelo 來實現以上的運行架構幾乎是零代碼的,因爲它在設計時天生就具備這樣的架構。
  • 服務器類型、數量的管理極簡單,利用鴨子類型、目錄定義,只要一個簡單 json 配置文件就可以實現所有服務器的管理。
  • rpc 調用可以實現完全零配置,也不用生成 stub。感謝 js 語言的動態性,基於 Convention over configuration 的原則,可以直接實現 rpc 調用。
  • 分佈式的開發和調試只佔用很少的資源,啓動極其迅速,十幾個進程只用 10 秒不到的時間完全啓動。多進程調試與單進程調試沒有任何區別,只在一個 console 裏搞定。

在本系列文章後面將會陸續討論 pomelo 是怎麼實現以上如此方便的特性, 以及這些設計帶來的啓發。

小結

本文分析了遊戲服務器框架的市場現狀,一個高可伸縮遊戲服務器架構的設計原則及運行架構。Node.js 與 pomelo 在解決高併發和分佈式架構中起到的作用。下文我們將深入分析 pomelo 在解決複雜的遊戲服務器運行架構中提供了哪些便利。

轉自https://www.infoq.cn/article/game-server-development-1/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章