microse / microse-swoole
基于 swoole 的微服务远程对象引擎
Requires
- php: >=7.4
- ext-swoole: >=4.4.0
- rowbot/url: ^3.1
Requires (Dev)
- eaglewu/swoole-ide-helper: dev-master
- easyswoole/phpunit: ^1.0
- phpunit/phpunit: ^9.4
This package is auto-updated.
Last update: 2024-09-13 15:48:45 UTC
README
Microse(代表微服务远程对象引擎)是一个轻量级引擎,为应用提供将模块作为 RPC 服务提供的能力,无论在另一个进程还是在另一台机器上。
这是基于 Swoole 的 microse 实现的 PHP 版本。有关 API 参考,请参阅 API 文档,或 协议参考。
其他实现
- microse-node Node.js 实现
- microse-py Python 实现
安装
composer require microse/microse-swoole
剥洋葱
为了使用 microse,必须创建一个根 ModuleProxyApp
实例,这样其他文件就可以将其作为根代理使用并访问其子模块。
示例
// src/app.php require __DIR__ . "/../vendor/autoload.php"; use Microse\ModuleProxyApp; // Create an abstract class to be used for IDE intellisense: abstract class AppInstance extends ModuleProxyApp { } // Create the instance amd add type annotation: /** @var AppInstance */ $app = new ModuleProxyApp("App");
在其他文件中,只需定义一个与文件名相同的类,这样另一个文件就可以通过 $app
实例直接访问它。
// src/Bootstrap.php namespace App; class Bootstrap { public function init(): void { // ... } }
如果需要 IDE 类型支持,不要忘记增强 AppInstance
类中的类型
use App\Bootstrap; abstract class AppInstance extends ModuleProxyApp { public Bootstrap $Bootstrap; }
其他文件可以作为属性访问模块
// src/index.php include_once __DIR__ . "/app.php"; // Accessing the module as a singleton and calling its function directly. $app->Bootstrap->init();
远程服务
上述示例访问了模块并在当前进程中调用其函数,但我们还可以做更多,我们可以将模块作为远程服务提供服务,并作为远程过程调用其函数。
示例
例如,如果我想在不同的进程中提供服务,我只需这样做
// src/Services/User.py namespace App\Services; class User { private $users = [ ["firstName" => "David", "lastName" => "Wood"] ] public function getFullName(string $firstName): string { foreach ($this->users as $user) { if ($user["firstName"] === $firstName) { return $firstName . " " . $user["lastName"]; } } } } // src/app.php use App\Services\User; abstract class AppInstance extends ModuleProxyApp { public Services $Services; } abstract class Services { public User $User; }
// src/server.php include_once __DIR__ . "/app.php"; go(function () { global $app; $server = $app->serve("ws://:4000"); // Register the service, no need to include class file or set properties, // modules can be accessed directly. $server->register($app->Services->User); echo "Server started!\n"; });
只需尝试 php server.php
,服务将立即启动。
在客户端代码中,在使用远程函数之前连接到服务。
// client.php include_once __DIR__ . "/app.php"; go(function () { global $app; $client = $app->connect("ws://:4000"); $client->register($app->Services->User); // Accessing the instance in local style but actually calling remote. // Since we're using swoole, this procedure is actually asynchronous. $fullName = $app->Services->User->getFullName("David"); echo $fullName . "\n"; // David Wood });
注意:要在一台或多台服务器节点上发布服务,只需创建并连接到多个通道,并将服务注册到每个通道上,在调用远程函数时,microse 将自动计算路由并将流量重定向到它们。
注意:RPC 调用将序列化(通过 JSON)所有输入和输出数据,那些无法序列化的数据将在传输过程中丢失。
生成器支持
在需要传输大量数据时,生成器函数可以提供很大帮助,与发送整个数据块可能会阻塞网络流量的常规函数不同,生成器函数将分批传输数据。
// src/Services/User.php namespace App\Services; class User { private $friends = [ "David" => [ [ "firstName" => "Albert", "lastName" => "Einstein" ], [ "firstName" => "Nicola", "lastName" => "Tesla" ], // ... ], // ... ]; public function getFriendsOf(string $name): \Generator { $friends = @$this->friends[$name]; if ($friends) { foreach ($friends as $friend) { yield $friend["firstName"] => $friend["lastName"]; // NOTE: only PHP supports 'yield $key => $value', if this // function is call from other languages, such as Node.js, // the '$key' will be ignored. } return "These are all friends"; } } }
$generator = $app->Services->User->getFriendsOf("David"); foreach ($generator as $firstName => $lastName) { echo $firstName . " ". $lastName . "\n"; // Albert Einstein // Nicola tesla // ... } // We can get the return value as well: $returns = $generator->getReturn(); // These are all friends
生成器函数返回一个 RPC 版本的生成器,因此如果您想向生成器发送数据,可以使用 Generator::send()
来实现,传递给方法的价值也将被传递到远程实例。
生命周期支持
由于 swoole 已经在底层处理异步操作,因此生命周期支持通过原始的 __construct
和 __destruct
方法实现,无需额外努力。
独立客户端
Microse 还提供了一种作为客户端应用程序运行的方式,在这种情况下,客户端实际上不会加载任何模块,因为没有这样的文件,它只是映射模块名称,这样您就可以像通常一样使用它们。
在以下示例中,我们假设 $app->services->user
服务由 Node.js 程序提供服务,我们可以在我们的 PHP 程序中像往常一样使用它。
use Microse\ModuleProxyApp; /** @var AppInstance */ $app = new ModuleProxyApp("app", false); // pass the second argument false go(function () use ($app) { $client = $app->connect("ws://:4000"); $client->register($app->services->user); $fullName = $app->services->user->getFullName("David"); echo $fullName . "\n"; // David Wood });
对于仅客户端的应用程序,您可能需要声明所有抽象类
abstract class AppInstance extends ModuleProxyApp { public services $services; } abstract class services { public user $user; } // Use 'interface' works as well since 'user' doesn't contain properties. interface user { abstract function getFullName(string $name): string; }
进程互操作
此实现支持同一进程中的互操作,这意味着,如果它检测到目标远程实例在该进程中提供服务,该函数将始终在本地调用,以防止不必要的网络流量。