RedRabbit
經典網游服務器架構
該圖省略了專門用途的dbserver、guildserver等用於專門功能的server,該架構的優點有:
- l LoginGate相當於DNS,可以動態的保證GameGate之間負載均衡。
- l 由於Clientt的邏輯操作都是由GameServer處理的,而Client的消息請求都被GameGate轉發到GameServer上,所以在不同的GameGate上的client仍能出現在相同的場景里。若在不同的場景,又可以將其分布在不公的GameServer處理,從而實現了GameServer的Scalability。
- l GameServer一般是由C++與腳本結合實現的。由於數據都是在內存中處理而且大部分的IO操作(網絡、數據庫等)都被異步化,所以保證了非常高的實時性。
缺點是:
- l 各個節點之間通過socket進行異步通信,測試過程叫復雜。
- l 各個節點往往都需要交互,這時就涉及到了誰連誰的問題,理解和設計架構的網絡拓撲也變得不太容易,相應的配置也會叫繁瑣,排錯的難度也較大。
- l GameServer由於是C++主語言實現,不免會涉及到崩潰和內存泄露問題,采用C++與腳本結合很大程度上緩解了這個問題,實際上越來越多的邏輯操作都是放到腳本中實現。
- l 由於該架構必須正確的配置連接關系,否則不能正常工作,對於運維而言也並不輕松。
討論完經典網游的服務器架構,今天的主題也呼之欲出了,但在此之前,先說一下該架構的核心思想,如果你讀過《面向模式的軟件架構.第4卷,分布式計算的模式語言》你也許想到了BrokerPattern,其核心思想是通過Broker代理層,促使Server的位置對於Client保持透明,client通過Broker找到對應的Server處理請求,Serverr是如何分布的、數量多少,Client都不受影響。Broker可以存在兩種模式,一種是類似於DNS提供的LookUp服務,它只是幫助Client定位到Server的位置,Client直接連接到Serverr進行通信。LoginGate扮演的就是這種Broker。另外一種Broker直接將Client請求投遞給Serverr,GameGate就是扮演的這種Broker。總的來說BrokerPattern中,Broker具有如下功能:
- l LookUp服務,幫助Client定位Server
- l Route服務,實現Client和Server之間的消息轉發
- l 注冊服務,Server必須要注冊到Broker上這樣Broker才能提供LookUp和Route功能。
BrokerPattern示意圖:
所以今天的主題是如何利用BrokerPattern構建實時的服務器框架。
RedRabbit
目標:
- l 節點之間通信采用異步消息、回調模式
- l Server必須很容易注冊到Broker上
- l C++/EPOOL實現網絡通信,保證實時性,支持邏輯層python實現,支持熱更新
- l 該框架能夠容易的構建單個區組的構架
- l 該框架支持跨區組通信,這也是Broker模式的優勢,節點之間通信不需要知道對方的位置,只需要知道對方的名稱
這個框架的名字叫RedRabbit。
FFRPC
首先介紹RedRabbit的通信組件ffrpc,ffrpc中有如下5種角色:
- l BrokerMaster,負責管理所有的BrokerSlave,所有Slave需要注冊到BrokerMaster上,BrokerMaster同步所有信息給所有節點。
- l BrokerSlave負責轉發Client和Service之間的消息。
- l Client為調用Service接口的一方,它通過Broker於Service通信,Client不知道Service的具體位置,它只是知道當前與之通信的Service名稱。
- l Service提供給Client調用的接口,並把接口注冊到Broker上,Service若調用了其他的Service的接口,則相對於其他Service其為Client角色。
- l BrokerBridge負責橋接各個brokerMaster,每一個BrokerMaster負責一組服務,BrokerBridge使Client調用其他組接口和調用本組的接口一樣容易,因為只需要指定對方服務名稱即可。
各個角色示意圖:
使用FFRPC實現的Echo服務實例代碼:
http://www.cnblogs.com/zhiranok/archive/2013/06/06/ffrpc.html
RedRabbit中的其他組件Gate和Scene
Gate
外網接入的client有些特殊,需要一定的安全處理。Gate是專門用於接入外部Client的組件。Gate的作用有:
- l Client的第一個消息必須為驗證消息,Gate 並沒有驗證Client的能力,它調用Scene@0的接口處理
- l Scene@0通過驗證后將Client將被分配唯一的SessionId。
- l Client的所有消息都被Gate轉發到對應的Scene上,Scene可以控制Gate接口切換某個Client到其他Scene上
- l Gate提供轉發消息、多播、廣播、斷開連接等接口公scene調用。
需要特別指出的是,Gate和Scene只是RedRabbit的組件,RedRabbit通過制定不同的啟動參數來確定開啟哪些組件。示例:
- l ./app_redrabbit -gate gate@0 -broker tcp://127.0.0.1:10241 -gate_listen tcp://121.199.21.238:10242
- l -gate 表示gate的名稱,scene通過名稱調用其接口
- l -gate_listen表示gate監聽的ip、port
- l -brkoker表示作為BrokerMaster啟動,一組服務中必須有一個BrokerMaster,如果Broker和Client和Service在同一進程中,Broker專門做了優化,消息會直接從內存間實現傳遞,避免了網絡轉發的開銷。
Scene
在RedRabbit中的所有Service都是運行在Scene組件之下的。Scene提供了通用的接口,可以和Gate和其他Scene通信,並把接口導入到了python中。Scene接收的Client的請求都交由Python處理,所以可以用Scene+Python實現GameServer、DbServer等各種專用的服務器。Scene組件提供的功能有:
- l 驗證Client有效性,scene@0必須提供此接口
- l 處理Client Enter消息,Scene第一次進入該Scene,觸發此事件
- l 處理Client Offline消息,Client下線,觸發此事件
- l Scene提供轉發、多播、廣播、關閉連接等接口給python
- l Scene提供定時器接口給python
- l Scene提供異步操作Mysql、Sqlite的接口,采用異步加回調,從而避免阻塞主線程
- l Scene提供了一套消息派發框架,支持client和python通信的協議包括json、thrift、protobuf。
使用RedRabbit構建的聊天室demo示例:
http://ffown.sinaapp.com/flash/
修改名稱,點擊flash的連接按鈕,進入聊天室發消息,右側的python腳本為服務器python的實現,修改右側腳本點保存按鈕,在flash中輸入reload即可實現熱更新!!!!
該聊天室服務器啟動的參數是:
./app_redrabbit -gate gate@0 -broker tcp://127.0.0.1:10241 -gate_listen tcp://121.199.21.238:10242 -python_path ./ -scene scene@0
該示例中把gate和scene啟動到了一個服務器程序上,實際上通過調整參數,二者可以啟動到不同進程中,RedRabbit通過參數開啟組件,而組件之間是通過Broker建立聯系的。
對應的python代碼:
# coding=UTF-8 import os import time import ffext def GetNowTime(): return time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) class player_mgr_t(object): def __init__(self): self.all_players = {} def get(self, session_id_): return self.all_players.get(session_id_) def remove(self, session_id_): del self.all_players[session_id_] def add(self, session_id_, player): self.all_players[session_id_] = player def size(self): return len(self.all_players) def idlist(self): return self.all_players.keys() class player_t(object): def __init__(self, session_id_): self.session_id = session_id_; def id(): return self.session_id #這個修飾器的意思是注冊process_chat函數接收cmd=1的消息 @ffext.session_call(1) def process_chat(session_id, msg): content = msg[0] if content == 'reload': os.system('./update_code.sh') ret = ffext.reload('main')#重載此腳本 ffext.broadcast_msg_session(1, '<b><font color="#ff0000"> main.py已完成重載'\ '%s</font></b>'%(str(ret))) return print("process_chat session_id=%s content=%s"%(session_id, content)) ret = '<font color="#008000">[%s %s]:</font>%s'%(session_id, GetNowTime(), content) ffext.broadcast_msg_session(1, ret) #這個修飾器的意思是注冊下面函數處理驗證client賬號密碼, #session_key為賬號密碼組合體,client第一個包必為登陸包 @ffext.session_verify_callback def my_session_verify(session_key, online_time, ip, gate_name): return [session_key]#需要返回數組,驗證成功,第一個元素為分配的id, #第二個元素可以不設置,若設置gate會返回給client,login gate的時候 #需要第二個元素返回分配的game gate #此修飾器的作用是注冊下面函數處理用戶下線 @ffext.session_offline_callback def my_session_offline(session_id, online_time): content = '<font color="#ff0000">[%s %s] offline </font>'%(session_id, GetNowTime()) ffext.broadcast_msg_session(1, content) ffext.singleton(player_mgr_t).remove(session_id) ffext.broadcast_msg_session(1, '<font color="#ff0000">當前在線:</font>') ffext.broadcast_msg_session(1, ffext.singleton(player_mgr_t).idlist()) #此修飾器的作用是注冊下面函數處理client切換到此場景服務器 @ffext.session_enter_callback def my_session_enter(session_id, from_scene, extra_data): #單播接口 ffext.send_msg_session(session_id, 1, '<font color="#ff0000">測試單播接口!歡迎你!'\ '</font>') content = '<font color="#ff0000">[%s %s] online </font>'%(session_id, GetNowTime()) ffext.broadcast_msg_session(1, content) player = player_t(session_id) ffext.singleton(player_mgr_t).add(session_id, player) ffext.broadcast_msg_session(1, '<font color="#ff0000">當前在線:</font>') ffext.broadcast_msg_session(1, ffext.singleton(player_mgr_t).idlist()) print("loading.......")
總結:
- l Ffrpc是基於BrokerPattern思想實現的異步消息+回調通訊庫。
- l 使用python構建實時服務器完全可以做到,在一些頁游和手游項目尤其適合。確保高實時性的建議一是把數據在內存中操作,二是io操作異步化。
- l RedRabbit支持Client與Python的通信協議有Json、thrift、protobuf。我個人最喜歡thrift。
- l RedRabbit支持跨區組通信,通過BrokerBridge把GroupA和GroupB的BrokerMaster連通起來。示例:
啟動BrokerBridge:
./app_redrabbit -broker tcp://127.0.0.1:10241
啟動GroupA的BrokerMaster:
./app_redrabbit -broker tcp://127.0.0.1:10242 -bridge_broker GroupA@tcp://127.0.0.1:10241
啟動GroupB的BrokerMaster:
./app_redrabbit -broker tcp://127.0.0.1:10242 -bridge_broker GroupB@tcp://127.0.0.1:10241
在GroupA的python中就可以這樣調用GroupB的接口:
Ffext.bridge_call(‘GroupB’, cmd, msg, callback)
項目源碼:
https://github.com/fanchy/RedRabbit
TODO:
構建跨服的demo示例, 下一篇。
更多精彩文章 http://h2cloud.org