1. 程式人生 > >伺服器架構設計,常見問題分析

伺服器架構設計,常見問題分析

MMORPG伺服器架構

轉自:http://www.blogjava.net/landon/archive/2012/07/14/383092.html

分析總結的很好,分享下。

一.摘要


1.網路遊戲 MMORPG 整體伺服器框架,包括早期,中期,當前的一些主流架構
2.網路遊戲網路層,包括網路 協議 , IO 模型,網路框架,訊息編碼等。
3.網路遊戲的 場景 管理, AI 指令碼 的應用等。
4. 開源 的網路伺服器引擎
5.參考書籍,部落格

二.關鍵詞

網路協議 網路IO 訊息 廣播 同步 CS TCP/UDP IP 叢集 負載均衡 分散式 
閘道器伺服器 GateServer 心跳 多執行緒/執行緒池 開源網路通訊框架/模型

阻塞/非阻塞/同步/非同步    Proactor/Reactor/Actor Select/Poll/Epoll/Iocp/Kqueue 
遊戲開發中的設計模式/資料結構

短連線和長連線 遊戲安全 快取 訊息編碼協議 指令碼語言 
Socket Nagle/粘包/截斷/TCP_NODELAY AI/場景 分線/分地圖 開源MMORPG伺服器


三.正文 框架結構

1.    早期的MMORPG伺服器結構

Client<->GameServer<->DB    所有業務,資料集中處理


優點 :簡單,快速開發
缺點:
     1.所有業務放在一起,系統負擔大大增加.一個bug可能導致整個伺服器崩潰,造成所有玩家掉線甚至丟失等嚴重後果。
     2.開服一剎那,所有玩家全部堆積在同一個新手村.->>>>卡,客戶端卡(同屏人數過多渲染/廣播風暴) 伺服器卡(處理大量同場景訊息/廣播風暴) 2.    中期-使用者分離叢集式


                GameServe1
Client            |                    DB
                GameServer2

玩家不斷增多-> 分線 ->程式自動或玩家手動選擇進入
缺點 :運營到後期,隨著每條線玩家的減少, 互動大大減少。

3.    中後期 資料分離叢集式 按地圖劃分伺服器,當前主流
     新手村問題 :《 天龍八部 》提出了較好的解決方案,建立多個平行的新手村地圖,一主多副,開服時儘可能多的同時容納新使用者的湧入,高等級玩家從其它地圖回新手村只能到達主新手村。

4.    當前主流的網路遊戲架構


         注:在GateServer和CenterServer之間是有一條TCP連線的。而GameServer和LogServer之間的連線可以是UDP連線。這是有一個大概的圖,很多地方需要細化。
GateServer:閘道器伺服器,AgentServer、ProxyServer

  優點
    (1)作為網路通訊的中轉站,負責維護將內網和外網隔離開,使外部無法直接訪問內部伺服器,保障內網伺服器的安全,一定程度上較少外掛的攻擊。
    (2)閘道器伺服器負責解析資料包、加解密、超時處理和一定邏輯處理,這樣可以提前過濾掉錯誤包和非法資料包。
    (3)客戶端程式只需建立與閘道器伺服器的連線即可進入遊戲,無需與其它遊戲伺服器同時建立多條連線,節省了客戶端和伺服器程式的網路資源開銷。
    (4)在玩家跳伺服器時,不需要斷開與閘道器伺服器的連線,玩家資料在不同遊戲伺服器間的切換是內網切換,切換工作瞬問完成,玩家幾乎察覺不到,這保證了遊戲的流暢性和良好的使用者體驗。

    缺點
1.閘道器伺服器成為高負載情況下的通訊瓶頸問題
2由於閘道器的單節點故障導致整組伺服器無法對外提供服務的問題

    解決: 多閘道器 技術。顧名思義,“多閘道器” 就是同時存在多個閘道器伺服器,比如一組伺服器可以配置三臺GameGme。當負載較大時,可以通過增加閘道器伺服器來增加閘道器的總體通訊流量,當一臺閘道器伺服器宕機時,它只會影響連線到本伺服器的客戶端,其它客戶端不會受到任何影響。

