H2Engine服務器引擎介紹


H2Engine服務器引擎介紹#

簡介

  H2Engine服務器引擎架構是輕量級的,與其說是引擎,個人覺得稱之為平台更為合適。因為它封裝的功能非常精簡,但是提供了非常簡潔方便的擴展機制,使得可以用C++、python、lua、js、php來開發具體的服務器功能。H2引擎的靈感來源於web服務器Apache。大家都知道Apache封裝了瀏覽器的的連接和協議通訊,而具體功能邏輯則通過fastcgi的方式交由不同的編程語言實現,本人大學的剛接觸php的時候,看到在php里print的字符串直接就出現在瀏覽器里,當時的感覺就是哇!這接口設計的真是帥!因為每個程序員最先學會的就是print,就會感覺這個接口設計的真是簡單易用。所以php真是當之無愧的最好的編程語言(哈哈)。后來一直從事游戲服務器開發,發現在服務器引擎領域就一直沒有這種Apache類似的設計非常通用、易理解、易擴展的引擎。現在游戲服務器領域大部分項目都是各搞各的,每個主程各搞一套自己用的舒服的架構。有些大廠或者相關的公司開源了一些服務器引擎,乍一看特別吊,但是跟Apache+php的這種架構相比,其易用性難以望其項背。當然服務器的長連接模式比web的request/response的模式本質上有更大的復雜性,服務器引擎的設計難點主要有如下幾點。

  1. 通訊協議沒有標准。大家都知道,http有行業標准,所有瀏覽器都是按照標准與服務器通信的,而通信部分的實現是服務器最為關鍵的部分,服務器程序員一般都知道,《網絡編程》沒看過幾遍是寫不了服務器程序的。一般而言服務器會采用二進制通信,常見的組包格式有2字節協議號+2字節標記+4字節包體長度+包體數據,這種協議格式緊湊,2字節的標記留作擴展也比較夠用,比如是否啟用壓縮、加密等,但是這種對某些編程語言不是很友好,比如js就無法采用此種協議。
  2. 消息封包沒有標准。消息封包常見的有struct二進制、自研的序列化、pb、thrift、json等幾種形式,而在web領域,一般要不json要不xml。在服務器領域一般采用pb的較多。
  3. 編程語言多樣。服務器編程語言為了高效,總體以c++為主,但是java、c#、python、lua、php、js也越來越流行,尤其是c++嵌入lua的模式大行其道。讓服務器引擎像Apache一樣可以支持各種語言,實現上很有難度。
  4. 並發與異步。通常游戲服務器為了平衡游戲復雜性和性能,采用多進程且每個進程主邏輯單線程的方案,多進程增加吞吐,單線程的程序更好保證穩定性,為了主邏輯不阻塞,幾所所有的io操作都是異步完成的,這與Apache的理念有很大的區別,這使得Apache引擎很難封裝的像Apache那樣簡潔,市面上有些人嘗試了用協程簡化異步,但是目前還形成相對成熟的方案。
  5. 數據同步的復雜性。Apache中php也是多進程的,但是不共享數據,無狀態的php設計本身就大大降低了復雜性,但是長連接是有狀態的。php中把狀態數據放到memcahe、redis等內存數據庫中,游戲服務器的多進程架構中也難免有數據需要共享,比如行會數據,但是像php那種通過分布式內存數據庫同步方式獲取在性能上(比如實時rpg游戲)是無法忍受的。如果采用異步獲取,邏輯代碼勢必支離破碎,到處都是回調,難以維護。通常的解決方案是單獨拎出來一個進程處理共享數據,比如CenterServer處理行會請求,所有行會操作都會轉到CenterServer處理,再將結果同步到其他進程,這樣不存在數據競爭和同步的性能問題,但是邏輯因為異步仍然是復雜了特別多。
  6. 性能難以量化。大家都知道Apache提供了ab程序可以量化服務器的性能,在服務器領域幾乎沒有通用的量化工具。一般都是會上線前用機器人壓力測試一下,不能很好的量化各個接口的性能,web領域對接口性能量化的工具比較多,很成熟,確實值得研究學習,因為優化的原則就是現有數據再優化,必須知道哪些需要優化,優化完有多少效果。

  那么如何解決以上問題呢?經過閉關苦思七七四十九天,終於有所開悟,繼而設計出來了H2Engine服務器引擎。接下來本文將闡述H2架構的設計細節,以及是如何演化得來。

