microse/microse-swoole

基于 swoole 的微服务远程对象引擎

v1.1.2 2021-06-13 08:10 UTC

This package is auto-updated.

Last update: 2024-09-13 15:48:45 UTC


README

Microse(代表微服务远程对象引擎)是一个轻量级引擎,为应用提供将模块作为 RPC 服务提供的能力,无论在另一个进程还是在另一台机器上。

这是基于 Swoole 的 microse 实现的 PHP 版本。有关 API 参考,请参阅 API 文档,或 协议参考

其他实现

安装

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;
}

进程互操作

此实现支持同一进程中的互操作,这意味着,如果它检测到目标远程实例在该进程中提供服务,该函数将始终在本地调用,以防止不必要的网络流量。