经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » PHP » 查看文章
PHP laravel+thrift+swoole打造微服务框架
来源:cnblogs  作者:程序媛的明天  时间:2019/11/7 20:45:50  对本文有异议

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

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

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

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

 

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

 

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

  1. laravel new laravel-thrift-app

安装laravel-s

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

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

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

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

 

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

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

 

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

 

通过 Composer 安装 Thrift PHP 依赖包:

  1. composer require apache/thrift

 

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

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

 

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

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

 

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

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

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

 

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

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

 

 

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

 

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

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

  1. 'event_handlers' => [ 'ServerStart' => \App\Events\ServerStartEvent::class, ],

 

编写ServerStartEvent类。

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

 

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

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

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

 

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

  1. 1 <?php
  2. 2 namespace App\Services\Client;
  3. 3
  4. 4 use App\Sockets\ClientTransport;
  5. 5 use App\Thrift\User\UserClient;
  6. 6 use Thrift\Exception\TException;
  7. 7 use Thrift\Protocol\TBinaryProtocol;
  8. 8 use Thrift\Protocol\TMultiplexedProtocol;
  9. 9 use Thrift\Transport\TBufferedTransport;
  10. 10 use Thrift\Transport\TFramedTransport;
  11. 11 use Thrift\Transport\TSocket;
  12. 12
  13. 13 class UserService
  14. 14 {
  15. 15 public function getUserInfoViaSwoole(int $id)
  16. 16 {
  17. 17 try {
  18. 18 // 建立与 SwooleServer 的连接
  19. 19 $socket = new ClientTransport("127.0.0.1", 9999);
  20. 20
  21. 21 $transport = new TFramedTransport($socket);
  22. 22 $protocol = new TBinaryProtocol($transport);
  23. 23 $client = new UserClient($protocol);
  24. 24 $transport->open();
  25. 25
  26. 26 $result = $client->getInfo($id);
  27. 27
  28. 28 $transport->close();
  29. 29 return $result;
  30. 30 } catch (TException $TException) {
  31. 31 dd($TException);
  32. 32 }
  33. 33 }
  34. 34 }

 

测试,新增一个路由。

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

 

启动laravel-s。

  1. php bin/laravels start

 

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

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

 

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

原文链接:http://www.cnblogs.com/a609251438/p/11811665.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号