Hyperf微服务——五、JsonRpc远程调用

一、JsonRpc请求的实现、

调用链从上到下

namespace App\JsonRpc;
//请求入口 通过容器获取接口对象
class IndexController extends AbstractController
{
    public function index()
    {
        $user_info = ApplicationContext::getContainer()->get(UserInfoInterface::class);
        return $user_info->getUserInfoById(1);
    }
}


namespace App\JsonRpc;
//服务启动,自动生成服务提供者的接口代理文件
class UserInfoInterface_7be0c5bab4e9d0b2671b5482e5fa29f4 extends \Hyperf\RpcClient\Proxy\AbstractProxyService implements UserInfoInterface
{
    public function getUserInfoById($id)
    {
    	//通过抽象代理文件的__call方法进行调用
        return $this->client->__call(__FUNCTION__, func_get_args());
    }
}


namespace Hyperf\RpcClient;
//rpc客户端的底层实现
class ServiceClient extends AbstractServiceClient
{
    /**
     * @var MethodDefinitionCollectorInterface
     */
    protected $methodDefinitionCollector;

    /**
     * @var string
     */
    protected $serviceInterface;

    /**
     * @var NormalizerInterface
     */
    private $normalizer;

    public function __construct(ContainerInterface $container, string $serviceName, string $protocol = 'jsonrpc-http', array $options = [])
    {
        $this->serviceName = $serviceName;
        $this->protocol = $protocol;
        $this->setOptions($options);
        parent::__construct($container);
        $this->normalizer = $container->get(NormalizerInterface::class);
        $this->methodDefinitionCollector = $container->get(MethodDefinitionCollectorInterface::class);
    }

	//真正的rpc调用方法
    protected function __request(string $method, array $params, ?string $id = null)
    {
        if ($this->idGenerator instanceof IdGeneratorInterface && ! $id) {
            $id = $this->idGenerator->generate();
        }
        //生成调用地址并发送消息体到服务提供者
        $response = $this->client->send($this->__generateData($method, $params, $id));
        if (! is_array($response)) {
            throw new RequestException('Invalid response.');
        }

        $response = $this->checkRequestIdAndTryAgain($response, $id);
        if (array_key_exists('result', $response)) {
            $type = $this->methodDefinitionCollector->getReturnType($this->serviceInterface, $method);
            if ($type->allowsNull() && $response['result'] === null) {
                return null;
            }

			//消息体解码
            return $this->normalizer->denormalize($response['result'], $type->getName());
        }

        if ($code = $response['error']['code'] ?? null) {
            $error = $response['error'];
            // Denormalize exception.
            $class = Arr::get($error, 'data.class');
            $attributes = Arr::get($error, 'data.attributes', []);
            if (isset($class) && class_exists($class) && $e = $this->normalizer->denormalize($attributes, $class)) {
                if ($e instanceof \Throwable) {
                    throw $e;
                }
            }

            // Throw RequestException when denormalize exception failed.
            throw new RequestException($error['message'] ?? '', $code, $error['data'] ?? []);
        }

        throw new RequestException('Invalid response.');
    }


	//对外部开放的调用方法
    public function __call(string $method, array $params)
    {
        return $this->__request($method, $params);
    }

    protected function setOptions(array $options): void
    {
        $this->serviceInterface = $options['service_interface'] ?? $this->serviceName;

        if (isset($options['load_balancer'])) {
            $this->loadBalancer = $options['load_balancer'];
        }
    }
}


namespace Hyperf\RpcClient;
//打包消息为json格式并请求服务端
class Client
{
    /**
     * @var null|PackerInterface
     */
    private $packer;

    /**
     * @var null|TransporterInterface
     */
    private $transporter;

	//消息体发送到服务提供者
    public function send($data)
    {
        if (! $this->packer) {
            throw new InvalidArgumentException('Packer missing.');
        }
        if (! $this->transporter) {
            throw new InvalidArgumentException('Transporter missing.');
        }
        //进行消息体编码并发送
        $packer = $this->getPacker();
        $packedData = $packer->pack($data);
        $response = $this->getTransporter()->send($packedData);
        return $packer->unpack((string) $response);
    }

