大型多人在线游戏服务器架构设计
由于大型多人在線游戲服務器理論上需要支持無限多的玩家,所以對服務器端是一個非常大的考驗。服務器必須是安全的,可維護性高的,可伸縮性高的,可負載均衡的,支持高并發請求的。面對這些需求,我們在設計服務器的時候就需要慎重考慮,特別是架構的設計,如果前期設計不好,最后面臨的很可能是重構。
一款游戲服務器的架構都是慢慢從小變大的,不可能一下子就上來一個完善的服務器構架,目前流行的說法是游戲先上線,再擴展。所以說我們在做架構的時候,一定要把底層的基礎組件做好,方便以后擴展,但是剛開始的時候留出一些接口,并不實現它,將來游戲業務的發展,再慢慢擴展。當然,如果前期設計的不好,后期業務擴展了,但架構沒辦法擴展,只能加班加點搞了。
面對龐大的數據量我們想到的唯一個解決方案就是分而治之,即采用分布式的方式去解決它。把緊湊獨立的功能單獨拿出來做。分擔到不同的物理服務器上面去運行。而且做到可以動態擴展。這就需要我們考慮好模塊的劃分,盡量要業務獨立,關聯性低。
前期,由于游戲需要盡快上線,開發周期短,我們需要把服務盡快的跑起來,這個時候的目標應該是盡快完成測試版本開發,單臺服務器支持的人數可以稍微低一些,但是當人數暴漲時,我們可以能過多開幾組服務來支持新增漲的用戶量,即可以平衡擴展就可以了。到后期我們再把具體的模塊單獨拿出來支持,比如前期邏輯服務器上包括:活動,關卡,背包,技能,好友管理等。后期我們可以把好友,背包管理或其它的單獨做一個服務進程,部署在不同的物理服務器上面。我們先按分區的服務進行設計,后面在部署的時候可以部署為世界服務器,下面是一個前期的架構圖,
本文來自游戲技術網:http://www.youxijishu.com
下面我們從每個服務器的功能說起:
1,登陸管理服務
負責用戶的登陸驗證,如果有注冊功能的話,也可以放在這里。一般手機游戲直接走sdk驗證。網頁游戲和客戶端游戲會有注冊功能,也可以叫用戶管理服務。
1.1用戶登陸驗證
負責接收客戶端的用戶登陸請求,驗證賬號的合法性,是否在黑名單(被封號的用戶),是否在白名單(一般是測試賬號,服務未開啟時也可以進入)。如果是sdk登陸,此服務向第三方服務發起回調請求。
1.2登陸安全加密
使用加密的傳輸協議,見通信協議部分。
1.3是否在白名單內
白名單是給內部測試人員使用的,在服務器未開啟的狀態下,白名單的用戶可以提前進入游戲進行游戲測試。
1.4判斷是否在黑名單
黑名單的用戶是禁止登陸的,一般這是一些被封號的用戶,拒絕登陸。
1.5登陸驗證
服務器使用私鑰解密密碼,進行驗證,如果是sdk登陸,則直接向第三方服務發起回調。
1.6登陸令牌(token)生成
當用戶登陸驗證成功之后,服務器端需要生成一個登陸令牌token,這個token具有時效性,當用戶客戶端拿到這個token之后,如果在一定時間內沒有登陸游戲成功,那么這個token將失敗,用戶需要重新申請token,token存儲在登陸服務這,向外提供用戶是否已登陸的接口,其它服務器想驗證如果是否登陸,就拿那個服務收到的token來此驗證。
1.7顯示用戶角色信息
當用戶登陸成功之后,顯示最近登陸的角色信息。
2,顯示公告
用戶登陸成功之后,請求公告服務器,獲取最新的公告,公告服務先根據token和Userid驗證用戶是否已登陸,公告有可能根據渠道的不同,顯示不同的公告。所以 公告一定是要可以根據渠道編輯的。
3,選區服務
當用戶登陸成功之后,請求服務器分區列表服務器,顯示當前所有的大區列表。
2.1 驗證用戶是否已登陸
向登陸服務器請求驗證是否已登陸。
2.1 大區列表顯示
大區列表信息中只顯示大區id和大區名稱。這樣做是為了安全考慮,不一次性把大區對應的網關ip和端口暴露出來,也可以減少網絡的傳輸量。
2.2 用戶點擊選擇某個大區,客戶端拿到大區id再向選區服務請求獲取此大區對應的網關ip地址和端口。根據負載算法計算得出。
2.3 網關的選擇
選區服務會維護一份網關的配置列表。一個大區對應一到多個網關,當配置有多個網關時,需要定時檢測各個網關是否連接正常,如果發現有網關連接不上,需要把大區對應的網關信息設置為無效,不再參與網關的分配,并發出報警。
一般對于網關的選擇,可以使用用戶id求余法加虛網關節點法。這樣在網關節點數量固定的情況下,一個用戶總是會被分配到同一個網關上面。但是如果只是使用求余法的話,可能會造成用戶分布不均衡,這里可以通過增加網關的虛擬節點(其它就是增加某個網關的權重,讓用戶多來一些到這個網關上面),這個可以參考哈稀一致性算法。包括后面說到的一個網關對應多個邏輯服務器,也可以使用同樣的方法。這部分可以抽象出來一個模塊使用。
2.4 選區服務對內要提供修改服務器狀態的接口,比如維護中...
4,登陸網關
4.1 建立連接
收到客戶端的建立連接請求之后,記錄此channel和對應的連接建立時間。并設置如果在一定時間內未收到登陸請求,則斷開連接。返回給客戶端登陸超時。
4.2 登陸請求
收到登陸請求后,移除記錄的channelid信息,向登陸服務器驗證用戶是否已登陸過,并向外廣播用戶角色登陸成功的消息。
4.3 登陸成功后,接收網關的其它的消息
4.4 客戶端消息合法性驗證
在向邏輯服務器轉發消息之前驗證消息的合法性,具體驗證方法見協議安全驗 證。
4.5 將客戶端消息轉發送到對應的邏輯服務器。
5 通信協議
5.1協議序列化和返回序列化
可以直接使用protobuf,直接對協議進行序列化和反序列化。
5.2協議組成
5.2.1 包頭構成
包總長度,加密字符串長度,加密字符串內容,userId,playerId,版本號,內包內容。
5.2.2 包體組成
請求的邏輯信息,是protobuf后對應的二進制數據。
|
包總長度 |
加密內容 |
UserId |
playerId |
請求序列id |
版本號 |
內包內容 |
|
|
Int |
64 |
Long |
Long |
Long |
int |
varchar |
|
|
4 |
64 |
8 |
8 |
8 |
4 |
變長 |
|
5.3協議內容加密
如果協議明文傳輸的話,被篡改的風險就非常大,所以我們要對傳輸協議進行加密傳輸,由于協議內容大小不固定,為了保證效率,采用對稱加密算法,首先客戶端使用AES的公鑰對消息內容加密(上表中userid之后的信息),客戶端把加密后的報文發送到服務器端。AES的公鑰在用戶第一次連接時獲取。
5.4協議完整性驗證
盡管我們對消息做了加密,但也不是萬無一失的,為了進一步確保消息沒有被篡改,我們需要對消息的完整性進行檢測,使用數字摘要的方式,首先客戶端對userid及之后的協議信息進行AES加密,加密之后取它的md5值,md5值用于驗證數據的完整性。這個md5值會被傳送到服務器,如果協議信息被修改了,那個md5就會不同。
5.5保證md5數字摘要的值的安全
為了防止非法用戶修改協議內容后,模擬客戶端操作重新生成新的數字摘要信息,我們對生成的數字摘要信息進行二次加密,這次使用RSA的公鑰對md5的值進行加密,將加密的內容和其它信息一起發送到服務器。服務器根據ip向登陸服務器拿到AES的公鑰和RSA的私鑰,先用RSA 私鑰取出客戶端加密的md5值,服務器端計算userid后面的數據的md5值,如果兩個md5值一樣,說明安全的。如果不一樣,說明用戶是非法的,加入黑名單。因為RAS使用公鑰加密,必須使用對應的私鑰才能解密,而且不同的公鑰對應的私鑰不同,這樣就算非法用戶重新生成了數字摘要,在服務器端也是驗證不通過的。
5.6取出明文信息
當服務器收到報文后,對報文進行數子摘要驗證通過之后,服務器端使用用戶自己對應的AES的公鑰,解密數據,獲得明文數據。為了保證安全,每個用戶的AES公鑰可能不一樣。
6 發布訂閱服務
發布訂閱是一種分布式的解耦方式,它使用模塊更加獨立,模塊間的數據交互更加方便,發布訂閱模式是一種一對多的關系,發布方不關心誰訂閱了它,只要想獲得它發布的消息的服務,都可以去訂閱它。發布方式是異步的,它增強了系統的處理性能,增加了系統的吞吐量。目前的大多數消息隊列都支持發布訂閱模式,比如rabbitmq,activemq,kafka等消息隊列。發布訂閱服務可以單獨部署,增強了系統的擴展性和穩定性。
7,RPC調用
在服務器內部不同的服務有時候需要信息交互。為了方便服務之間的調用,我們引入了RPC的概念。客戶端調用一個api之后,底層會把此調用發送到遠程的服務上處理,遠程服務處理完之后再返回結果。rpc的作用就是封裝底層協議的序列化和反序列化,它讓用戶感覺不到調用被發送到了遠程服務,而感覺還是在本地一樣
7.1 同步rpc
當調用一個同步的rpc之后,結果并不是立刻返回,而是在等待rpc服務器端的返回。同步rpc可以直接使用帶同步的socket實現。或者http請求。另一種方式是調用rpc方法之后,在本地自旋,直到服務端返回。
7.2 異步rpc
異步rpc調用之后,結果是立刻返回的,它的處理方式是把業務放在回調方法里面,而不是一直占用線程在那里等待數據的返回,這樣就可以記空閑的線程去處理另外的消息,當消息從服務器端返回后,會去調用那個回調方法。
8,合服要提前設計好
現在大多數的游戲都是分區分服的,經過一段時間的運營之后,有些老的大區可能在線人數非常的少了,為了節約成本,首先會在一臺物理機器上運行多個大區對應的進程,再過一段時間,可能需要把不同區的數據合并起來到一個數據庫中。而對用戶來說是感覺不到變化的。
為什么說合服要提交設計好呢?因為如果設計不好,后期在合服的時候會遇到很多問題, 比如用戶唯一主鍵問題,表與表主鍵關聯重復問題,那么在合服存在的情況下,如何保證用戶的唯一性呢,也就是我一個用戶在兩個大區都建立了賬號,這個時候userid是一樣的,還有一個角色id,如果角色id不是全局唯一的,也可能重復。而角色id如果參與了表外鍵設計,一重復數據就亂了。
首先,要保證用戶的唯一性。而且各個表的外鍵引用也必須是唯一的,即合服之后不會再發生改變。那么有幾個鍵需要全局唯一,userid(用戶id),roleId(角色id),為了區分用戶原來所在的區,需要記錄角色所在的大區id,所以一個userid和一個大區id來確定一個唯一的角色id,而角色的其它信息使用角色id做外鍵引用。這樣合服就可以直接把兩個庫的數據合并到一起了。
這個只是用角色數據舉個例子,在數據庫中,凡是獨立存在的,最好都使用全局唯一id,比如公會,每個服都會有公會,但每個服的公會id不能都是從一開始,即不能使用數據庫自增的方式
總結
以上是生活随笔為你收集整理的大型多人在线游戏服务器架构设计的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python入门(2)全局变量
- 下一篇: 消毒柜导购樱花消毒柜怎么样