騰訊互娛架構師談遊戲服務器緩存係統怎麼造
本文根據DBAplus社群第88期線上分享整理而成。
講師介紹
韓偉
騰訊科技互娛研發部架構師
-
曾在網易任職8年,擔任無線事業部產品總監。
-
多年來一直從事技術開發,擅長開發高性能係統,對於軟件架構設計也有豐富的經驗。
-
個人的技術興趣在設計模式、軟件體係架構等提高軟件開發效率方麵的知識。
主題簡介:
1、遊戲類業務的通信模型分析
2、遊戲類業務的數據處理流程分析
3、一般緩存係統的特點在遊戲中的問題
4、現代遊戲服務器端的幾個典型運行時架構
一、背景
在中國的互聯網諸多業務領域中,遊戲一直是充當“現金牛”而存在的。常言道“隔行如隔山”,遊戲領域和互聯網其他領域確實可以說是不同的兩個行業。但是,在遊戲服務器端開發領域中的很多重要問題,並沒有被明確的分辨出其特異性,從而得到專門的對待。
我們不管是在業界開源領域,還是內部分享中,很少會有專門針對遊戲業務特征進行專門設計的組件、類庫或者框架。我們從遊戲的客戶端方麵來看,一款專業的遊戲客戶端引擎,已經是遊戲開發的標配,比如最早的Flash Builder,到後期的Cocos2d-X,Unity,Unreal;但是服務器端,我們幾乎找不到同樣重量級的產品。
在遊戲服務器端開發所有要麵對的問題中,有兩個是最核心和最普遍的:一是和客戶端的通訊;二是遊戲登錄用戶的數據處理。對於和客戶端通訊的這個問題,大量的遊戲開發者會使用“通用”的開源組件,比如Protocol Buffer、Thrift、Jetty、Node.js等等通信或RPC框架。雖然針對遊戲,還是要做大量的改造,但一般都有很多現成的代碼可供修改。
在一般的互聯網應用中,我們一般認為服務都是通過請求-響應的方式來完成的。而在遊戲業務領域中,請求-響應可以看成是一種類型的通訊方式,但還有另外一種重要的通訊模型,就是“數據同步”方式:遊戲中某個角色的HP、位置坐標改變了,需要在客戶端和服務器之間、客戶端和客戶端之間同步。這造成了一般情況下通信協議的大量增加。
對於第二個問題,不管是Memcache還是MySQL,或者是Redis,都不能完全滿足遊戲開發者的需求。很多團隊嚐試過各種組合和修改,試圖創造出利用現有開源軟件,建設既能迎合靈活的需求變化,又具備高延遲和高可用的數據處理係統,但最後這些努力基本上都很難圓滿成功。因為我們在遊戲服務器端代碼中,還是充斥著大量的內存、緩存管理,數據同步、落地等等代碼。而且每個遊戲都要重新去寫一遍這些類似的功能,不能不說是一種浪費。
如果我們要想出一種能滿足“遊戲”這個業務領域的數據係統設計,那麼就一定要搞清楚為什麼在如此之多的開源項目和遊戲團隊中,沒能實現完美契合的原因。
二、電子商務/一般互聯網業務的C-S通訊流程
基於Web Service類型的通訊模型,現在基本已經成為互聯網開源組件的標準。由此而誕生的RESTful API,或者各種RPC模型,其實都是基於這樣的客觀事實:
用戶主動請求,服務器產生回應。典型的就是網頁的點擊、表單的提交。
主動通知的消息,僅僅是提示用戶發起查詢請求。比如在APP按鈕上的小紅點,消息頁的數字提示等等,這些主動通知都是為了通知用戶去刷新頁麵。
三、遊戲類業務的通信模型分析
遊戲中的通信,一般和操作有關。這些操作一般分為兩類:
-
UI麵板類操作
-
戰鬥場景操作
這兩者的最大區別,就是UI麵板類操作一般無需讓其他玩家看見。而戰鬥場景操作則需要廣播給所有玩家看到。
在第二種情況下,一般就不是客戶端主動發起,而是服務器端直接推送實際數據,然後客戶端直接顯示這些數據。這個模式和簡單的“推送”還不一樣,而應該更進一步,是一種從服務器端發起的,向客戶端“同步”數據的請求。
因此,一個好的遊戲服務器端框架,應該是能同時支持請求-響應模型和“推送同步”模型的。
四、電子商務/一般互聯網類業務的數據處理流程
Memcache、Redis、MySQL在一般互聯網業務中的應用非常廣泛。而且基本上能很好的應對各種常見的應用場景,包括類似BBS的社區、新聞門戶、電子商務類係統。
在企業內部信息係統中(Intranet),這一類數據軟件也能發揮非常好的功效。由於電子商務類是其中最複雜的係統,所以我在這裏以此為例說明,一般數據處理的流程是如何的。
假設我們瀏覽了一個網店,選中了一個商品,點擊了下單這個流程,實際上需要的後台流程可能是下圖所示:
從上麵的分析大概可以總結出幾個特點:
1、忍受延遲:每個操作的延遲要求較低,操作頻率不會太高。一般我們頁麵在5秒內打開,都不會引起太多客戶的抗議。所以,就算我們處理一個請求的時候,後台進行多次的進程間調用,產生的延遲和帶寬消耗也是可以忍受的。
2、在線交互少:互聯網業務大多數是基於瀏覽器的,所以在線用戶之間很少實時交互。
3、數據分散:一般來說,互聯網應用的數據可以在多個不同的業務係統中共用,但是需要專門的業務模塊來做管理,以維持數據的一致性。
4、數據變更麵廣:係統需要持續處理很多數據變更,互聯網業務有很大一部分數據是來源於普通用戶、網絡編輯、店主等等使用者,在使用的過程中,他們會大量的修改係統所存儲的數據。
以上四個特點,導致了我們一般會把後台要處理的數據,分別用Cache係統和DB係統來處理。並且,我們一般會按業務功能劃分模塊,同時也劃分業務係統。由於延遲和在線交互的需求較弱,所以使用大量進程來做模塊隔離,依然是非常可行的,總體來說,就是一種比較“分散”的數據使用方式。
五、遊戲類業務的數據處理流程分析
在各種遊戲中,MMORPG是數據處理最為複雜的一類,也是最典型的一種“重服務器端”的遊戲類型,因此可以作為遊戲業務中通用性的參考標準。在MMORPG中,我們可以發現,數據的處理需求,和一般互聯網業務大相徑庭,它體現出的是一種明顯的“集中”式的數據處理需求。我們可以從一般MMORPG的服務器架構中體現出來:
在遊戲業務中,一般我們都會發現以下的特點:
1、延遲敏感:遊戲中用戶會產生大量操作,都要求“實時”進行反饋,所以一般都不能忍受1秒以上的延遲,在大量動作類型的遊戲中,一般都會要求服務器的反饋時延在50ms左右。因此遊戲開發者都習慣於盡量減少後台進程間的交互,盡管這對提高係統吞吐量很不利。所以大部分遊戲服務器端都有一個所謂“GameServ-er”,裏麵運行了遊戲70%以上的功能。
2、大量實時交互:在線遊戲的特點,就是很多玩家可以通過服務器“看見”彼此,能實時的互動。因此我們必須要把用戶的在線數據,集中到一起,才能提供互相操作的可能;而且A用戶操作B用戶的數據,是最常見的數據操作,所謂戰鬥玩法,就是互相修改對方的數據的過程。
3、數據集中:遊戲是一個幾乎完全虛擬的世界,在遊戲中的數據,實際上很少能在其他係統中產生價值。而遊戲邏輯也禁止通過遊戲以外的方式,修改遊戲的數據。所以遊戲中的數據,一般都會集中存放在單獨的數據庫中。由於沒有數據共用的需求,所以也不需要把GameServer裏麵集中的邏輯劃分出很多單獨的進程模塊來。
4、數據變更少:實際上遊戲的數據變更還是很快的,比如遊戲中的每次中彈,都要減少HP的數值。但是遊戲裏的數據,一般都遵守這樣一個規則:“變化越快的數據,重要性越低”。也就是說,遊戲中是可以容忍一定程度的數據不一致和不完整的。而遊戲中的數據,一般會分成兩類:玩家存檔和遊戲設置。
對於玩家存檔來說,其單條數據量一般不大,但會有大量的記錄數,因為每個玩家都會有一個存檔。但是其讀取、修改,一般很典型的和玩家的登錄、登出、升級等業務邏輯密切關聯,所以其緩存時機是比較容易根據業務邏輯來把握的。而對於遊戲設置數據來說,幾乎隻有升級遊戲版本的時候才會修改,大部分運行時是隻讀的,其緩存簡單的讀入內存就解決問題了。
六、一般緩存係統的特點在遊戲中的問題
根據以上的分析,我們可以看到,普通的緩存係統,如Memcache和Redis,實際上其特點是不太適合遊戲業務的:
一般跨進程的緩存係統,無法解決遊戲要求的低延遲問題。級別是同機房,每次數據存取都需要10-20ms的時間,對於遊戲戰鬥中大量的數據讀、寫來說,是很難接受的。(但是一些回合製戰鬥、低頻操作還是有用的)
通用型的緩存係統或者數據庫,一般都比較難集結多個進程,形成一個完整的數據存儲網格。這讓玩家間的互相交互產生了額外的難度,開發者必須先想辦法確定玩家的數據在哪個後台進程上,然後才能去讀寫。一般的數據庫或緩存係統,為了保證數據的一致性或者完整性,往往會需要犧牲一些分布式的能力。而這種犧牲在遊戲業務中,其實是一種浪費,因為遊戲的很多數據都無需這種能力。
通用性數據係統一般不依賴於特定的語言,所以很少能直接把某種“對象”存入到數據係統中。在遊戲開發中,需要存儲的數據結構數量往往是非常大量的:一個普通的遊戲,基本上都會超過100種數據結構。對於每個數據結構,都去建表或者編寫序列化/反序列化配置,是一種非常累人的工作。——明明在代碼中,已經用編程語言定義了他們的結構,還要重複的搞一次。
根據上麵說的這些問題,我們實際上是需要另外一種完全不同設計思想的數據係統。對於遊戲業務來說,一個好用的數據係統,應該包括這樣一些特點:
可以利用GameServer進程內的內存進行自動化的緩存管理。由於GameServer進程往往集中了大部分的邏輯運算,所以大部分的數據緩存也應該在這個進程中,這樣才能符合遊戲所需的延遲要求。
自動進行數據落地和容災管理。由於遊戲數據中有大量的“過程數據”,所以其一致性和完整性要求會稍微低於其他業務,所以應該利用這一點,讓GameServer本身也可以是分布式的程序,從而提高係統整體的吞吐量。
具備良好的編程易用性。最好是能直接存取編程中的對象,避免反複對數據結構的描述,節省大量的開發時間。
七、現代遊戲服務器端的幾個典型運行時架構
遊戲本身的邏輯複雜性,導致了架構上也是分成很多不同的“門派”。和互聯網/電商日漸趨同的架構不一樣,遊戲的“運行時架構”,往往會向著不同方向更加的分化,而不是統一。下麵就講講遊戲領域架構的幾個主要分支:
1、MMORPG
這一類遊戲主要采用“分區分服”類的架構。典型例子有《石器時代》《傳奇》。從表麵上看,這類遊戲的服務器架構似乎非常簡單,就是硬生生的把遊戲世界按照硬件集群分開來,克隆出很多的個平行的遊戲世界。服務器中的通訊、計算、存儲能力都是每個遊戲世界單獨一份。但事實上,並沒有這麼簡單。MMORPG類的遊戲,在服務器端主要有幾個挑戰:
一個是海量網絡廣播的挑戰,由於有大量的玩家的實時互動,所以需要廣播大量的數據包;
第二個是大量計算任務需要快速的數據緩存的挑戰,在遊戲的戰鬥中,每一個動作都幾乎需要對數據做讀寫,由於涉及大量不確定的玩家數據,所以在整個服務器中快速查詢、修改玩家數據變的延遲變得非常苛刻。
但是MMORPG類遊戲,在業務領域上又有幾個突出的特點:
一是基本都是對在線玩家數據的操作,很少像電商那樣,都是對持久化數據做操作;
二是在線數據的分布,有一個虛擬的“遊戲地圖”作為分布的脈絡,玩家總是從一個遊戲場景,走向另外一個場景,數據是按場景來聚合的;
三是遊戲邏輯雖然複雜,但較少產生關聯性的查詢,隻要少量的如“拍賣行”,“排行”這樣的數據,是需要比較複雜的關聯到其他數據單元的。
所以MMORPG的服務器端架構,很自然的就采用以內存作為整個虛擬世界的緩存,然後按遊戲地圖進行進程分布的樣子。由於數據都在內存中,才能滿足戰鬥的低延遲響應,如果好像電商類,每一個操作要幾百毫秒的話,遊戲簡直就沒法玩了。而按地圖分布的進程,可以讓玩家在遊戲的過程中,在切換地圖的過程中,把內存數據在進程間搬遷,這樣既自然又實用,因為大部分的數據關聯操作(比如戰鬥)都是以遊戲地圖為紐帶的。
在持久化存儲上,基本上都很容易使用NoSQL來做,因為幾乎都是在玩家登錄的時候加載數據到內存,離線登出時回寫到持久化且銷毀內存數據,所以完全可以隻通過一個索引來完成玩家數據的存取。早期很多網絡遊戲甚至直接使用文件係統簡單的進行玩家存檔的管理。
上圖非常簡單的描述了MMORPG遊戲服務器的基本架構,但是這隻是核心邏輯的部分,實際上還有很多其他的服務器進程並沒有畫出來,比如聊天服務器、目錄服務器、各種協調的proxy和dir服務器。那些服務器一般都以數據緩存的特點存在,如果是需要全局的,比如“拍賣行”服務器,就會讓所有數據集中在一個進程上(也可能按拍賣的物品種類分布,外加搜索索引服務器進程)。
2、棋牌類
和MMORPG不同,棋牌類服務器的特點是,需要海量的用戶在一起玩,比如需要有一個容納所有玩家的遊戲大廳,可以在那裏找到所有的在線玩家一起玩;但是每個具體的戰鬥過程,又是小規模幾個玩家交互的,比如需要同時開啟成千上萬的房間(或者桌子)來進行具體的遊戲。因此這類遊戲的服務器挑戰,就有兩個:一是全局所有玩家需要互相交互的需求;二是管理大量的小群體玩家動態交互通信群組的需求。
為了解決這兩個問題,一般來說服務器端會分為兩個部分,一是大廳服務器,一是房間服務器。大廳服務器是一個巨大的廣播集群,負責不太實時的數據傳輸和查詢。房間服務器是一組可以快速租用、退還的小型實時廣播服務進程。
在大廳服務器中,所有的在在線的玩家,都按其ID來分布在多個進程中的一個,在玩家之間的查詢、廣播操作時,采用多個服務器並行操作,最後匯總結果的方式來提供。這樣的操作延遲是會比較高,但是能讓海量的用戶數據存儲到不同的機器上。
而房間服務器則會負責提供具體的遊戲廣播功能,一旦玩家組成了群組進入,大廳服務器會拷貝數據到房間服務器,而房間服務器就隻對這幾個玩家負責了,遊戲結束則清理掉這些玩家數據,準備新的遊戲。
3、MOBA類
從本質上來說,類似《英雄聯盟》這類遊戲,和棋牌類遊戲會比較像,隻不過他們的遊戲邏輯比較複雜,所以“房間服務器”裏麵要運行的邏輯會比較多,不似棋牌類那麼簡單(甚至可以通用,隻要有廣播功能即可)。在MOBA類遊戲裏,有一個最大的難點,是“自動匹配”,這個功能在棋牌的大廳服務器裏是比較少見的。
隻有盡量在更大的範圍內匹配在線玩家,等待的時間才會更短,匹配結果才更準確;然而大量的數據充斥在有限的進程空間中,本身又會導致承載壓力,被迫要把數據分散到其他進程去。這個矛盾是貫穿匹配係統的問題。
為了解決這個問題,有些遊戲采用退縮的策略,就是降低匹配的準確度,盡快的把玩家匹配起來。這樣隨機的把玩家放入不同進程的匹配隊列中,隻要人滿了就開始玩,這樣也是可以的。有一些遊戲則費比較大的功夫,做一個分布式的內存緩存,希望盡量多的玩家在一起匹配,付出的代價就是需要更多服務器間的數據交互,以及延遲。
上麵說的這集中典型的模式,在現在的遊戲服務器架構中,往往並不是單一出現的。比如現在的《魔獸世界》,就可以讓存檔在不同服務器的玩家,都連到同一個在線場景服務器中玩。而如《DNF》這類遊戲,社交場景所連接的“大廳服務器”本身也是可以按地圖劃分的。盡管遊戲服務器架構的形態日趨複雜和分化,但其中的思想是統一的,就是“按業務邏輯所要求的數據緩存布局”來分布。
八、總結
遊戲服務器和普通互聯網業務服務器端,最大的區別實際上就在於“狀態”。遊戲服務器的狀態是實時快速變化的、可以容忍丟失的、需要大量廣播同步的;普通互聯網業務服務器的狀態一般是持久化的、不容忍丟失的、隻和特定客戶端相關的。
所以一個好的遊戲服務器框架,在通訊和數據這兩個基本層麵,會和一般我們所接觸的開源組件有很大的差異。所以現在大部分的遊戲公司的服務器端,其實都不是使用完整的一個框架,甚至幾個不同的項目,其服務器端架構都不一樣。大家在看到阿裏巴巴公司共享大量的開源軟件的時候,也應該看到電子商務的業務特點,其實是比較適合做這種統一框架的。網易、騰訊、金山在遊戲研發領域,其實也有很多的經驗,但是很少能有一些開源軟件公開出來。主要原因還是遊戲領域的“通用性”太難把握。
Q&A
Q1:請問老師,現在有些遊戲開始做全球同服,他們是怎麼玩的?
A1:一般MMORPG是不會做全球同服,MOBA類和棋牌類是可以的。這一類架構,通過延遲較高的大廳服務器和分布全球不同機房的“房間服務器”來組合實現。現在歐美地區,在亞馬遜雲服務器上部署遊戲,到各地的延遲還是不錯的,所以也有一些遊戲公司直接全部使用亞馬遜。但是實際上,某些國家連上去還是很慢,比如我國、越南、俄羅斯。
Q2:能介紹下如何實時推送嗎?
A2:實時推送在遊戲裏麵,一般是TCP長連接下發數據,或者是UDP直接下發兩種底層實現。遊戲會自己實現從sendto()/recvfrom()到數據的編解碼整套流程,所以這也是遊戲框架難以統一的一個原因。遊戲的代碼風格,一般不會是請求-回應方式的,而是“收到某個類型的包”去處理的模式,這和“消息隊列”的代碼模式有點像。很多遊戲的網絡處理模塊,包括服務器、客戶端,都是一個大的switch...case...,裏麵按每個數據包的“包頭協議ID”來做分發處理。而不是類似RPC那種風格的。
Q3:基於遊戲的低延遲、狀態、內存數據特性,很多工作都在進程內完成,運維、DBA工作有什麼特別的地方,有什麼痛點、難點嗎?或者說從韓老師作為開發的角度,希望運維怎麼樣更好的支持?
A3:遊戲的數據邏輯,大部分不依賴數據庫,幾乎沒人在遊戲領域用存儲過程,這方麵DBA可以鬆一口氣,但是運維的部署工作就繁重很多。因為一套遊戲裏麵,會分很多“區”和“服”,常常因為業務運營的需求,去做開新服開新區。這就代表著運維要不停地做新的服務器進程的部署。而且遊戲裏麵的進程種類繁多,沒有類似servlet容器這樣的概念,每個進程都必須配置正確,整個遊戲才會沒有問題,而每個遊戲的這套玩意都不一樣。
除了開新服,合並老服務器也是大問題,因為存在兩個數據庫的記錄要合並起來,設計不好就有很多索引衝突,邏輯上的故障,這其實才是DBA最常要解決的問題。舉個例子,有一個很著名的MOBA遊戲,他的運維部署工具是某個員工寫的,後來這哥們離職了,就再也沒有人能修改這個工具,也沒人能理解這個工具,大家都隻能跟著以前的做法去用。
原文發布時間為:2017-01-04
本文來自雲棲社區合作夥伴DBAplus
最後更新:2017-05-13 08:43:27