	//接收服务提供者返回的消息体
    public function recv()
    {
        $packer = $this->getPacker();
        $response = $this->getTransporter()->recv();
        return $packer->unpack((string) $response);
    }

    public function getPacker(): PackerInterface
    {
        return $this->packer;
    }

    public function setPacker(PackerInterface $packer): self
    {
        $this->packer = $packer;
        return $this;
    }

    public function getTransporter(): TransporterInterface
    {
        return $this->transporter;
    }

    public function setTransporter(TransporterInterface $transporter): self
    {
        $this->transporter = $transporter;
        return $this;
    }
}


namespace Hyperf\JsonRpc;
//jsosrpc发送消息到服务提供者并接收返回
class JsonRpcTransporter implements TransporterInterface
{
    use RecvTrait;

    /**
     * @var null|LoadBalancerInterface
     */
    private $loadBalancer;

    /**
     * If $loadBalancer is null, will select a node in $nodes to request,
     * otherwise, use the nodes in $loadBalancer.
     *
     * @var Node[]
     */
    private $nodes = [];

    /**
     * @var float
     */
    private $connectTimeout = 5;

    /**
     * @var float
     */
    private $recvTimeout = 5;

    /**
     * @var array
     */
    private $config = [];

    public function __construct(array $config = [])
    {
        $this->config = array_replace_recursive($this->config, $config);

        $this->recvTimeout = $this->config['recv_timeout'] ?? 5.0;
        $this->connectTimeout = $this->config['connect_timeout'] ?? 5.0;
    }

	/**
	 * 基于swoole协程客户端发送方法实现消息体发送
	 * swoole文档 https://wiki.swoole.com/#/coroutine_client/client?id=send
	 **/
    public function send(string $data)
    {
        $client = retry(2, function () use ($data) {
            $client = $this->getClient();
            if ($client->send($data) === false) {
                if ($client->errCode == 104) {
                    throw new RuntimeException('Connect to server failed.');
                }
            }
            return $client;
        });

        return $this->recvAndCheck($client, $this->recvTimeout);
    }

	/**
	 * 基于swoole协程客户端接收方法实现消息体发送
	 * swoole文档 https://wiki.swoole.com/#/coroutine_client/client?id=recv
	 **/
    public function recv()
    {
        $client = $this->getClient();

        return $this->recvAndCheck($client, $this->recvTimeout);
    }

	/**
	 * 基于swoole协程客户端连接方法实现远程服务器连接
	 * swoole文档 https://wiki.swoole.com/#/coroutine_client/client?id=connect
	 **/
    public function getClient(): SwooleClient
    {
        $class = spl_object_hash($this) . '.Connection';
        if (Context::has($class)) {
            return Context::get($class);
        }

        return Context::set($class, retry(2, function () {
            $client = new SwooleClient(SWOOLE_SOCK_TCP);
            $client->set($this->config['settings'] ?? []);
            $node = $this->getNode();
            $result = $client->connect($node->host, $node->port, $this->connectTimeout);
            if ($result === false && ($client->errCode == 114 or $client->errCode == 115)) {
                // Force close and reconnect to server.
                $client->close();
                throw new RuntimeException('Connect to server failed.');
            }
            return $client;
        }));
    }

    public function getLoadBalancer(): ?LoadBalancerInterface
    {
        return $this->loadBalancer;
    }

    public function setLoadBalancer(LoadBalancerInterface $loadBalancer): TransporterInterface
    {
        $this->loadBalancer = $loadBalancer;
        return $this;
    }

    /**
     * @param \Hyperf\LoadBalancer\Node[] $nodes
     */
    public function setNodes(array $nodes): self
    {
        $this->nodes = $nodes;
        return $this;
    }

    public function getNodes(): array
    {
        return $this->nodes;
    }

    /**
     * If the load balancer is exists, then the node will select by the load balancer,
     * otherwise will get a random node.
     */
    private function getNode(): Node
    {
        if ($this->loadBalancer instanceof LoadBalancerInterface) {
            return $this->loadBalancer->select();
        }
        return $this->nodes[array_rand($this->nodes)];
    }
}


