乐趣区

关于swoole:推荐一个基于swoole开发高性能rpc框架

简介

swrpc 是一个基于 swoole 开发的高性能 rpc 包,swrpc 提供了注册发现,链路追踪,中间件等等性能,能够很容易集成到第三方框架,如 laravel,yii 等等。
https://github.com/wuzhc/swrpc

性能

  • 反对多过程模式或协程模式
  • 反对同步,异步调用
  • 反对自定义中间件
  • 反对服务端按类提供对外服务
  • 反对链路追踪
  • 反对注册服务发现
  • 反对客户端负载平衡,蕴含随机,权重两种模式
  • 反对自定义日志,包协定,序列化形式等等

装置

php composer.phar require "wuzhc/swprc:~1.0.1" -vvv

疾速体验

假如咱们 User 和 School 两个模块,为了取得用户学校名称,咱们须要在 User 模块发动 rpc 申请调用 School 模块的服务。

School 模块

<?php
use Swrpc\LogicService;
class SchoolService extends LogicService 
{public function getUserSchool($userID) {
        $name = $userID == 123 ? '火星' : '水星';
        return $name.'学校';
    }    
}

School 模块将作为 rpc 服务端,对外提供服务,启动如下:

<?php
use Swrpc\Server;

$basePath = dirname(dirname(__FILE__));
require_once $basePath . "/vendor/autoload.php";

$options = [
    'enable_coroutine' => true,
    'pid_file'         => __DIR__ . '/swrpc.pid',
];
$server = new Server('School_Module', '127.0.0.1', 9501, 1, $options);
$server->addService(SchoolService::class); 
$server->start();

将 SchoolService 增加到 server,server 会自动检索类中所有可用的 public 办法。

User 模块

User 模块作为客户端,调用 School 模块服务如下

<?php
use Swrpc\Request;
use Swrpc\LogicService;
use Swrpc\Client;

class UserService extends LogicService
{public function getUserSchoolName()
    {
        $userID = 123;
        $module = 'School_Module'; // 申请指标模块名称,须要和服务端定义的统一
        $client = Client::create($module, '127.0.0.1', 9501);
        return $client->send(Request::create('SchoolService_getUserSchool', [$userID]));
    }
}

// 调用
echo UserService::factory()->getUserSchoolName();

多过程和协程模式

多过程或协程模式须要和 swoole 配置统一,具体参考 swoole 配置

多过程模式

创立 10 过程来解决申请

$options = [
    'worker_num'       => 10
    'pid_file'         => __DIR__ . '/swrpc.pid',
];
$server = new Server('School_Module', '127.0.0.1', 9501, 1, $options);

协程模式

目前 swrpc 协程模式是运行在单过程的

$options = [
    'enable_coroutine' => true,
    'pid_file'         => __DIR__ . '/swrpc.pid',
];
$server = new Server('School_Module', '127.0.0.1', 9501, 1, $options);

同步调用和异步调用

在客户端发动同步调用,客户端会始终期待服务端返回后果

$client = \Swrpc\Client::create($module, '127.0.0.1', 9501);
return $client->send(SyncRequest::create('SchoolService_getUserSchool', [$userID]));

在客户端发动异步调用,客户端会立马失去响应后果,申请将被 swoole 的 task 过程解决

$client = \Swrpc\Client::create($module, '127.0.0.1', 9501);
return $client->send(AsyncRequest::create('SchoolService_getUserSchool', [$userID]));

自定义中间件

中间件容许程序能够对申请进行前置操作和后置操作,底层应用了责任链设计模式,所以为了执行下一个中间件,必须返回 $next($request),如果想提前返回,则返回后果必须是Swrpc\Response 类型

// 中间件除了用匿名函数定义,还能够用实现 Swrpc\Middlewares\MiddlewareInterface 接口的类
$middleware = function (\Swrpc\Request $request, Closure $next) {$start = microtime(true); // 前置操作,记录申请开始工夫
    $result = $next($request);
    echo '耗时:'.(microtime(true) - $start).PHP_EOL; // 后置操作,记录申请完结工夫,从而计算申请耗时
    return $result; // 持续下个中间件的解决
};
$server = new Server('School_Module', '127.0.0.1', 9501, 1, $options);
$server->addService(SchoolService::class); 
$server->addMiddleware($middleware); // 增加中间件
$server->start();

如果要提前停止中间件,能够提前在匿名函数或类办法中返回 \Swrpc\Response 对象,如下

$middleware = function (\Swrpc\Request $request, Closure $next) {if (empty($request->getParams())) {return \Swrpc\Response::error('参数不能为空'); // 提前返回,必须是 Response 类型
    }   
    return $next($request); 
};

服务端按类提供对外服务

从下面的例子中,咱们把 SchoolService 整个类增加的 server 中,这样 server 就能对外提供 SchoolService 类所有 public 办法的性能。

$server = new Server('School_Module', '127.0.0.1', 9501, 1, $options);
$server->addService(SchoolService::class); // 提供 SchoolService 所有 public 办法性能
$server->addService(AreaService::class); // 提供 AreaService 所有 public 办法性能
$server->start();

注册服务发现

