go語言遊戲服務端開發(一)——架構

五邑隱俠,本名關健昌,12年遊戲生涯。 本教程以Go語言為例。
 
網絡遊戲程序分為客戶端和服務端。客戶端負責圖形渲染、交互和一些簡單校驗處理,服務端負責業務邏輯處理、數據存儲。
我們開發一個遊戲demo,服務端程序可以是一個單線程的服務進程。它包含網絡通信、業務邏輯處理、數據存儲。服務端打開網絡端口監聽,客戶端通過網絡連接到服務端,服務端接入連接。客戶端發包給服務端,服務端接收到包後進行解析,調用對應的處理程序進行處理,處理程序處理成功後,修改數據並保存下來,再把響應包封包發送給客戶端。
0
簡單的數據存儲可以保存在文件里。當用戶量逐漸增加,數據存儲的性能、完整和安全要求逐漸增大,數據存儲改為存儲到數據庫。這樣服務端就分為服務進程、數據庫進程兩個進程。服務進程跟數據庫進程通信,進行數據讀寫。由於遊戲的實時要求比較高,如果每個請求都要讀寫數據庫,當大量用戶同時訪問時,數據庫讀寫成了服務性能的瓶頸。因此,一般遊戲都做緩存,簡單的緩存可以緩存在服務進程的內存里,業務處理直接操作緩存數據,每隔一段時間(例如10分鐘),用個獨立線程把被修改的緩存數據保存到數據庫。
0
這樣會有一個問題,就是一旦服務進程宕機,在保存間隔時間裏的遊戲數據就會丟失掉。特別是遊戲服務進程有更新上線時,穩定性還沒有被線上並發驗證,宕機的幾率會增加,數據丟失的風險也會增加。為了減輕風險,可以考慮把數據緩存跟服務進程分離。使用共享內存,或者使用第三方的專業緩存程序(memcached、redis)作緩存。這樣數據丟失的風險就大大降低了。
0
隨着遊戲業務開發,遊戲業務不再是簡單的請求、返回,它還有一系列推送需求,如聊天、郵件系統,這類需求可能是全員推送,這會佔用服務進程的CPU時間,導致請求響應變慢。而且這種推送時效性要求沒有遊戲其他業務高,因此可以把這種廣播類型的推送抽離出來放到一個單獨的廣播進程,服務進程通過向廣播進程發送廣播消息到廣播隊列,廣播進程從廣播隊列獲取消息進行廣播。這樣又產生了新的問題,客戶端需要連接到兩個服務端的進程。為了解決這個問題,可以增加網關進程,客戶端只需要連接到網關進程,由網關進程代替客戶端將請求發送到服務進程進行處理,服務進程、廣播進程的響應/推送進入網關,由網關確定轉發給哪個客戶端連接。
0
這樣服務端就變成了5個獨立進程的進程組,接下來對數據庫和數據緩存做下技術選型,可以選擇業界用得比較多的mysql數據庫、redis緩存。利用redis的list結構做消息隊列建立起服務進程和廣播進程的通信。
0
這些進程都部署在一台機器上,所以目前服務端的處理能力受限於一台機器的硬件性能。當然我們也可以將這5個進程分散到多台機器上,如果玩家人數再增加,可以把網關進程、服務進程和廣播進程集群,redis、mysql改成分佈式緩存和分佈式數據庫,這樣也能擴容。更通用的做法是對遊戲分服,每個分服的數據互相隔離。這是目前市面上大多數遊戲的做法,滾服運營也已經是比較成熟的運營手段。由這5個進程組成的進程組,1個分服對應一個進程組,一個進程組部署在同一台機器上。這樣通過分服就可以橫向支持更多的玩家並發訪問。
這樣又出現了新的問題,客戶端怎麼知道該連接哪個分服。而且現在的遊戲,同一個玩家可以在不同的分服進行遊戲,以達到好友同玩一個服。一般的,在玩家進入分服前,讓玩家先進行全局登錄,然後根據遊戲類型,要麼下發分服列表由玩家自己選擇分服,要麼給玩家指定分服(例如類似COC的所謂不分服遊戲,這個分服可以理解為一個節點)。增加一個全局的賬號進程,負責遊戲的全局登錄、下發分服信息。賬號進程還負責支付回調,進行訂單確認和發貨。
0
隨着遊戲運營推廣,分服數量越來越多,遊戲迭代也需要對服務進行維護,靠人工維護分服列表顯得越來越笨拙。應該建立一個自動化的機制,當開新分服,或者分服進入維護的時候,自動更新分服列表。在這裡可以通過服務註冊發現的機制來實現自動化。利用一個全局的redis作為註冊中心,所有分服的信息通過服務編號作為字段,都保存在redis的一個hash結構里,key為gamesrv。當開新分服,或者切換分服狀態時,分服的服務進程向redis更新自己所在分服的信息,然後通過訂閱發佈機制,發佈gamesrv通道的消息,參數為更新的服務編號。賬號進程在玩家全局登錄時,只需要把redis里key為gamesrv的hash返回給客戶端就可以。
0
還有一種情況是,分服宕機了,這個時候需要修改分服狀態為維護中,避免玩家進入這個分服。而且redis里分服的信息也應該落地固化,這樣就算redis重啟了也不丟失。這裡可以利用redis的固化機制保存數據。增加一個管理進程,redis數據有變更就調用一次redis的bgsave命令。當然也可以搭建一個註冊進程,提供類似redis的訂閱發佈機制,以及zookeeper的連接監聽、樹形數據保存、落地固化。管理進程與所有分服的服務進程保持連接,當分服宕機時,管理進程知道對應分服的連接斷開,則修改redis里對應分服的信息狀態為維護中,這樣管理進程也起到監控作用。管理進程除了參與服務的註冊發現,還提供對服務和玩家進行管理和操作的服務,管理進程與賬號進程保持連接,賬號進程支付確認後可以通過管理進程通知具體的分服進行虛擬道具發貨。
 