//接收服务提供者返回的消息体并检查远程连接可用
trait RecvTrait
{
    /**
     * @param Client|RpcConnection $client
     * @param float $timeout
     */
    public function recvAndCheck($client, $timeout)
    {
        $data = $client->recv((float) $timeout);
        if ($data === '') {
            // RpcConnection: When the next time the connection is taken out of the connection pool, it will reconnecting to the target service.
            // Client: It will reconnecting to the target service in the next request.
            $client->close();
            throw new RecvException('Connection is closed. ' . $client->errMsg, $client->errCode);
        }
        if ($data === false) {
            $client->close();
            throw new RecvException('Error receiving data, errno=' . $client->errCode . ' errmsg=' . swoole_strerror($client->errCode), $client->errCode);
        }

        return $data;
    }
}

二、定义服务消费者

如果我们的调用场景是 用户服务 调用 订单服务 ,那么首先需要按照上一篇文章 Hyperf微服务——四、第一个微服务的搭建 创建一个订单服务, 然后在用户服务按照如下步骤定义订单服务的服务消费者配置。

1.配置服务消费者文件

通过在 config/autoload/services.php配置文件内进行一些简单的配置,即可通过动态代理自动创建消费者类。

<?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 [
    'enable' => [
        'discovery' => true,
        'register' => true,
    ],
    //通过闭包函数完成多个服务消费者的定义
    'consumers' => value(function () {
        $consumers = [];
        //统一包含定义服务消费者的关系文件
        $services = include __DIR__ . '/service_consumers.php';
        //循环处理服务消费者的配置信息
        foreach ($services as $name => $interface) {
            $consumers[] = [
                'name' => $name,
                'service' => $interface,
                'protocol' => 'jsonrpc',
                'load_balancer' => 'random',
                'registry' => [
                    'protocol' => env('PROTOCOL', 'consul'),
                    'address' => env('CONSUL_URI', 'http://127.0.0.1:8500'),
                ],
                'options' => [
                    'connect_timeout' => 5.0,
                    'recv_timeout' => 5.0,
                    'settings' => [
                        // 根据协议不同,区分配置
                        'open_eof_split' => true,
                        'package_eof' => "\r\n",
                        // 'open_length_check' => true,
                        // 'package_length_type' => 'N',
                        // 'package_length_offset' => 0,
                        // 'package_body_offset' => 4,
                    ],
                    // 重试次数,默认值为 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,
                    ],
                ],
            ];
        }
        return $consumers;
    }),
    'providers' => [],
    'drivers' => [
        'consul' => [
            'uri' => env('CONSUL_URI', 'http://127.0.0.1:8500'),
            'token' => '',
            'check' => [
                'deregister_critical_service_after' => '90m',
                'interval' => '1s',
            ],
        ],
    ],
];

2.配置服务消费者绑定关系

创建配置文件 config/autoload/service_consumers.php定义服务消费者的关系,如果所有服务统一使用consul为注册中心,配置文件 config/autoload/services.php的其他项不需要修改。

<?php
//定义服务消费者配置文件,用来快速注册服务消费者
return [
    //用户服务
    'OrderService' => App\JsonRpc\OrderInterface::class,
    //...
];

3. 注入接口文件

这样便可以通过注入 OrderInterface 接口来使用客户端了
复制服务提供者订单服务的OrderInterface文件到用户服务的JsonRpc目录,如下如所示,启动用户服务即可进行RPC远程调用。
在这里插入图片描述

4.封装rpc调用方法

可以通过封装简易调用方法,以方便业务开发者使用,不需要关注调用逻辑。

	//封装简易rpc调用,
   public function rpc($interface, $function, $args)
    {
        $interface = (new \ReflectionClass($interface))->getName();
        $data = ApplicationContext::getContainer()->get($interface)->$function($args);
        return $data;
    }

	//调用demo
	$this->rpc('\App\JsonRpc\OrderInterface','listOrder', []);

至此,一个简单的微服务RPC调用已经实现了。下面就可以根据实际的业务场景进行深度开发了。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Justin-D

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值