hyperf实现Rpc服务(docker+consul)
前言
在开始搭建hyperf的rpc环境之前,需要准备两台服务器,配置好php与swoole的环境,并且在其中一台服务器中部署好consul服务,其中php的环境配置与consul的搭建,可以在小编的其他篇章的文章中获取,基本环境如下。
服务端 Server :
IP : port (tcp) | 服务 |
---|---|
192.168.67.128 : 9503 | docker,php,swoole |
客户端 Client :
IP : port (tcp) | 服务 |
---|---|
192.168.67.130 : 9508 | docker,php,swoole,consul |
先切换阿里云镜像
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer
下拉项目
composer create-project hyperf/hyperf-skeleton
此处假装已经部署好了hyperf的基本环境,并且可以正常运行hyperf项目
开始部署rpc服务
1 . 1 安装组件
在 server端 与 client 端都需要加载
composer require hyperf/consul
composer require hyperf/json-rpc
composer require hyperf/rpc-server
composer require hyperf/rpc-client
#安装统一接入层
composer require hyperf/service-governance
#选择安装对应的适配器
composer require hyperf/service-governance-consul
1 . 2 发布配置文件
在 server端 与 client 端都需要发布配置
生成 autoload/services.php文件
php bin/hyperf.php vendor:publish hyperf/service-governance
生成 autoload/cunsul.php文件
php bin/hyperf.php vendor:publish hyperf/consul
1 . 3 server服务端 (192.168.67.128)
提供rpc所需要服务以及配置设置,这里依照官网的案例,重点是补充官网中没提到的细节操作
1 . 3 . 1 服务提供
创建 app\JsonRpc目录,并且定义接口与提供的服务,具体配置参考官网文档
<?php
namespace App\JsonRpc;
use Hyperf\RpcServer\Annotation\RpcService;
/**
* 注意,如希望通过服务中心来管理服务,需在注解内增加 publishTo 属性
* @RpcService(name="CalculatorService", protocol="jsonrpc-http", server="jsonrpc-http" , publishTo="consul")
*/
class CalculatorService implements CalculatorServiceInterface
{
// 实现一个加法方法,这里简单的认为参数都是 int 类型
public function add(int $a, int $b): int
{
// 这里是服务方法的具体实现
return $a + $b;
}
}
定义接口契约 CalculatorServiceInterface
<?php
declare(strict_types=1);
namespace App\JsonRpc;
interface CalculatorServiceInterface
{
public function add(int $v1, int $v2): int;
public function getIpList(int $numbers);
}
1 . 3 . 2 接口与服务的绑定
在服务端,需要在 \config\autoload\dependencies.php 中使得接口与服务的对象绑定
<?php
declare(strict_types=1);
return[
App\JsonRpc\CalculatorServiceInterface::class => App\JsonRpc\CalculatorService::class,
];
1 . 3 . 3 配置 server.php
在config/autoload/server.php中配置提供tcp服务的端口与监听
[
'name' => 'jsonrpc',
'type' => Server::SERVER_BASE,
'host' => '0.0.0.0',
'port' => 9503,
'sock_type' => SWOOLE_SOCK_TCP,
'callbacks' => [
Event::ON_RECEIVE => [\Hyperf\JsonRpc\TcpServer::class, 'onReceive'],
],
],
'settings' => [
//'open_eof_split' => true,
'open_eof_check' => true,
'package_eof' => "\r\n",
'package_max_length' => 1024 * 1024 * 20,
/*'open_length_check' => true,
'package_length_type' => 'N',
'package_length_offset' => 0,
'package_body_offset' => 4,
'package_max_length' => 1024 * 1024 * 20,*/
],
]
注意一 :
如果使用consul,项目如果hyperf运行在docker环境内,不自定义服务的 ip 和 port的话 ,在服务启动的时候,hyperf会获取到docker 172.17.0.XXX 的地址注册到consul中,这个地址是无法满足对外提供的,需要配置自定义 ip 和port
1 . 3 . 3 . 1 自定义 ip 和 port
在 app\Listener 目录中创建 RpcRegisterServiceListener
<?php
declare(strict_types=1);
namespace App\Listener;
use Hyperf\ServiceGovernance\Listener\RegisterServiceListener;
use InvalidArgumentException;
/**
* Rpc服务注册到第三方注册中心重写
*
* Class RpcRegisterServiceListener
*
* @package App\Listener
*/
class RpcRegisterServiceListener extends RegisterServiceListener
{
/**
* 重写获取服务与配置信息 -- 使用自定义ip:port注册服务
*
* @return array
*/
protected function getServers(): array
{
$result = [];
$servers = $this->config->get('server.servers', []);
foreach ($servers as $server) {
if (! isset($server['name'], $server['host'], $server['port'])) {
continue;
}
if (! $server['name']) {
throw new InvalidArgumentException('Invalid server name');
}
if ($server['name'] != 'jsonrpc') {
$result[] = $server;
continue;
}
$host = $server['custom_ip'] ?? $server['host']; // 使用自定义ip
if (in_array($host, ['0.0.0.0', 'localhost'])) {
$host = $this->getInternalIp();
}
if (! filter_var($host, FILTER_VALIDATE_IP)) {
throw new InvalidArgumentException(sprintf('Invalid host %s', $host));
}
$port = $server['custom_port'] ?? $server['port']; // 使用自定义port
if (! is_numeric($port) || ($port < 0 || $port > 65535)) {
throw new InvalidArgumentException(sprintf('Invalid port %s', $port));
}
$port = (int) $port;
$result[$server['name']] = [$host, $port];
}
return $result;
}
}
在 \config\autoload\dependencies.php 中绑定
<?php
declare(strict_types=1);
return[
Hyperf\ServiceGovernance\Listener\RegisterServiceListener::class => App\Listener\RpcRegisterServiceListener::class,
];
在 .env 中添加配置项
RPC_CUSTOM_IP=192.168.67.128
RPC_CUSTOM_PORT=9503
在 \config\autoload\server.php 的 jsonrpc 中添加配置
[
'name' => 'jsonrpc',
'type' => Server::SERVER_BASE,
.
.
//自定义 ip 和 port
'custom_ip' => env('RPC_CUSTOM_IP',''),
'custom_port' => env('RPC_CUSTOM_PORT',''),
.
.
]
注意二 :
在 settings 的配置项中,开启的粘包处理,如果使用了 consul ,切记不能使用 包头 + 包体 的方式,protocol中的配置也只能是jsonrpc,不能是 jsonrpc-tcp-length-check 只能使用 eof 的方式来处理粘包,不然consul无法接收到请求的数据,则会抛出异常 Cannot select any node from load balancer.
如果使用nodes的方式,则可以使用包头 + 包体 的方式
'settings' => [
//'open_eof_split' => true,
'open_eof_check' => true,
'package_eof' => "\r\n",
'package_max_length' => 1024 * 1024 * 20,
//使用consul不能使用,nodes的方式可以使用
/*'open_length_check' => true,
'package_length_type' => 'N',
'package_length_offset' => 0,
'package_body_offset' => 4,
'package_max_length' => 1024 * 1024 * 20,*/
],
1 . 4 定义代理消费者类
在 \config\autoload\services.php 中,定义rpc服务配置,主要针对生产者把配置数据注册进consul中
<?php
return [
'enable' => [
// 开启服务发现
'discovery' => true,
// 开启服务注册
'register' => true,
],
'consumers' => [],
'providers' => [],
// 服务驱动相关配置
'drivers' => [
'consul' => [
'uri' => 'http://192.168.67.130:8500',
'token' => '',
'check' => [
'deregister_critical_service_after' => '90m',
'interval' => '1s',
],
],
],
];
到此,服务端的配置基本就弄好了,现在启动服务,正常运作,consul就会查询到注册的服务
2 . 1 client 客户端 (192.168.67.130)
2 . 1 . 1 拿到 server端定义的接口
创建 \app\JsonRpc\CalculatorServiceInterface.php
<?php
declare(strict_types=1);
namespace App\JsonRpc;
interface CalculatorServiceInterface
{
public function add(int $v1, int $v2): int;
public function getIpList(int $numbers);
}
2 . 1 . 2 客户端调用
<?php
declare(strict_types=1);
namespace App\Controller;
use Hyperf\Utils\ApplicationContext;
use App\JsonRpc\MathValue;
use Hyperf\Di\Annotation\Inject;
use App\JsonRpc\CalculatorServiceInterface;
class IndexController extends AbstractController
{
use ApiResponse;
public function add()
{
try{
$client = ApplicationContext::getContainer()->get(CalculatorServiceInterface::class);
$data = $client->add(1,3);
return $data;
}catch (\Exception $e){
return $e->getMessage();
}
}
public function getIpList()
{
try{
$client = ApplicationContext::getContainer()->get(CalculatorServiceInterface::class);
$result = $client->getIpList(30);
return $this->success($result);
}catch (\Exception $e){
return $e->getMessage();
}
}
}
2 . 1 . 3 定义代理消费者类
在 \config\autoload\services.php 中,定义rpc服务配置,主要针对消费者从consul中发现配置
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
return [
'consumers' => [
// jsonrpc 通过consul中台进行请求
[
'name' => 'CalculatorService',
'service' => \App\JsonRpc\CalculatorServiceInterface::class,
'protocol' => 'jsonrpc', // 默认为jsonrpc-http 所需必须进行指定
//'protocol' => 'jsonrpc-tcp-length-check',
'registry' => [
'protocol' => 'consul',
'address' => 'http://192.168.67.130:8500', // 这里需要自己的consul地址
],
// 如果没有指定上面的 registry 配置,即为直接对指定的节点进行消费,通过下面的 nodes 参数来配置服务提供者的节点信息
'nodes' => [
['host' => '192.168.67.128', 'port' => 9503],
],
// 配置项,会影响到 Packer 和 Transporter
'options' => [
'connect_timeout' => 5.0,
'recv_timeout' => 5.0,
'settings' => [
// 根据协议不同,区分配置
//'open_eof_split' => true,
'open_eof_check' => true,
'package_eof' => "\r\n",
/*'open_length_check' => true,
'package_length_type' => 'N',
'package_length_offset' => 0,
'package_body_offset' => 4,
'package_max_length' => 1024 * 1024 * 20,*/
],
// 重试次数,默认值为 2,收包超时不进行重试。暂只支持 JsonRpcPoolTransporter
'retry_count' => 2,
// 重试间隔,毫秒
'retry_interval' => 100,
// 使用多路复用 RPC 时的心跳间隔,null 为不触发心跳
'heartbeat' => 30,
// 当使用 JsonRpcPoolTransporter 时会用到以下配置
'pool' => [
'min_connections' => 1,
'max_connections' => 32,
'connect_timeout' => 10.0,
'wait_timeout' => 3.0,
'heartbeat' => -1,
'max_idle_time' => 60.0,
],
],
],
]
];
注意 :在 settings 的配置项中,开启的粘包处理,如果使用了 consul ,切记不能使用 包头 + 包体 的方式,protocol中的配置也只能是jsonrpc,不能是 jsonrpc-tcp-length-check 只能使用 eof 的方式来处理粘包,不然consul无法接收到请求的数据,则会抛出异常 Cannot select any node from load balancer.
如果使用nodes的方式,则可以使用包头 + 包体 的方式
2 . 1 . 4 请求客户端
客户端首先去 consul 中获取到对应的 ip 和 port,在拿 ip 和 port 去远程调用 另一台服务器的方法,并返回到客户端中