DCServer :資料中心伺服器。主要的功能是快取玩家角色資料,保證角色資料能快速的讀取和儲存
CenterServer :全域性伺服器/中心伺服器,也叫 WorldServer . 主要負責維持GameServer之間資料的轉發和資料廣播。另外一些遊戲系統也可能會放到Center上處理,比如好友系統,公會系統。

     改進 :將閘道器伺服器細化為LogingateServer和多個GameGateServer.

5.    按 業務分離式 叢集
由於網路遊戲存在很多的業務,如聊天,戰鬥,行走,NPC等,可以將某些業務分到單獨的伺服器上。這樣每個伺服器的程式則會精簡很多。而且一些大流量業務的分離,可以有效的提高遊戲伺服器人數上限。

 
優點:

      1.業務的分離使得每種伺服器的程式變的簡單,這樣可以降低出錯的機率。即使出錯,也不至於影響到每一個整個遊戲的進行,而且通過快速啟動另一臺備用伺服器替換出錯的伺服器。
     2.業務的分離使得流量得到了分散,進而相應速度回得到提升 。
     3.大部分業務都分離了成了單獨的伺服器,所以可以動態的新增,從而提高人數上限。

改進 :甚至可以將登陸伺服器細化拆分建角色,選擇角色伺服器
6.     一種簡單實用的網路遊戲伺服器架構

下圖中每個方框表示一個獨立的程序APP元件,每個服務程序如果發生宕機會影響部分使用者,整體服務但不會全部中斷。在宕機程序重啟後,又可以併入整體,全部服務得以繼續。



gls :game login server,遊戲登入伺服器,某種程式上,其不是核心元件,gls呼叫外部的介面,進行基本的使用者名稱密碼認證。此外需要實現很多附屬的功能:登入排隊(對開服非常有幫助),GM超級登入通道(GM可以不排隊進入遊戲),封測期間啟用使用者控制,限制使用者登入,控制客戶端版本等。
db :實質上是後臺sql的 大記憶體緩衝 ,隔離了資料庫操作,比較記憶體中的資料,只把改變的資料定時批量寫入sql。系統的演算法,開發穩定性都要求非常高。
center :所有元件都要在這裡註冊,線上玩家的session狀態都在這裡集中存放,和各元件有心跳連線。所有對外的介面也全部通過這裡。
角色入口:玩家登入遊戲後的選擇角色
gs :game server,最核心元件,同一地圖,所有遊戲邏輯相關的功能,都在這裡完成。
gate :建立和使用者的常連結,主要作sockt轉發,遮蔽惡意包,對gs進行保護。協議加密解密功能,一個gate共享多個gs,降低跳轉地圖連線不上的風險。
IM,關係,寄售:表示其它元件,負責對應的跨地圖發生全域性的遊戲邏輯。

7. 另一個架構圖


     1-   這是一條 WebService 的管道,在使用者啟用該區帳號,或者修改帳號密碼的時候,通過這條通道來插入和更新使用者的帳號資訊。
     2-   這也是一條 WebService 管道,用來獲取和控制使用者該該組內的角色資訊,以及進行付費商城代幣之類的更新操作。
     3-   這是一條 本地的TCP/IP 連線,這條連線主要用來進行伺服器組在登陸伺服器的註冊,以及登陸伺服器驗證帳戶後,向用戶伺服器註冊帳戶登陸資訊,以及進行對已經登陸的帳戶角色資訊進行操作(比如踢掉當前登陸的角色),還有伺服器組的資訊更新(當前線上玩家數量等)。
     4-   這也是一條 本地TCP/IP 連線,這條連線用來對連線到GameServer的客戶端進行驗證,以及獲取角色資料資訊,還有傳回GameServer上角色的資料資訊改變。
     5-   這條連線也是一條 本地的TCP/IP 連線,它用來進行公共資訊伺服器和數個遊戲伺服器間的互動,用來交換一些遊戲世界級的資訊(比如公會資訊,跨服組隊資訊,跨服聊天頻道等)。
     6-   這裡的兩條連線,想表達的意思是,UserServer和GameServer的Agent是可以互換使用的,也就是玩家進入組內之後,就不需要再切換Agent。如果不怕亂套,也可以把登陸伺服器的Agent也算上,這樣使用者整個過程裡就不需要再更換Agent,減少重複連線的次數,也提高了穩定性。(畢竟連線次數少了,也降低了連不上伺服器的出現機率)
