fooking文檔(不定期更新) 頂 原

簡介

fooking是一個分佈式網關,其主要目的是用於承載客戶端長連接,然後將接受的客戶端數據以fastcgi協議轉發給後端業務邏輯處理服務器,讓後端服務真正獨立的同時還無需關心擴展的問題,簡單配置即可。

fooking服務包含兩部分,一部分是gateway主要用於承載客戶端鏈接、轉發請求;另一部分是router,主要用於各gateway之間的消息傳遞、數據統計等。gateway可以開多個進程,並且可以配置多臺服務器來提升連接數。

優勢

1、非侵入式,無需安裝擴展,php版本隨你挑,後端錯誤邏輯不會影響網關正常服務

2、開發簡單,做爲後端php可以像寫web一樣,輕鬆echo即可

3、動態網關,方便線上應急擴容

4、session維持,每個客戶端分配唯一ID,無需關心fd

5、單播/組播,指定一人或者多人或者羣組發送消息

6、服務器狀態監控,監控信息細化到每個gateway的進程

7、客戶端事件通知,連接打開與關閉

8、無語言限制,後端只需遵循fastcgi協議即可

9、自定義協議,可用lua自定義協議處理

10、負載均衡,配合分配策略,可讓每臺服務器,甚至每個進程的連接數量基本相同

getting started

既然你已經點到這裏了,那麼我相信你應該瞭解fooking的基本架構了,那麼開始我們的fooking初體驗。

fooking使用c++開發,安裝g++、make等基礎開發工具,那麼接下來的事就簡單了(windows下可以使用cygwin編譯爲exe)。

git clone http://git.oschina.net/scgywx/fooking.git
make

第一招,啓動router。在啓動之前我得再BB兩句,fooking的配置文件和腳本都是使用lua,所以建議瞭解一下lua的基本知識(只是建議,不是必須)。

router的配置文件位於src/router.lua,配置文件裏面的參數都有詳細的解釋,這裏就不多說了,接下來只需要按這個姿勢執行命令就能啓動router了。

cd src
./fooking ../router.lua

