1. 程式人生 > >基於SWOOLE的分散式SOCKET訊息伺服器架構

基於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
    

流程圖

  • 整個流程圖如下:

    enter image description here

  • 流程圖說明:
    client1可向client2或者其他client傳送訊息,並接收其他client傳送的訊息.

    Redis中儲存client連線的資訊,給每個使用者分配唯一的key,包括連結的哪臺伺服器,轉發伺服器定時檢測訊息伺服器,如訊息伺服器掛掉,由轉發伺服器清理掉Redis已經掛掉的所有連結。

  • 完整的流程:

    1.Client1Client2傳送一條訊息

    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

enter image description here

可以在轉發伺服器上看到兩個訊息伺服器已經連線
3.開始測試,分別開啟兩個telnet,連線兩個訊息伺服器,傳送訊息測試:
登陸

enter image description here

傳送訊息測試

enter image description here

訊息成功接收。

基於強大的swoole擴充套件,讓php高效的實現這些成為可能,目前訊息伺服器到轉發伺服器是長連線,轉發伺服器到訊息伺服器是短連線,存在效能瓶頸,也浪費了連線資源。下一步改造成長連線,訊息伺服器的client使用非同步。