如果服务端启动的时候有设置注册核心,则启动胜利会主动向注册核心注册服务端地址。

$server = new Server('School_Module', '127.0.0.1', 9501, 1, $options);
$server->addRegister(new Consul(['weights' => ['Passing' => 10, 'Warning' => 1]]));
$server->addService(SchoolService::class); 
$server->start();

如上,应用 Consul 作为服务的注册核心,通过 http://127.0.0.1:8500 能够查看注册信息,如果想用 etcd 等其余注册核心,只有实现 Swrpc\Middlewares\RegisterInterface 接口即可,而后在通过 $server->addRegister() 增加到 server

客户端负载平衡

如果服务端启动多个节点,例如 School 模块启动 3 个节点,并且注册到了注册核心,那么咱们能够从注册核心获取所有服务端节点信息,而后做一些策略解决。

$register = new Consul();
$client = \Swrpc\Client::createBalancer('School_Module', $register, \Swrpc\Client::STRATEGY_WEIGHT);
$result = $client->send(Request::create('SchoolService_getUserSchool', [$userID]);

目前 swrpc 提供两种简略策略模式,\Swrpc\Client::STRATEGY_WEIGHT 权重模式 \Swrpc\Client::STRATEGY_RANDOM 随机模式

链路追踪

当咱们的服务十分多并且须要相互调用时候,如果其中某个调用失败,会导致咱们得不到咱们想要的后果,而要调试出是哪个环节出了问题也比拟麻烦,可能你须要登录每台机器看下有没有谬误日志,或者看返回的错误信息是哪个服务提供的。链路追踪记录了整个调用链过程,如果某个环节出错,咱们能够疾速从调用链失去调用中断中央。

class UserService extends LogicService
{public function getUserSchoolName()
    {
        $userID = 123;
        $module = 'School_Module'; // 申请指标模块名称,须要和服务端定义的统一
        $client = Client::create($module, '127.0.0.1', 9501);
        return $client->send(Request::create('SchoolService_getUserSchool', [$userID], $this->getTracerContext(__FUNCTION__))); //getTracerContext()用于提供追踪上下文}
}

$users = UserService::factory()
    ->setModule('User_Module') // 以后模块,用于调用链的终点
    ->setTracerUrl('http://127.0.0.1:9411/api/v2/spans') //zipkin 链路追踪地址
    ->getUserSchoolName();

如图,User_Module 调用 Class_Module,Class_Module 又去调用 School_Module

每个调用还记录响应后果

自定义日志处理器

默认应用 Monolog/Logger 作为日志处理器,日志信息会输入到控制台。可依据本人需要笼罩默认处理器,只有日志类
实现 Psr\Log\LoggerInterface 即可

use Swrpc\Server;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;

$logger = new Logger('swrpc');
$logger->pushHandler(new StreamHandler(fopen('xxxx.log','w+'), Logger::DEBUG));

$server = new Server('127.0.0.1', 9501, ['enable_coroutine'=>true]);
$server->addService(UserService::class);
$server->addLogger($logger); // 笼罩默认日志处理器
$server->start();

序列化形式

默认应用固定头 + 包体来解决 tcp 粘包 问题,默认配置为 'package_length_type' => 'N','package_body_offset' => 4
默认序列化数据会应用 serialize(),如果 swoole 版本在 4.5 以上的主动应用 swoole_substr_unserialize(),能够实现的类来笼罩默认配置,只有实现src/Packer/PackerInterface 即可,留神服务端和客户端须要应用一样的协定,否则解析不了。

use Swrpc\Server;

$packer = new \Swrpc\Packer\SerializeLengthPacker();
$server = new Server('127.0.0.1', 9501, ['enable_coroutine'=>true]);
$server->addService(UserService::class); 
$server->addPacker($packer); // 笼罩默认值

平安证书配置

参考:https://wiki.swoole.com/#/ser…

服务端

$options = [
    'ssl_cert_file' => __DIR__.'/config/ssl.crt',
    'ssl_key_file'  => __DIR__.'/config/ssl.key',
    'pid_file'      => __DIR__ . '/swrpc.pid',
];
$server = new Server('School_Module', '127.0.0.1', 9501, $options, SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL);
$server->addService(SchoolService::class); 
$server->start();

留神:

  • HTTPS 利用浏览器必须信赖证书能力浏览网页;
  • wss 利用中,发动 WebSocket 连贯的页面必须应用 HTTPS
  • 浏览器不信赖 SSL 证书将无奈应用 wss
  • 文件必须为 PEM 格局,不反对 DER 格局,可应用 openssl 工具进行转换

测试

应用 phpuint 实现的简略测试案例,配置文件phpunit.xml

php phpunit.phar tests --debug

phpunit 测试报告

Client (SwrpcTests\Client)
 [x] Client connect
 [x] Client sync request
 [x] Client async request

Packer (SwrpcTests\Packer)
 [x] Serialize length pack
 [x] Serialize lenght unpack
 [x] Serialize eof pack
 [x] Serialize eof unpack

Server (SwrpcTests\Server)
 [x] Server register to consul
 [x] Server unregister from consul
 [x] Server add service
 [x] Server add middleware
退出移动版