第二招,啓動gateway。配置文件位於src/config.lua,配置參數相必閣下見其名便能知其意,只是其中幾個參數需要說明,1是ROUTER_HOST與ROUTER_PORT,這裏配置剛剛啓動router的host與port,主要與router連通;2是BACKEND_SERVER,列出php-fpm的host與port即可(如果是unix domain socket,要在前面加unix://前綴),3是FASTCGI_ROOT與FASTCGI_FILE,分別是php腳本路徑與腳本文件名(僅測試可指到example/chat目錄下)。啓動命令如下

./fooking ../config.lua

第三招,啓動fpm;作爲php後端服務端,php-fpm肯定少不了。

service php-fpm start

第四招,啓動nginx,其實這一步,可有可無的,主要就是用來訪問example/chat下面的靜態資源,把目錄指到example/chat目錄下即可(當然你也可以不用裝nginx,直接用瀏覽器打開example/chat/index.html也行)。

service nginx start

第五招,測試chat,打開你的瀏覽器(需要支持websocket協議的瀏覽器),直接localhost,應該就能聊天了。這個聊天的例子使用了websocket協議,協議部分的實現位於scripts/WebSocket.lua,並且這個文件也配置在config.lua的SCRIPT_FILE參數中。

協議說明

前端協議

前端協議是指gateway與客戶端的通信協議,默認是使用4字節數據長度(大端)+數據,當然你可以使用lua來自定義協議(需要配置SCRIPT_FILE),在lua自定義協議的腳本里有4個事件onConnect,onClose,onInput,onOutput分別對應連接、關閉、輸入(解包)、輸出(打包),具體更詳細的用法可以參見scripts/WebSocket.lua,裏面實現了websocket協議的打包與解包。

後端協議

後端協議是指gateway與後端服務的通信協議,使用fastcgi與php-fpm通信,這個對於phper來說應該不陌生,並且使用很簡單;由於客戶端的請求已經在前端協議中處理了,所以發給php的都是完整的數據包,所以在php裏面要獲取數據包內容就很簡單了,只需要這樣:

file_get_contents("php://input");

另外自定義參數還可以通過配置文件的FASTCGI_PARAMS來隨意添加,在php裏面只需要使用$_SERVER就能獲取參數值了,需要注意的是SESSIONID、EVENT、REMOTE_ADDR、REMOTE_PORT已經被fooking使用,請不需添加在自定義參數列表內,具體說明如下:

$sessionid = $_SERVER['SESSIONID'];//客戶端唯一ID
$event = $_SERVER['EVENT'];//事件類型(0-表示消息事件,1-表示連接事件,2-表示關閉事件)
$ip = $_SERVER['REMOTE_ADDR'];//客戶端ip
$port = $_SERVER['REMOTE_PORT'];//客戶端端口
$myparam = $_SERVER['MY_PARAM'];//自定義參數

那麼php需要返回消息給客戶端怎麼辦?首先你要告訴fooking,你要返回多少數據給客戶端,因此你需要設置header("Content-Length: 字節數"), 接下來只需要echo數據就好了,是不是很簡單?

header("Content-Length: 11");
echo "hello world";

如果你還想指定數據數據的偏移位置,那麼你可以使用Content-Offset,比如:

header("Content-Length: 5");
header("Content-Offset: 6");//也可以使用-5,效果一樣。
echo "hello world";//客戶端會收到world

什麼時候會用到Content-Offset呢?比如你有debug、warning等調試信息在消息前面,這時候就可以使用Content-Offset做負值偏移,同時還能在fooking的日誌中完整的看到debug、warning等信息。

那爲什麼要把debug、warning信息放在消息前面?因爲正文消息可能是加密或者其它二進制流信息,如果帶有\0的字符,那麼日誌就會被截斷,所以把消息放在最後確保日誌能完整輸出。

單播/組播

fooking提供的組播類似於redis的pub/sub,當你對某個組感興趣,可以加入到該組,而該組有消息則會通知你。同時每個client的信息和組信息在gateway上有存儲,並且會同步到router上,所以如果你想要給某個組或者單人發消息,只需要發到router即可,那麼對應php的後端,只需要包含api.php即可。

include 'api.php';
$router = new RouterClient('router host', 'router port');
$router->sendMsg('session id', 'hi');//指定單人發消息
$router->sendAllMsg('hi');//給所有人發消息
$router->kickUser('session id');//關閉指定連接
$router->addChannel('channel name', 'session id');//加入到指定組
$router->delChannel('channel name', 'session id');//從指定組移除
$router->publish('channel name', 'msg');//向指定組發送消息
$router->info();//獲取當前router信息

服務器分配策略

通過使用info接口拉取到各服務器監控信息,可以很輕鬆實現服務器均勻分配,info接口返回如下:

Array(
    [clients] => 8 //當前總連接數
    [channels] => 0 //當前channel總數
    [gateways] => Array(//服務器列表
         [1] => Array(
             [0] => Array(
                  [serverid] => 1,//服務器id(這個就是config.lua裏面的SERVER_ID)
                  [workerid] => 0,//進程編號
                  [clients] => 4,//當前進程負責連接數
                  [channels] => 0,//當前進程負責channel數
              )
             [1] => Array(
                  [serverid] => 1,
                  [workerid] => 1,
                  [clients] => 4,
                  [channels] => 0,
             )  
        )
    ) 
)

知道info接口數據之後,我們再根據每臺服務器ID當前連接數進行處理,分配當前連接數最少的服務器給客戶端,代碼如下:

$router = new RouterClient();
$info = $router->info();
$gateways = array();
foreach($info[‘gateway’] as $serverid => $workers){
	foreach($workers as $worker){
		$gateways[$serverid]+= $worker[‘clients’];
	}
}

asort($gateways);
$serverid = key($gateways);

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章