目录
在之前的 PHP Swoole的基本用法_浮尘笔记的博客-CSDN博客 中演示了Swoole对于TCP、UDP、HTTP、WebSocket的基本用法,本篇内容将主要演示基于Swoole的一个框架Swoft中如何实现一个RPC服务。
Swoft 是一款基于 Swoole 扩展实现的 PHP 微服务协程框架。Swoft 能像 Go 一样,内置协程网络服务器及常用的协程客户端且常驻内存,不依赖传统的 PHP-FPM。有类似 Go 语言的协程操作方式,有类似 Spring Cloud 框架灵活的注解、强大的全局依赖注入容器、完善的服务治理、灵活强大的 AOP、标准的 PSR 规范实现等等。官网:Swoft
安装Swoft
git clone https://github.com/swoft-cloud/swoft.git
cd swoft/
composer install
cp .env.example .env
运行服务: 运行服务 | Swoft
## 查看帮助
php ./bin/swoft -h
## 查看版本
php ./bin/swoft -V
## [HTTP 服务]
$ php ./bin/swoft http:start # 启动 HTTP 服务
$ php ./bin/swoft http:start -d # 以守护进程模式启动
$ php ./bin/swoft http:restart # 重启 HTTP 服务
$ php ./bin/swoft http:reload # 重新加载 HTTP 服务
$ php ./bin/swoft http:stop # 停止 HTTP 服务
## [Websocket服务]
$ php ./bin/swoft ws:start # 启动 WS 服务
$ php ./bin/swoft ws:start -d # 以守护进程模式启动
$ php ./bin/swoft ws:restart # 重启 WS 服务
$ php ./bin/swoft ws:reload # 重新加载 WS 服务
$ php ./bin/swoft ws:stop # 关闭 WS 服务
## [RPC服务]
$ php ./bin/swoft rpc:start # 启动 RPC 服务
$ php ./bin/swoft rpc:start -d # 以守护进程模式启动
$ php ./bin/swoft rpc:restart # 重启 RPC 服务
$ php ./bin/swoft rpc:reload # 重新加载 RPC 服务
$ php ./bin/swoft rpc:stop # 关闭 RPC 服务
Swoft的注解
官方资料:注解 | Swoft
【问】注解 和 注释 有什么区别?
【答】注释提供对可执行代码的说明,单纯用于开发人员阅读,不影响代码的执行。注解往往充当着对代码的声明和配置的作用,为可执行代码提供程序可用的额外信息。注解(Annotations)是Swoft里面很多重要功能特别是AOP、IoC容器的基础。
/**
* Class Home
*
* @Controller(prefix="home")
*/
class Home
{
/**
* 该方法路由地址为 /home/index
*
* @RequestMapping(route="/index", method="post")
*
* @param Request $request
*/
public function index(Request $request)
{
// TODO:
}
}
Swoft中的注解是怎么实现的?
在启动的时候,它会遍历文件夹下边的文件,然后通过反射获取里面的注解然后来使用。
先进入 swoft/bin/swoft 文件,
(new \App\Application())->run();
点进去 \App\Application() 方法,进入到 swoft/vendor/swoft/framework/src/SwoftApplication.php,找到 processors() 方法
protected function processors(): array
{
return [
new EnvProcessor($this), //处理环境变量
new ConfigProcessor($this), //处理配置信息
new AnnotationProcessor($this), //处理注解
new BeanProcessor($this), //处理容器对象
new EventProcessor($this), //处理事件
new ConsoleProcessor($this), //处理命令
];
}
找到 swoft/vendor/swoft/framework/src/Processor/AnnotationProcessor.php 中,
// 遍历目录查找其中的AutoLoader.php,如果没有就不解析
AnnotationRegister::load([]);
swoft/vendor/swoft/annotation/src/Resource/AnnotationResource.php
public function load(): void
{
// 通过composer获取psr-4映射的目录并遍历
$prefixDirsPsr4 = $this->classLoader->getPrefixesPsr4();
//...
$this->loadAnnotation($autoLoader);
}
/**
* 循环解析目录下的每个文件的注解
*/
private function loadAnnotation(LoaderInterface $loader): void
{
//...
// 解析某一个类,查看某个类/方法/属性 有没有注解
$this->parseAnnotation($ns, $className);
}
private function parseAnnotation(string $namespace, string $className): void
{
// 使用反射获取类中的信息
$reflectionClass = new ReflectionClass($className);
//根据反射类解析并查找类的注解,并且返回整合完的注解,返回数组
$oneClassAnnotation = $this->parseOneClassAnnotation($reflectionClass);
//注册到 AnnotationRegister 类中
AnnotationRegister::registerAnnotation();
}
private function parseOneClassAnnotation(ReflectionClass $reflectionClass): array
{
//获取类的注解
$classAnnotations = $reader->getClassAnnotations($reflectionClass);
//注册注解
$this->registerParser($className, $classAnnotation);
//获取类的属性并遍历,获取属性注解
$reflectionProperties = $reflectionClass->getProperties();
//获取类方法,遍历方法,获取方法的注解
$reflectionMethods = $reflectionClass->getMethods();
}
swoft/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php
namespace Doctrine\Common\Annotations; //使用 Doctrine 注解引擎
public function getClassAnnotations(ReflectionClass $class)
{
// getDocComment
// https://www.php.net/manual/en/reflectionclass.getconstructor.php
return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName());
}
swoft/vendor/swoft/bean/src/Container.php 中可以看到获取到的注解其实是个数组格式
/**
* @var Container
*/
public static $instance;
/**
* All load annotations
*
* @var array
*
* @example
* [
* 'loadNamespace' => [
* 'className' => [
* 'annotation' => [
* new ClassAnnotation(),
* new ClassAnnotation(),
* new ClassAnnotation(),
* ]
* 'reflection' => new ReflectionClass(),
* 'properties' => [
* 'propertyName' => [
* 'annotation' => [
* new PropertyAnnotation(),
* new PropertyAnnotation(),
* new PropertyAnnotation(),
* ]
* 'reflection' => new ReflectionProperty(),
* ]
* ],
* 'methods' => [
* 'methodName' => [
* 'annotation' => [
* new MethodAnnotation(),
* new MethodAnnotation(),
* new MethodAnnotation(),
* ]
* 'reflection' => new ReflectionFunctionAbstract(),
* ]
* ],
* 'pathName' => '/xxx/xx/xx.php'
* ]
* ]
* ]
*/
以上流程只是大概了解了一下Swoft注解实现的过程,里面还有很多细节和优秀的设计思想值得学习,所以使用一个框架的时候要尽可能的去阅读它的源代码,能够从中学习到很多优秀的设计思想。
PHP8中已经新增了注解的功能,可以参考这篇文章:PHP8的注解_php8 注解_烟草的香味.的博客-CSDN博客
Swoft中的RPC服务
官方文档:RPC Server | Swoft
RPC,是一种远程调用方式(Remote Procedure Call),通过 RPC 我们可以像调用本地方法一样调用别的机器上的方法,用户将无感服务器与服务器之间的通讯。RPC 在微服务当中起到相当大的作用。RPC 就用于调用者与服务之间的通讯,RPC 协议可基于 TCP、UDP 或者 HTTP 实现,但是更推荐选择 TCP。
RPC 调用和 RESTful API 两者之间的区别在哪呢?
- TCP 支持长连接,当调用服务的时候不需要每次都进行三次握手才实现。从性能和网络消耗来说 RPC 都具备了很好的优势。
- RESTful API 基于 HTTP 的,也就是说每次调用服务都需要进行三次握手建立起通信才可以实现调用,当我们的并发量高的时候这就会浪费很多带宽资源。
- 服务对外的话采用 RESTful API 会比 RPC 更具备优势,因此看自己团队的服务是对内还是对外。
更多功能和说明可以详细阅读官方文档,接下来演示一下 调用者 的实现过程。
把swoft的源码复制三份,分别命名为 order(订单服务)、goods(商品服务)、user(用户服务),表示订单服务 需要从 商品服务 和 用户服务中调用数据。
修改 user/app/bean.php 中修改 rpcServer的端口号
'rpcServer' => [
'class' => ServiceServer::class,
'port' => 18408,
],
启动并运行:
php user/bin/swoft rpc:start
然后修改 goods服务的 goods/app/bean.php 的 rpcServer 改为 18409,还有 goods/app/Rpc 下面的“User”相关的部分都改成 “Goods”,order的同理。删掉 goods/app/Http/Controller/RpcController.php
然后启动运行。
接下来修改调用端:order的部分,修改 httpServer 的端口号为18406,user的端口号改为上面设置的 18408,同时复制一份user和user.pool部分为goods和goods.pool,
'httpServer' => [
'port' => 18406,
],
'user' => [
'host' => '127.0.0.1',
'port' => 18408,
],
'user.pool' => [
],
'goods' => [
'host' => '127.0.0.1', //如果是另一台主机,则改为对应主机的IP地址
'port' => 18409,
],
'goods.pool' => [
],
把上面的 goods/app/Rpc/Lib/GoodsInterface.php 给 order/app/Rpc/Lib/GoodsInterface.php 复制一份,修改 order/app/Http/Controller/RpcController.php
use App\Rpc\Lib\UserInterface;
use App\Rpc\Lib\GoodsInterface;
class RpcController
{
/**
* @Reference(pool="user.pool")
*
* @var UserInterface
*/
private $userService;
/**
* @Reference(pool="goods.pool")
*
* @var GoodsInterface
*/
private $goodsService;
public function getList(): array
{
$result = $this->userService->getList(12, 'type');
$result2 = $this->goodsService->getList(12, 'type');
return [$result, $result2];
}
}
然后把这个order的服务也启动起来。
打开浏览器,访问 http://localhost:18406/rpc/getList
第一个输出的是userList,说明是从user这个服务获取的信息,第二个打印的是goodsList,说明是从goods服务获取的信息。在没有注册中心的情况下,直接通过RPC连接下面的服务来访问获取数据的。
本篇内容就先到这里吧,代码演示的较多,详细代码可以参考: https://gitee.com/rxbook/swoft-demo-2023