• PHP laravel+thrift+swoole打造微服务框架


    Laravel作为最受欢迎的php web框架一直广受广大互联网公司的喜爱。

    笔者也参与过一些由laravel开发的项目。虽然laravel的性能广受诟病但是业界也有一些比较好的解决方案,比如堆机器,比如使用swoole进行加速。

    一个项目立项到开发上线,随着时间和需求的不断激增,会越来越复杂,变成一个大项目,如果前期项目架构没设计的不好,代码会越来越臃肿,难以维护,后期的每次产品迭代上线都会牵一发而动全身。项目微服务化,松耦合模块间的关系,是一个很好的选择,随然增加了维护成本,但是还是很值得的。

    那么有什么办法使一个laravel项目改造成微服务呢?

     

    最近研究thrift的时候发现thrift对php之城非常好,那么可不可以使用使用thrift作为rpc框架,使用swoole来实现异步TCP服务,打造一个微服务框架呢。

     

    心动不如行动我们开始尝试一下吧。首先我们创建一个laravel的项目,笔者使用的laravel官方提供的homestead的环境。

    laravel new laravel-thrift-app 

    安装laravel-s

    composer require "hhxsv5/laravel-s:~3.5.0" -vvv 

    laravel-s是一个由swoole写的laravel扩展,赋予laravel更好的性能,具体使用方法参看官方文档。

    在项目的根目录下新建一个thrift的目录,然后在该子目录下创建 Thrift IDL 文件 user.thrift,用于定义和用户相关的服务接口。

    1 namespace php App.Thrift.User  
    2 // 定义用户接口 
    3 service User {     
    4 string getInfo(1:i32 id)
    5  }

    这里我们定义了一个接口,接着在项目根目录下运行如下命令,根据上述 IDL 文件生成相关的服务代码:

    thrift -r --gen php:server -out ./ thrift/user.thrift 

    查看文件这时候我们会发现在AppThriftUser`目录下生成对应的服务代码。

     

    通过 Composer 安装 Thrift PHP 依赖包:

    composer require apache/thrift 

    编写服务代码,在 app目录下新建一个 Services/Server 子目录,然后在该目录下创建服务接口类 UserService,该类实现自 `AppThriftUserUserIf` 接口:

     1 <?php
     2 namespace AppServicesServer;
     3 
     4 
     5 use AppThriftUserUserIf;
     6 
     7 class UserService implements UserIf
     8 {
     9     public function getInfo($id)
    10     {
    11         return "chenSi".$id;
    12     }
    13 }

    在 app 目录下新建一个 Sockets目录用于存放 Swoole 相关代码,首先我们创建一个 ServerTransport.php用来存放服务端代理类,并编写代码如下:

     1 <?php
     2 namespace AppSockets;
     3 
     4 
     5 use ThriftServerTServerTransport;
     6 
     7 class ServerTransport extends TServerTransport
     8 {
     9     /**
    10      * @var array 服务器选项
    11      */
    12     public $options = [
    13         'dispatch_mode'         => 1, //1: 轮循, 3: 争抢
    14         'open_length_check'     => true, //打开包长检测
    15         'package_max_length'    => 8192000, //最大的请求包长度,8M
    16         'package_length_type'   => 'N', //长度的类型,参见PHP的pack函数
    17         'package_length_offset' => 0,   //第N个字节是包长度的值
    18         'package_body_offset'   => 4,   //从第几个字节计算长度
    19     ];
    20 
    21     /**
    22      * @var SwooleServer
    23      */
    24     public $server;
    25     protected $host;
    26     protected $port;
    27     protected $sockType;
    28 
    29 
    30     public function __construct($swoole, $host, $port = 9999, $sockType = SWOOLE_SOCK_TCP, $options = [])
    31     {
    32         $this->server = $swoole;
    33         $this->host   = $host;
    34         $this->port   = $port;
    35         $this->sockType = $sockType;
    36         $this->options = array_merge($this->options,$options);
    37 
    38     }
    39 
    40 
    41     public function listen()
    42     {
    43         $this->server =$this->server->addlistener($this->host,$this->port,$this->sockType);
    44         $this->server->set($this->options);
    45         return null;
    46     }
    47 
    48 
    49     public function close()
    50     {
    51         //$this->server->shutdown();
    52         return null;
    53     }
    54 
    55 
    56     protected function acceptImpl()
    57     {
    58         return null;
    59     }
    60 }

    我们在代理类的构造函数中初始化 Swoole TCP 服务器参数,由于我们使用的是laravel-s然后在该类中定义 listen 方法启动这个swoole增加监听的端口并监听客户端请求。

    我们在 app/Sockets目录下创建 Transport.php文件用于存放基于 Swoole 的传输层实现代码:

      1 <?php
      2 /**
      3  * Created by PhpStorm.
      4  * User: 74100
      5  * Date: 2019/10/21
      6  * Time: 2:22
      7  */
      8 namespace AppSockets;
      9 
     10 use SwooleServer as SwooleServer;
     11 use ThriftExceptionTTransportException;
     12 use ThriftTransportTTransport;
     13 
     14 class Transport extends TTransport
     15 {
     16     /**
     17      * @var swoole服务器实例
     18      */
     19     protected $server;
     20     /**
     21      * @var int 客户端连接描述符
     22      */
     23     protected $fd = -1;
     24     /**
     25      * @var string 数据
     26      */
     27     protected $data = '';
     28     /**
     29      * @var int 数据读取指针
     30      */
     31     protected $offset = 0;
     32 
     33     /**
     34      * SwooleTransport constructor.
     35      * @param SwooleServer $server
     36      * @param int $fd
     37      * @param string $data
     38      */
     39     public function __construct(SwooleServer $server, $fd, $data)
     40     {
     41         $this->server = $server;
     42         $this->fd = $fd;
     43         $this->data = $data;
     44     }
     45 
     46     /**
     47      * Whether this transport is open.
     48      *
     49      * @return boolean true if open
     50      */
     51     public function isOpen()
     52     {
     53         return $this->fd > -1;
     54     }
     55 
     56     /**
     57      * Open the transport for reading/writing
     58      *
     59      * @throws TTransportException if cannot open
     60      */
     61     public function open()
     62     {
     63         if ($this->isOpen()) {
     64             throw new TTransportException('Swoole Transport already connected.', TTransportException::ALREADY_OPEN);
     65         }
     66     }
     67 
     68     /**
     69      * Close the transport.
     70      * @throws TTransportException
     71      */
     72     public function close()
     73     {
     74         if (!$this->isOpen()) {
     75             throw new TTransportException('Swoole Transport not open.', TTransportException::NOT_OPEN);
     76         }
     77         $this->server->close($this->fd, true);
     78         $this->fd = -1;
     79     }
     80 
     81     /**
     82      * Read some data into the array.
     83      *
     84      * @param int $len How much to read
     85      * @return string The data that has been read
     86      * @throws TTransportException if cannot read any more data
     87      */
     88     public function read($len)
     89     {
     90         if (strlen($this->data) - $this->offset < $len) {
     91             throw new TTransportException('Swoole Transport[' . strlen($this->data) . '] read ' . $len . ' bytes failed.');
     92         }
     93         $data = substr($this->data, $this->offset, $len);
     94         $this->offset += $len;
     95         return $data;
     96     }
     97 
     98     /**
     99      * Writes the given data out.
    100      *
    101      * @param string $buf The data to write
    102      * @throws TTransportException if writing fails
    103      */
    104     public function write($buf)
    105     {
    106         if (!$this->isOpen()) {
    107             throw new TTransportException('Swoole Transport not open.', TTransportException::NOT_OPEN);
    108         }
    109         $this->server->send($this->fd, $buf);
    110     }
    111 }

    Transport类主要用于从传输层写入或读取数据,最后我们创建 Server.php 文件,用于存放基于 Swoole 的 RPC 服务器类:

     1 <?php
     2 /**
     3  * Created by PhpStorm.
     4  * User: 74100
     5  * Date: 2019/10/21
     6  * Time: 2:24
     7  */
     8 namespace AppSockets;
     9 
    10 use SwooleServer as SwooleServer;
    11 use ThriftServerTServer;
    12 
    13 class Server extends TServer
    14 {
    15     public function serve()
    16     {
    17 
    18         $this->transport_->server->on('receive', [$this, 'handleReceive']);
    19         $this->transport_->listen();
    20 
    21     }
    22 
    23     public function stop()
    24     {
    25         $this->transport_->close();
    26     }
    27 
    28     /**
    29      * 处理RPC请求
    30      * @param Server $server
    31      * @param int $fd
    32      * @param int $fromId
    33      * @param string $data
    34      */
    35     public function handleReceive(SwooleServer $server, $fd, $fromId, $data)
    36     {
    37         $transport = new Transport($server, $fd, $data);
    38         $inputTransport = $this->inputTransportFactory_->getTransport($transport);
    39         $outputTransport = $this->outputTransportFactory_->getTransport($transport);
    40         $inputProtocol = $this->inputProtocolFactory_->getProtocol($inputTransport);
    41         $outputProtocol = $this->outputProtocolFactory_->getProtocol($outputTransport);
    42         $this->processor_->process($inputProtocol, $outputProtocol);
    43     }
    44 }

     

    该类继承自 ThriftServerTServer,在子类中需要实现 serve` 和 `stop`方法,分别定义服务器启动和关闭逻辑,这里我们在 serve方法中定义了 Swoole TCP 服务器收到请求时的回调处理函数,其中 $this->transport 指向 AppSwooleServerTransport 实例,回调函数 handleReceive中我们会将请求数据传入传输层处理类 Transport进行初始化,然后再通过一系列转化通过处理器对请求进行处理,该方法中 `$this` 指针指向的属性都是在外部启动 RPC 服务器时传入的,后面我们会看到。定义好请求回调后,即可通过 `$this->transport_->listen()` 启动服务器并监听请求。

     

    最后我们使用laravel-s的事件回调。

    在laravel-s的配置文件新增Master进程启动时的事件。

    'event_handlers'           => [     'ServerStart' => AppEventsServerStartEvent::class, ], 

    编写ServerStartEvent类。

     1 <?php
     2 namespace AppEvents;
     3 use AppSocketsServerTransport;
     4 use Hhxsv5LaravelSSwooleEventsServerStartInterface;
     5 use AppServicesServerUserService;
     6 use AppSocketsTFramedTransportFactory;
     7 use AppThriftUserUserProcessor;
     8 use ThriftFactoryTBinaryProtocolFactory;
     9 use SwooleHttpServer;
    10 use AppSocketsServer as TServer;
    11 
    12 
    13 class ServerStartEvent implements ServerStartInterface
    14 {
    15     public function __construct()
    16     {
    17     }
    18     public function handle(Server $server)
    19     {
    20         // 初始化thrift
    21         $processor = new UserProcessor(new UserService());
    22         $tFactory = new TFramedTransportFactory();
    23         $pFactory = new TBinaryProtocolFactory();
    24         // 监听本地 9999 端口,等待客户端连接请求
    25         $transport = new ServerTransport($server,'127.0.0.1', 9999);
    26         $server = new TServer($processor, $transport, $tFactory, $tFactory, $pFactory, $pFactory);
    27         $server->serve();
    28     }
    29 }

    这时候我们服务端的代码已经写完。开始写客户端的代码。

    接下来,我们来修改客户端请求服务端远程接口的代码,在此之前在 app/Sockets目录下新建一个 ClientTransport.php 来存放客户端与服务端通信的传输层实现代码:

      1 <?php
      2 namespace AppSockets;
      3     use SwooleClient;
      4     use ThriftExceptionTTransportException;
      5     use ThriftTransportTTransport;
      6 
      7     class ClientTransport extends TTransport
      8     {
      9         /**
     10          * @var string 连接地址
     11          */
     12         protected $host;
     13         /**
     14          * @var int 连接端口
     15          */
     16         protected $port;
     17         /**
     18          * @var Client
     19          */
     20         protected $client;
     21 
     22         /**
     23          * ClientTransport constructor.
     24          * @param string $host
     25          * @param int $port
     26          */
     27         public function __construct($host, $port)
     28         {
     29             $this->host = $host;
     30             $this->port = $port;
     31             $this->client = new Client(SWOOLE_SOCK_TCP);
     32         }
     33 
     34         /**
     35          * Whether this transport is open.
     36          *
     37          * @return boolean true if open
     38          */
     39         public function isOpen()
     40         {
     41             return $this->client->sock > 0;
     42         }
     43 
     44         /**
     45          * Open the transport for reading/writing
     46          *
     47          * @throws TTransportException if cannot open
     48          */
     49         public function open()
     50         {
     51             if ($this->isOpen()) {
     52                 throw new TTransportException('ClientTransport already open.', TTransportException::ALREADY_OPEN);
     53             }
     54             if (!$this->client->connect($this->host, $this->port)) {
     55                 throw new TTransportException(
     56                     'ClientTransport could not open:' . $this->client->errCode,
     57                     TTransportException::UNKNOWN
     58                 );
     59             }
     60         }
     61 
     62         /**
     63          * Close the transport.
     64          * @throws TTransportException
     65          */
     66         public function close()
     67         {
     68             if (!$this->isOpen()) {
     69                 throw new TTransportException('ClientTransport not open.', TTransportException::NOT_OPEN);
     70             }
     71             $this->client->close();
     72         }
     73 
     74         /**
     75          * Read some data into the array.
     76          *
     77          * @param int $len How much to read
     78          * @return string The data that has been read
     79          * @throws TTransportException if cannot read any more data
     80          */
     81         public function read($len)
     82         {
     83             if (!$this->isOpen()) {
     84                 throw new TTransportException('ClientTransport not open.', TTransportException::NOT_OPEN);
     85             }
     86             return $this->client->recv($len, true);
     87         }
     88 
     89         /**
     90          * Writes the given data out.
     91          *
     92          * @param string $buf The data to write
     93          * @throws TTransportException if writing fails
     94          */
     95         public function write($buf)
     96         {
     97             if (!$this->isOpen()) {
     98                 throw new TTransportException('ClientTransport not open.', TTransportException::NOT_OPEN);
     99             }
    100             $this->client->send($buf);
    101         }
    102     }

    我们在 app/Services/Client 目录下创建 UserService.php,用于存放 RPC 客户端连接与请求服务接口方法:

     1 <?php
     2     namespace AppServicesClient;
     3 
     4     use AppSocketsClientTransport;
     5     use AppThriftUserUserClient;
     6     use ThriftExceptionTException;
     7     use ThriftProtocolTBinaryProtocol;
     8     use ThriftProtocolTMultiplexedProtocol;
     9     use ThriftTransportTBufferedTransport;
    10     use ThriftTransportTFramedTransport;
    11     use ThriftTransportTSocket;
    12 
    13     class UserService
    14     {
    15         public function getUserInfoViaSwoole(int $id)
    16         {
    17             try {
    18                 // 建立与 SwooleServer 的连接
    19                 $socket = new ClientTransport("127.0.0.1", 9999);
    20 
    21                 $transport = new TFramedTransport($socket);
    22                 $protocol = new TBinaryProtocol($transport);
    23                 $client = new UserClient($protocol);
    24                 $transport->open();
    25 
    26                 $result = $client->getInfo($id);
    27 
    28                 $transport->close();
    29                 return $result;
    30             } catch (TException $TException) {
    31                 dd($TException);
    32             }
    33         }
    34     }

    测试,新增一个路由。

    1 Route::get('/user/{id}', function($id) {
    2     $userService = new UserService();
    3     $user = $userService->getUserInfoViaSwoole($id);
    4     return $user;
    5 });

    启动laravel-s。

    php bin/laravels start 

    在浏览器中输入 (192.168.10.100为我homestead设置的地址,5200为laravel-s设置的端口号)

    这时候我们就会发现浏览器上面出现chensi2几个大字。一个由larave+thrift+swoole搭建的微服务框架就这样完成了。端口号固定9999也可以使用consul做服务发现。

     

    当然了有兴趣的可以写一个package自己去实现而不用laravels这个扩展。

    • 多PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提升,对此我整理了一些资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货需要的可以免费分享给大家,需要的加群(点击→)677079770
  • 相关阅读:
    4、Work-Queue
    一个简单servlet容器
    一个简单的Web服务器
    jersey实现RESTful接口PUT方法JSON数据传递
    Java数据库连接组件C3P0和DBCP
    C/S架构和B/S架构
    一些同样适用于人生的计算机原理
    网络编程初探--使用UDP协议的简易聊天室
    IO练习--按字节截取字符串
    IO包中的其他类总结
  • 原文地址:https://www.cnblogs.com/a609251438/p/11811665.html
Copyright © 2020-2023  润新知