0
賬號進程處理全局登錄,那就要有一個賬號的數據庫用來保存玩家的賬號信息,分服可以通過管理進程把分服創角、角色更新信息保存到賬號數據庫,以便玩家選擇分服時知道各分服角色的信息。玩家進行全局登錄是短暫的一次請求,所以用http短連接來增加訪問量。賬號進程支持集群,這樣需要有對外統一的訪問地址。管理進程也提供一些http請求給管理後台網頁對服務和玩家進行管理和操作。增加一個http網關提供統一的地址訪問,可以用業界普遍使用的nginx作為http網關。
0
對於輕中度遊戲,遊戲的通信量不會很多,沒必要每個分服都有一個長連接socket網關。假設一個分服同時連接服務器的客戶端有5k,一台機器的socket網關能支持5w個玩家。這樣可以把網關獨立出來,一台機器部署兩個socket網關,每個socket網關都可以進入任意一個分服。這樣子可以通過加減機器橫向伸縮socket網關,把網絡流量成本集中在幾台網關機器上降低成本。這樣子賬號進程就需要知道網關列表和負載情況,以通過負載均衡給玩家返回合適的連接網關。每個網關也要監聽各個分服的狀態變化,確定是否連接對應的分服的服務進程、廣播進程。因此網關需要參與服務的註冊發現。
0
這樣一個服務端的架構就基本出來了。從圖中可以看出,最大單點風險是管理進程。可以通過守護進程讓管理進程崩潰自動重啟(守護進程作為父進程創建、等待作為子進程的管理進程退出後,如果沒有維護標記則重新創建子進程),提高整個服務的健壯性。
產品上線後還要有數據統計後台,數據來源可以通過賬號進程、服務進程運行時生成日誌到日誌文件,由腳本分析日誌上報給統計數據庫。統計後台可以作為一個單獨的項目去做建表、上報、生成報表。
服務端架構就介紹到這裡,接下來聊一下網絡通信。