在這個架構裡面,GameServer實際上是一個遊戲邏輯的綜合體,裡面可以再去擴充套件成幾個不同的邏輯伺服器,通過PublicServer進行公共資料交換。
     UserServer 實際上扮演了一個ServerGroup的領頭羊的角色,它負責向LoginServer註冊和更新伺服器組的資訊(名字,當前人數),並且對Agent進行排程,對選擇了該組的玩家提供一個使用者量最少的Agent。同時,它也兼了一個角色管理伺服器的功能,傳送給客戶端當前的角色列表,角色的建立,刪除,選擇等管理操作,都是在這裡進行的。而且,它還是一個使用者資訊的驗證伺服器,GameServer需要通過它來進行客戶端的合法性驗證,以及獲取玩家選擇的角色資料資訊。
採用這種架構的遊戲,通常有以下表現。
     1- 使用者必須啟用一個大區,才能在大區內登陸自己的帳號。
     2- 使用者啟動客戶端的時候,彈出一個登陸器,選擇大區。
     3- 使用者啟動真正的客戶端的時候,一開始就是輸入帳號密碼。
     4- 帳號驗證完成之後,進行區內的伺服器選擇。
     5- 伺服器選擇完成之後,進入角色管理。同時,角色在不同的伺服器裡不能共享。 四.正文網路通訊

1. 網路協議
 根據遊戲型別    實時性要求/是否允許丟包 來決定  TCP/UDP 協議

a. TCP:面向連線,可靠,保證順序,慢,有延遲
     TCP每次傳送一個數據包後都要等待接收方傳送一個應答資訊,這樣TCP才可以確認資料包通過因特網完整地送到了接收方。如果在一段時間內TCP沒有收到接收方的應答,他就會停止傳送新的資料包,轉而去重新發送沒有收到應答2的資料包,並且持續這種傳送狀態知道收到接收方的應答。所以這會造成網路資料傳輸的延遲,若網路情況不好,傳送方會等待相當長一段時間
        UDP:無連線,不可靠,不保證順序,快

b. 長連線/短連線
長連線,指在一個TCP連線上可以連續傳送多個數據包,在TCP連線保持期間,如果沒有資料包傳送,需要雙方發檢測包以維持此連線,一般需要自己做線上維
    連線→資料傳輸→保持連線(心跳)→資料傳輸→保持連線(心跳)→……→關閉連線
短連線是指通訊雙方有資料互動時,就建立一個TCP連線,資料傳送完成後,則斷開此TCP連線,如Http
    連線→資料傳輸→關閉連線
2. IO模型

        Unix5中io模型
1.    阻塞IO (Blocking I/O Model)
2.    非阻塞IO (Nonblocking I/O Model)
3.    IO複用 (I/O Multiplexing Model)
4.    訊號驅動IO (Signal-Driven I/O Model)
5.    非同步IO (Asynchronous I/O Model)

IO分兩個階段:
1.通知核心準備資料。2.資料從核心緩衝區拷貝到應用緩衝區