H2Engine服務器引擎的演化

  先看下最為常見的游戲服務器架構圖:

   這個架構是很成熟的,同時充分考慮了系統可伸縮性。Gate和GameServer是性能的關鍵,這兩個都可以平行擴展,H2引擎就是從這個架構抽象而來。首先看Gate這個組件,每個Client連接一個Gate,而GameServer具體有多少個是對client透明的。因為可以啟動N個Gate,所以這個架構理論上可以支持N個Client。linux實現的Gate單個進程撐2萬連接已經不是問題,但是對於分服方式的RPG游戲,有哪個能做到單服在線2萬的?我們的游戲都是限6000在線上限,超過就得排隊了。主要是怕后邊GameServer太卡,因為玩家有聚集效應,都會集中在比較熱門的地圖上。所以當今linux epoll單機如此高性能的基礎上,單個gate進程玩家就足夠應付一個區服的Client連接。所以在上面的架構圖中簡化為單gate,如下圖:

   這個時候發現LoginServer的功能就有些雞肋了。LoginServer本來是類似於DNS的功能,它會返回負載最小的Gate給Client,從而保證Gate的負載均衡,但是現在已經單Gate了,LoginServer變得不是很有必要了,原來的LoginServer上的賬戶驗證功能完全移植到GameServer來做。所以在H2引擎架構中,不再有LoginServer的角色。

   Gate和GameServer肯定是不能少的了。DB是不是是必須的組件呢?答案是否定的。如果從DBServer發展的歷史來看,當DBServer出現的時候,內存數據庫還沒有興起,如今,Memcache、Redis等內存數據庫已經大行其道,無論從效率還是穩定性,或者靈活性上,都更值得推薦。從運維角度講,他們維護通用的內存數據庫也更有經驗。但是就本人看來,大部分情況下連Memcache、Redis這種都不需要,直接GameServer緩存一下就行了(主要是處理下斷線重連,手游閃斷還是很頻繁的),因為GameServer本身就是有狀態的服務器, 從上線后玩家數據就已經載入內存了,相當於所有的讀操作都是緩存好的,所有的更新操作直接寫數據庫理論上完全可以撐住,而且直接寫數據庫也避免了小回檔問題。因為畢竟寫操作對於讀操作量級小太多。如果真的應用場景需要緩存數據,那么部署一個Redis吧。去掉了DBServer,H2引擎架構簡化成了只有Gate和GameServer,這次真的簡化到極限了。

   下面讓我們來討論N個GameServer應該放幾台機器上的問題。標准答案當然是需要幾台放幾台,但是如果你身邊有運維的話,他可能給出的答案是一台機器,為什么呢,原因其一是這樣運維更方便管理,下發程序、配置、重啟、監控等也更容易。原因其二是現在機器都是多核cpu,內存也是過剩的,單台機器的處理能力與往日不可同日耳語。GameServer是主邏輯單線程的,如果一台機器上部署一個,那么cpu資源無法得到更好的利用。就本人經驗而言,GameServer很少需要超過4個,為啥?想想看,如果一個RPG游戲單服設計在線1萬人,平均分配到每個進程也就是2500人,很輕松啊,當然如果人過多聚集在單個進程,那還有有可能單個GameServer成為瓶頸,這種情況多開GameServer也解決不了問題。從cpu利用上來說,GameServer主邏輯單線程只能用一個cpu內核,考慮到啟停io線程的計算需要一個cpu的計算量,那么平均2個cpu,4個GameServer也就是8個cpu,現在服務器沒有8核好意思說是服務器?以往經驗來看,玩家會比較集中在熱點地圖,一般會某個或某兩個GameServer相對會cpu較高。另外一個服務器角色Gate是io密集型的,所以和GameServer放到一個機器上,也是扛得住的。這樣在H2引擎中,完全有理由將進程全部跑在一個機器上,先上一個架構圖,然后再講一下這樣設計有何特點。

   到這里大家有沒有發現,跑在一台物理機的Gate和GameServer像不像Apache和php的關系?到此,H2引擎的雛形已經形成。Gate在這里扮演Apache的角色,GameServer在這里就是php的角色,Apache有一層fastcgi的東西實現進程間通信,只要按照fastcgi的標准,就可以讓Apache支持任何的編程語言,在H2引擎中,也設計了一套進程間通信機制ffrpc,區別於Apache的fastcgi,ffrpc是基於消息+回調機制的長連接通信方式。ffrpc的實現暫時不展開了,現在H2引擎里已經實現了c++、python、lua的支持。H2的雛形已經有了,還需要進一步的抽象完善,因為H2不僅可以用於游戲服務器,在實時聊天、消息推送等需要長連接的應用場景也可以適用。所以為了更加容易理解,對Gate和GameServer組件的名稱進行重新命名,變得更加通用一些。

   前邊講到服務器引擎設計的6大難題,下面討論下在H2引擎中是如何解決的。首先是通信問題,Apache通用是因為Client都是用http協議,那么可不可以讓游戲服務器的Client統一用某種通信協議呢?坦白說太難了。但是本人認為,隨着websocket的逐漸普及,websocket可能有一統江湖的可能。其實有了websocket大家自己設計通信協議的理由已經很小了。H2集成了兩種通信協議,websocket和普通的二進制協議,如果你的Client已經使用了websocket,那么接入H2就是so easy了。

   對於問題2數據封包的處理,H2給出的答案就是無為而治,既然沒有標准,那么H2也不干涉你的選擇自由,交給H2Worker處理,數據封包對於H2引擎是透明的,但是建議大家使用pb或者thrift就好了,H2的ffrpc就是使用了thrift完成的進程間通信。本人更推薦thrift,因為thrift對於各個語言的支持更好,對於js這種處理二進制尷尬的語言都兼容的很好。

   問題3的多語言問題,H2設計了ffrpc庫,每個語言只需要接入並實現幾個簡單接口就可以了,相當於每個語言都需要開發自己專用的H2Worker,比如H2WorkerPhp、H2WorkerPython、H2WorkerLua等,目前C++、Python、Lua、js、php的Worker實現已經集成到H2Engine中,也就是說如果你想用lua或者python來寫游戲服務器,那你直接寫腳本就可以了。H2Engine晚些會加入支持的語言是C#。

   問題4並發與異步的問題,H2Engine的設計是主邏輯單線程,提供一個IO線程池,IO操作用異步+回調的方式完成。其實IO操作主要就是數據庫操作,IO線程會創建一個異步IO句柄,每個IO句柄投遞的IO異步操作都是串行保證順序的,所以IO線程池既能夠保證多線程並發,又能夠保證比如針對某個User的操作是順序的、可靠的。

   問題6性能量化的問題,由於客戶端的請求通過引擎被處理,那么H2Worker上就可以收集到所有接口的性能數據,統計后格式化定時輸出,這樣就可以量化各個接口的的性能。甚至可以開發出圖形化展示工具,可以看接口性能隨時間的變化,或者不同接口間性能的比較。

   最后着重討論問題5數據共享的問題。前邊提到ffrpc提供了基於TCP進程間通信的機制,對於單機還是多機,都是無差別的,那么H2Engine和H2Worker理論上放不同機器也是可以的。事實也的確如此,H2引擎其實對於多機是完美支持的,但是為什么將H2的架構限制在同機器呢,這主要是考慮到數據共享的需求,同機情況下,H2Engine和H2Worker就可以通過共享內存共享數據,其效率和便捷性與多機tcp模式不可同日而語。經過權衡,要比較優雅的實現進程間共享數據,限制在同機可以大大的降低復雜性,雖然犧牲了一些可伸縮性。

  首先SharedMemory並不存儲共享的數據,只存需要更新的數據,相當於共享內存作為交換數據的媒介。進程間共享數據的流程如下:

  1. 每個H2Worker維護一個自己的ShareMemDataSet,在共享內存中創建一個信號量,並且單獨開一個線程,監聽在此信號量上,如果被觸發,則立即從共享內存拷貝要更新的數據到自己的進程,並投遞給主邏輯線程去更新SharedMemDataSet。由於ShareMemDataSet是主邏輯維護的,這樣的好處就是主邏輯線程如果只是讀取而不修改,那么直接使用本線程的SharedMemDataSet數據,性能自然是杠杠的,比如行會數據一般讀取操作遠大於寫操作。

  2. 如果H2Worker要修改共享數據,他就要獲取共享內存上的全局鎖,然后拷貝要更新的數據到共享內存,然后喚醒其他H2Worker的信號量,待所有數據被拷貝完畢后,解除全局鎖,因為更新操作一定是主邏輯操作的,所以獲取完全局鎖后,主邏輯會自動檢查一下本地要更新的操作是否全部完成,保證加鎖完畢后,當前進程的SharedMemDataSet一定是最新的。下面來一段模擬行會操作的偽代碼:

  這種數據同步有多個好處,首先是數據競爭,共享內存加鎖同步數據,效率非常高,使得加鎖的粒度較小,避免多進程鎖競爭。其二是更新操作很像發送消息,區別於異步發送消息的機制是,消息發送完,其他worker的數據立即得到了更新,這是異步消息發送機制不能比擬的。

