基於SWOOLE的分散式SOCKET訊息伺服器架構
訊息伺服器使用socket,為避免伺服器過載,單臺只允許500個socket連線,當一臺不夠的時候,擴充訊息伺服器是必然,問題來了,如何讓連結在不同訊息伺服器上的使用者可以實現訊息傳送呢?
要實現訊息互通就必須要讓這些訊息伺服器本身能互通,想了兩個方式,一種是訊息伺服器之間交叉連結,另一種是增加一個特殊的訊息伺服器,這個訊息伺服器不對外開放,只負責訊息轉發和推送。
下列測試不考慮防火牆等。僅測試可行性和效率。
測試環境
-
訊息伺服器
192.168.0.201 9501 192.168.0.202 9501
-
轉發伺服器
192.168.0.203 9501
-
公共快取
Redis 192.168.0.231 6379
-
軟體環境
centos 6.5 mini swoole php
流程圖
-
整個流程圖如下:
-
流程圖說明:
client1
可向client2
或者其他client
傳送訊息,並接收其他client
傳送的訊息.Redis
中儲存client
連線的資訊,給每個使用者分配唯一的key
,包括連結的哪臺伺服器,轉發伺服器定時檢測訊息伺服器,如訊息伺服器掛掉,由轉發伺服器清理掉Redis已經掛掉的所有連結。 -
完整的流程:
1.
Client1
給Client2
傳送一條訊息2.
Socket1
接收到訊息,根據key從Redis
取出Client2
的連線資訊,連線在本機,直接推送給Client2
3.如果連線不在本機,把訊息推送到轉發伺服器,由轉發伺服器把該訊息推送給連線所在訊息伺服器,訊息伺服器接收訊息,推送給
Client2
。4.訊息傳送結束。
編碼實現
-
Socket
在socket1上建立一個server.php,內容如下:<?php //服務端 $serv = new swoole_server("0.0.0.0", 9501); //redis $redis = new \Redis(); $redis->connect("192.168.0.231", 6379); //client $proxy = new swoole_client(SWOOLE_TCP | SWOOLE_KEEP); $proxy->connect("192.168.0.203", 9501); $serv->on('start', function($serv) { echo "Service:Start..."; }); $serv->on('connect', function ($serv, $fd) { }); $serv->on('receive', function ($serv, $fd, $from_id, $data) { global $redis; $data = (array) json_decode($data); $cmd = $data['cmd']; switch ($cmd) { case "login"://登陸 //儲存連線資訊 $save = array( 'fd' => $fd, 'socket_ip' => "192.168.0.201" ); $redis->set($data['name'], serialize($save)); break; case "chat": $recv = unserialize($redis->get($data['recv'])); if ($recv['socket_ip'] != "192.168.0.201") { //需要轉發 $data['cmd'] = 'forward'; $data['recv_ip'] = $recv['socket_ip']; $serv->task(json_encode($data)); } else { //直接傳送 $serv->send($recv['fd'], "{$data['send']}給您發了訊息:{$data['content']}"); } break; case "forward"://接收轉發訊息 $recv = unserialize($redis->get($data['recv'])); $serv->send($recv['fd'], "{$data['send']}給您發了訊息:{$data['content']}"); break; } //$serv->send($fd, 'Swoole: ' . $data); }); $serv->on('task', function ($serv, $task_id, $from_id, $data) { global $proxy; $proxy->send($data); }); $serv->on('finish', function ($serv, $task_id, $data) { }); $serv->on('close', function ($serv, $fd) { echo "Client: Close.\n"; }); $serv->set(array('task_worker_num' => 4)); $serv->start();
在socket2上只需把ip變更一下即可。192.168.0.201變更為192.168.0.202.
-
Proxy
在轉發伺服器上建立指令碼proxy.php,內容如下:$serv = new swoole_server("0.0.0.0", 9501); //服務端 $serv->on('start', function($serv) { echo "Service:Start..."; }); $serv->on('connect', function ($serv, $fd) { }); $serv->on('receive', function ($serv, $fd, $from_id, $data) { global $redis; $serv->task($data); }); $serv->on('task', function ($serv, $task_id, $from_id, $data) { $forward = (array) json_decode($data); $client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $client->connect($forward['recv_ip'], 9501); unset($forward['recv_ip']); $client->send(json_encode($forward)); $client->close(); }); $serv->on('finish', function ($serv, $task_id, $data) { }); $serv->on('close', function ($serv, $fd) { echo "Client: Close.\n"; }); $serv->set(array('task_worker_num' => 4)); $serv->start();
測試
注意開啟順序
1.開啟轉發伺服器php proxy.php
2.分別開啟socket伺服器php server.php
可以在轉發伺服器上看到兩個訊息伺服器已經連線
3.開始測試,分別開啟兩個telnet,連線兩個訊息伺服器,傳送訊息測試:
登陸
傳送訊息測試
訊息成功接收。
基於強大的swoole
擴充套件,讓php高效的實現這些成為可能,目前訊息伺服器到轉發伺服器是長連線,轉發伺服器到訊息伺服器是短連線,存在效能瓶頸,也浪費了連線資源。下一步改造成長連線,訊息伺服器的client使用非同步。