根據這2點IO型別可以分成:
     1.阻塞IO,在兩個階段上面都是阻塞的。
     2.非阻塞IO,在第1階段,程式不斷的輪詢直到資料準備好,第2階段還是阻塞的
     3.IO複用,在第1階段,當一個或者多個IO準備就緒時,通知程式,第2階段還是阻塞的,在第1階段還是輪詢實現的,只是所有的IO都集中在一個地方,這個地方進行輪詢
     4.訊號IO,當資料準備完畢的時候,訊號通知程式資料準備完畢,第2階段阻塞
     5.非同步IO,1,2都不阻塞







    同時阻塞多個I/O操作。而且可以同時對多個讀操作,多個寫操作的I/O函式進行檢測,直到有資料可讀或可寫時,才真正呼叫I/O操作函式
J ava#Selector

   

允許套介面進行訊號驅動I/O,並安裝一個訊號處理函式,程序繼續執行並不阻塞。當資料準備好時,程序會收到一個SIGIO訊號,可以在訊號處理函式中呼叫I/O操作函式處理資料.


J ava#NIO2
發出系統呼叫後,直接返回。通知IO操作完成。
前四種同步IO,最後一種非同步IO.二者區別:第二個階段必須要求程序主動呼叫recvfrom.而非同步io則將io操作全部交給核心完成,完成後發訊號通知。此期間,使用者不需要去檢查IO操作的狀態,也不需要主動的去拷貝資料。

3. 執行緒阻塞的原因:

     1.Thread.sleep(),執行緒放棄CPU,睡眠N秒,然後恢復執行
     2.執行緒要執行一段同步程式碼,由於無法獲得相關的鎖,阻塞。獲得同步鎖後,才可以恢復執行。
     .執行緒執行了一個物件的wait方法,進入阻塞狀態,只有等到其他執行緒執行了該物件的notify、nnotifyAll,才能將其喚醒。
     4.IO操作,等待相關資源
阻塞執行緒的共同特點是:放棄CPU,停止執行,只有等到導致阻塞的原因消除,才能恢復執行 。或者被其他執行緒中斷,該執行緒會退出阻塞狀態,並丟擲InterruptedException.

4. 阻塞/非阻塞/同步/非同步
同步/非同步關注的是訊息如何通知的機制。而阻塞和非阻塞關注的是處理訊息。是兩組完全不同的概念。

5.幾個常用概念
Select Poll
Epoll(Linux) Kqueue(FreeBSD)   
IOCP    windows  
Reactor
Dispatcher(分發器),Notifer(通知器), 事件到來時,使用Dispatcher(分發器)對Handler進行分派,這個Dispatcher要對所有註冊的Handler進行維護。同時,有一個Demultiplexer(分揀器)對多路的同步事件進行分揀。    

Proactor
Proactor和Reactor都是併發程式設計中的設計模式.用於派發/分離IO操作事件的 。這裡所謂的IO事件也就是諸如read/write的IO操作。"派發/分離"就是將單獨的IO事件通知到上層模組。兩個模式不同的地方在於,Proactor用於非同步IO,而Reactor用於同步IO。

兩個模式的相同點,都是對某個IO事件的事件通知(即告訴某個模組,這個IO操作可以進行或已經完成)。在結構上,兩者也有相同點:demultiplexor負責提交IO操作(非同步)、查詢裝置是否可操作(同步),然後當條件滿足時,就回調handler。
不同點在於,非同步情況下(Proactor),當回撥handler時,表示IO操作已經完成;同步情況下(Reactor),回撥handler時,表示IO裝置可以進行某個操作(can read or can write),handler這個時候開始提交操作。

6. 網路通訊框架
TCP Server框架:
Apache  MINA (Multipurpose Infrastructure for Network Applications)2.0.4
Netty  3.5.0Final
Grizzly  2.2
Quickserver 是一個免費的開源Java庫,用於快速建立健壯的多執行緒、多客戶端TCP伺服器應用程式。使用QuickServer,使用者可以只集中處理應用程式的邏輯/協議
Cindy  強壯,可擴充套件,高效的非同步I/O框架
xSocket 一個輕量級的基於nio的伺服器框架用於開發高效能、可擴充套件、多執行緒的伺服器。該框架封裝了執行緒處理、非同步讀/寫等方面
ACE  6.1.0 C++ADAPTIVE CommunicationEnvironment,
SmaxFoxServer  2.X 專門為Adobe Flash設計的跨平臺socket伺服器