總結

  1. H2引擎集成了websocket,也推薦大家在長連接應用中,逐漸使用websocket。
  2. 協議的封包pb、thrift已經很夠用了,H2引擎支持pb、thrift、json以及傳統二進制struct,但是推薦thrift,主要是效率和多語言支持都更好。
  3. 基於網游服務器的場景,H2引擎考慮到單台物理機的處理能力當前足以應付單服的需求,所以將H2的架構設計為部署在同機上,這樣大大簡化了服務器的架構,多gate的架構其實來源於rpg剛興起的年代,那時候服務器的內存有限,cpu多核也還沒流行,但是今非昔比,單機模式也就是偽分布式模式其實更符合實際。
  4. 針對傳統網游服務器架構中多進程數據共享的痛點,H2做了特殊的設計,由於H2Worker在同一台機器上,得以使H2可以通過共享內存共享數據。
      大家知道,Apache+php之所以在web領域里流行,還有很大一個原因是php的框架又多又好用,相比而言,網游服務器領域的引擎、框架都太落后了,主要原因還是服務器沒有形成標准,這也是本人從業多年,孜孜不倦想要有所突破的地方。從web的成熟經驗來看,功能開發的快,就要有好多框架,要有好的框架,就要有成熟標准的引擎,現在市面上有些游戲服務器引擎就經常會糅合引擎和框架的功能,有的甚至夾雜了游戲服務器的數據結構和游戲邏輯。H2的設計哲學,引擎的歸引擎,框架的歸框架,雖然跟Apache相比距離“引擎”的稱號相距甚遠,但是這是H2的目標。另外,基於H2的框架也會不斷的增加完善。舉個例子,針對rpg游戲,我們可以設計出一套c++的框架,比如封裝地圖管理、角色管理、道具管理、任務系統、成就系統、副本系統、npc系統等,想想看,2d rpg領域相關的系統還是很好抽象的。問題是沒有標准的、成熟的引擎作為基礎。相關從業人員應該有共鳴,比如A團隊開發一套任務系統,給B團隊也是用不了啊,大家的定時器、數據庫接口都不一樣,無法做到拿來就用。如果大家都用H2,別人開源的系統分分鍾就可以拿來用,想象下還是挺美好的。不同的游戲類型框架實現是不一樣的,不同語言實現細節也會不同,使用H2引擎后可以根據不同游戲類型、不同語言分類框架,這個是后續擴展H2引擎的計划。

相關連接

  1. 文檔 http://h2cloud.org
  2. 源碼 https://github.com/fanchy/h2engine


免責聲明!

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



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