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 https://github.com/hhxsv5/laravel-s/blob/master/README-CN.md

  1. 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 文件生成相关的服务代码:

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

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

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

我们在代理类的构造函数中初始化 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 App\Sockets;
  9.  
  10. use Swoole\Server as SwooleServer;
  11. use Thrift\Exception\TTransportException;
  12. use Thrift\Transport\TTransport;
  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 App\Sockets;
  9.  
  10. use Swoole\Server as SwooleServer;
  11. use Thrift\Server\TServer;
  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. }

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

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

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

  1. <?php
  2. namespace App\Sockets;
  3. use Swoole\Client;
  4. use Thrift\Exception\TTransportException;
  5. use Thrift\Transport\TTransport;
  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 App\Services\Client;
  3.  
  4. use App\Sockets\ClientTransport;
  5. use App\Thrift\User\UserClient;
  6. use Thrift\Exception\TException;
  7. use Thrift\Protocol\TBinaryProtocol;
  8. use Thrift\Protocol\TMultiplexedProtocol;
  9. use Thrift\Transport\TBufferedTransport;
  10. use Thrift\Transport\TFramedTransport;
  11. use Thrift\Transport\TSocket;
  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。

  1. php bin/laravels start

在浏览器中输入 http://192.168.10.100:5200/user/2 (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

PHP laravel+thrift+swoole打造微服务框架的更多相关文章

  1. 基于thrift的微服务框架

    前一阵开源过一个基于spring-boot的rest微服务框架,今天再来一篇基于thrift的微服务加框,thrift是啥就不多了,大家自行百度或参考我之前介绍thrift的文章, thrift不仅支 ...

  2. Thrift 个人实战--Thrift RPC服务框架日志的优化

    前言: Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的rpc框架服务端/客户端代码. 不过Thrift的实现, 简单使用离实际生产环境还 ...

  3. laravel 整合 swoole ,并简单 ab 测试对比性能以及在 PHPstorm 中利用debug调试配置swoole服务中的PHP代码

    安装PHP 的 swoole 扩展 及 安装 laravel,就不描述了 整合 laravel 和 swoole 用了这个轮子,侵入性很小,一行代码搞定,推荐一下,今天刚用,不能预测未来是否会遇见坑 ...

  4. RPC服务框架探索之Thrift

    前言架构服务化后,需要实现一套方便调用各服务的框架,现在开源如日中天,优先会寻找开源实现,如果没有合适自家公司业务的,才会考虑从零开发,尤其是一切以KPI为准绳的公司,谁会跟钱过不去?N个月之前,公司 ...

  5. 美团大众点评服务框架Pigeon

    服务框架Pigeon架构 • Pigeon提供jar包接入 ,线上运行在tomcat里 • Monitor-CAT ,负责调用链路分析.异常监控告警等 • 配置中心-Lion ,负责一些开关配置读取 ...

  6. 分布式服务框架 dubbo/dubbox 入门示例

    dubbo是一个分布式的服务架构,可直接用于生产环境作为SOA服务框架. 官网首页:http://dubbo.io/ ,官方用户指南 http://dubbo.io/User+Guide-zh.htm ...

  7. .Net下几个服务框架介绍

    简介 在公司的服务多了以后,为了调用上的方便,同时为了以后的服务治理,一般都会使用一些服务框架,这里主要介绍我知道的几个服务框架,简析一下这些服务框架的基本概念. 可投入生产环境使用的 以下两个服务框 ...

  8. Laravel 学习笔记 —— 神奇的服务容器 [转]

    容器,字面上理解就是装东西的东西.常见的变量.对象属性等都可以算是容器.一个容器能够装什么,全部取决于你对该容器的定义.当然,有这样一种容器,它存放的不是文本.数值,而是对象.对象的描述(类.接口)或 ...

  9. 高性能的分布式服务框架 Dubbo

    我思故我在,提问启迪思考! 1. 什么是Dubbo? 官网:http://dubbo.io/,DUBBO是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及作为SOA服务治理的 ...

随机推荐

  1. 支撑微博亿级社交平台,小白也能玩转Redis集群(实战篇)

    上篇文章<支撑微博亿级社交平台,小白也能玩转Redis集群(原理篇)>介绍了Redis集群相关原理,这篇文章将介绍Redis Cluster集群的搭建.配置,运维.扩容等具体操作 集群搭建 ...

  2. Service Cloud 零基础(一)Case 浅谈

    本片参考:https://resources.docs.salesforce.com/222/latest/en-us/sfdc/pdf/salesforce_case_implementation_ ...

  3. 转 NAT技术详解

    NAT产生背景 今天,无数快乐的互联网用户在尽情享受Internet带来的乐趣.他们浏览新闻,搜索资料,下载软件,广交新朋,分享信息,甚至于足不出户获取一切日用所需.企业利用互联网发布信息,传递资料和 ...

  4. 基于STL的堆略解

    什么是STL 以下内容摘自这儿. STL(Standard Template Library,标准模板库)是惠普实验室开发的一系列软件的统称.它是由Alexander Stepanov.Meng Le ...

  5. 5分钟读懂Linux权限管理

    权限管理: 本文用于初学者对Linux文件系统权限的快速了解!! 进程安全上下文:   进程对文件的访问权限应用模型:     进程的属主与文件的属主是否相同:如果相同,则应用属主权限:      否 ...

  6. halcon学习方法小结及以后的学习计划

    学了这么久的halcon,感觉还是没有摸到门路. 记录一下这么久以来经历过的学习阶段: 看冈萨雷斯<数字图像处理>这本书,使用halcon做练习. 我实际上只比较完整地看了这本书的形态学处 ...

  7. 使用 Helm Chart 部署及卸载 istio

    部署 istio 1.添加 istio 官方的 helm 仓库 helm repo add istio https://storage.googleapis.com/istio-release/rel ...

  8. $nextTick 页面局部刷新 延迟加载

    Vue中的nextTick涉及到Vue中DOM的异步更新,感觉很有意思,特意了解了一下.其中关于nextTick的源码涉及到不少知识,很多不太理解,暂且根据自己的一些感悟介绍下nextTick 本人写 ...

  9. SpringBoot与MybatisPlus3.X整合之通用枚举(十二)

    一 通用枚举 解决了繁琐的配置,让 mybatis 优雅的使用枚举属性! 自3.1.0开始,可配置默认枚举处理类来省略扫描通用枚举配置 默认枚举配置 升级说明: 3.1.0 以下版本改变了原生默认行为 ...

  10. java常用类Time

    LocalDate:IOS格式(yyyy-MM-dd)日期 LocalTime:表示一个时间 LocalDateTime:表示时间日期 Instant 时间线上的瞬时点,可以用来记录应用程序中的时间时 ...