7. 訊息編碼協議
AMF/JSON/XML/自定義/ProtocolBuffer

無論是做何種網路應用,必須要解決的問題之一就是 應用層從位元組流中拆分出訊息 的問題,也就是對於 TCP 這種位元組流協議,接收方應用層能夠從位元組流中識別傳送方傳輸的訊息.
1.使用特殊字元或者字串作為訊息的邊界,應用層解析收到的位元組流時,遇見此字元或者字串則認為收到一個完整的訊息 
2.為每個訊息定義一個長度,應用層收到指定長度的位元組流則認為收到了一個完整的訊息
訊息分隔標識(separator)、訊息頭(header)、訊息體(body)
 len | message_id | data 

 |separator |     header   | body |
  | len       | message_id | data

8.  粘包:
TCP粘包是指傳送方傳送的若干包資料到接收方接收時粘成一包,從接收緩衝區看,後一包資料的頭緊接著前一包資料的尾。 
     1.傳送方引起的粘包是由TCP協議本身造成的,TCP為提高傳輸效率,傳送方往往要收集到足夠多的資料後才傳送一包資料。若連續傳送幾次的資料都很少,通常TCP會根據優化演算法把這些資料合成一包後一次傳送出去,這樣接收方就收到了粘包資料。
     2.接收方引起的粘包是由於接收方使用者程序不及時接收資料,從而導致粘包現象。這是因為接收方先把收到的資料放在系統接收緩衝區,使用者程序從該緩衝區取資料,若下一包資料到達時前一包資料尚未被使用者程序取走,則下一包資料放到系統接收緩衝區時就接到前一包資料之後,而使用者程序根據預先設定的緩衝區大小從系統接收緩衝區取資料,這樣就一次取到了多包資料

解決措施:
     1.對於傳送方引起的粘包現象,使用者可通過程式設計設定來避免,TCP提供了強制資料立即傳送的操作指令push,TCP軟體接收到該操作指令後,就立即將本段資料傳送出去,而不必等待發送緩衝區滿;
TCP-NO-DELAY-關閉了優化演算法,不推薦
     2.對於接收方引起的粘包,則可通過優化程式設計、精簡接收程序工作量、提高接收程序優先順序等措施,使其及時接收資料,從而儘量避免出現粘包現象-當傳送頻率高時依然可能出現粘包
     3.接收方控制,將一包資料按結構欄位,人為控制分多次接收,然後合併,通過這種手段來避免粘包。-效率低
     4.接收方建立一預處理執行緒,對接收到的資料包進行預處理,將粘連的包分開

分包演算法思路:
基本思路是首先將待處理的接收資料(長度設為m)強行轉換成預定的結構資料形式,並從中取出資料結構長度欄位,即n,而後根據n計算得到第一包資料長度
1) 若n<m,則表明資料流包含多包資料,從其頭部擷取n個位元組存入臨時緩衝區,剩餘部分資料一次繼續迴圈處理,直至結束。
2) 若n=m,則表明資料流內容恰好是一完整結構資料,直接將其存入臨時緩衝區即可。
3) 若n>m,則表明資料流內容尚不夠構成一個完整結構資料,需留待與下一包資料合併後再行處理。

.正文之場景管理、ai 、指令碼

  AOI : (Area Of Interest),廣義上,AOI系統支援任何遊戲世界中的物體個體對一定半徑範圍內發生的事件進行處理;但MMOPRG上絕大多數需求只是對半徑範圍內發生的物體離開/進入事件進行處理。當你進入一個遊戲場景時,如果你能看到其他玩家,那背後AOI系統就正在運作.

     1. 很容易想象,AOI的需求最簡單的做法是全世界玩家資訊全部同步給客戶端。這個方案是O(n^2)的複雜度,對伺服器來說是不能承受之重。但如果是超小地圖十人以下的特殊需求倒可能是個簡潔的方案。
     2. 比較流行的方案是網格法,簡單,高效:將地圖按設定的格子大小劃分為網格,設玩家移動到某座標,我們很容易地將玩家歸入該座標所屬的網格G的玩家鏈中,而這個玩家的可見集可以簡單地將以網格G為中心的九宮格中的玩家鏈聚合而得到。而要獲得兩次移動間的可見集差異,也非難事.

轉自雲風Blog:
所謂 AOI ( Area Of Interest ) ,大致有兩個用途。
     一則是解決  NPC 的 AI 事件觸發問題 。遊戲場景中有眾多的 NPC ,比 PC 大致要多一個數量級。NPC 的 AI 觸發條件往往是和其它 NPC 或 PC 距離接近。如果沒有 AOI 模組,每個 NPC 都需要遍歷場景中其它物件,判斷與之距離。這個檢索量是非常巨大的(複雜度 O(N*N) )。一般我們會設計一個 AOI 模組,統一處理,並優化比較次數,當兩個物件距離接近時,以訊息的形式通知它們。
     二則用於 減少向 PC 傳送的同步訊息數量 。把離 PC 較遠的物體狀態變化的訊息過濾掉。PC 身上可以帶一個附近物件列表,由 AOI 訊息來增減這個列表的內容。
在伺服器上,我們一般推薦把 AOI 模組做成一個獨立服務 。場景模組通知它改變物件的位置資訊。AOI 服務則傳送 AOI 訊息給場景
AOI 的傳統實現方法大致有三種:

第一,也是最苯的方案。直接定期比較所有物件間的關係,發現能夠觸發 AOI 事件就傳送訊息。這種方案實現起來相當簡潔,幾乎不可能有 bug ,可以用來驗證服務協議的正確性。在場景中物件不對的情況下其實也是不錯的一個方案。如果我們獨立出來的話,利用一個單獨的核,其實可以定期處理相當大的物件數量。

第二,空間切割監視的方法。把場景劃分為等大的格子,在每個格子裡樹立燈塔。在物件進入或退出格子時,維護每個燈塔上的物件列表。對於每個燈塔還是 O(N * N) 的複雜度,但由於把物件資料量大量降了下來,所以效能要好的多,實現也很容易。缺點是,儲存空間不僅和物件數量有關,還和場景大小有關。更浪費記憶體。且當場景規模大過物件數量規模時,效能還會下降。因為要遍歷整個場景。對大地圖不太合適。這裡還有一些優化技巧,比如可以把格子劃分為六邊形 的。

第三,使用十字連結串列 (3d 空間則再增加一個連結串列維度) 儲存一系列線段,當線段移動時觸發 AOI 事件。演算法不展開解釋,這個用的很多應該搜的到。優點是可以混用於不同半徑的 AOI 區域。

2.AI
    1.怪物AI
    2.NPC AI
    3.世界環境AI
實現方法: 狀態機
 其他:
 尋路:A*
 神經網路
 遺傳演算法

3. 指令碼語言的選擇:
Lua/Python/Erlang
Groovy/JRuby/Scala/Fantom/JPython-五大基於JVM的語言
  作用:可應用於部分應用層邏輯經常發生變化的系統。如任務系統。以在不需要重新編譯整個工程的情況下調整、 測試和修改遊戲執行的機制和特性 .正文之開源網路遊戲伺服器

魔獸世界模擬器
mangosTrinity  TrinityCore2

天堂2模擬器
L2J

永恆之塔模擬器

Arianne

七.正文之參考書籍,部落格
1. 雲風Blog  ttp://codingnow.com/
2.書籍<大型多人線上遊戲開發><網路遊戲伺服器程式設計><UNIX網路程式設計>

注:有部分內容來自網路,謝謝你們!