基于Hyperf下json-RPC的Demo:

https://github.com/ifconfigure/hyperf-rpc-demo

为什么是RPC?而不是HTTP

1、简单来说,RPC没有那么多HTTP的Request 和Response Body 字段,比如:content-typeuser-agentaccept-languagecontent-encodingexpires 等字段。往往这些字段就占了报文的70%内容,如果请求次数过于频繁,可想而知对于性能和网络开销有多大。

2、而在内部服务器间相互通讯,我们完全可以减少握手次数

  • 使用HTTP2.0,HTTP2.0可以在一个连接上发送多次数据,相比目前的http1.1模式,它还是非阻塞的
  • 使用基于TCP的RPC协议,长连接传输数据

基于以上,微服务间使用HTTPv1.1的服务器间通讯并不是最好的方案,这就是为什么很多PHP框架基于PHP-FPM不适合做微服务的原因,因为PHP-FPM每个请求都会产生一个新的Woker进程,如果每个Worker都跑去与微服务建立HTTP1.1连接,那其实没有意义

为什么需要Consul?

consul是什么?为什么需要服务发现?看图就知道了

20200404174808701024x845.png

我在Server1部署了一套HTTP服务(服务消费者)对外提供接口,但是它同时也依赖Server2的服务(服务提供者),Server1不需要知道Server2的地址,只需要知道Consul这个服务者中心就行了,它会帮我注册所有的服务

这在现实中举个例子就是,一个大公司的Team A在做HTTP业务核心功能,Team B做了很多服务轮子,那Team A总不能每次开发都跑去Team B问:“你们的地址是多少啊?端口是多少啊? 服务名叫什么啊?告诉我好调用啊”。那如果要调用100个服务,岂不是Team A的人每次都要为了配置IP和端口忙死?

一、在Server 1搭建Consul

1、下载consul for linux,解压后就是一个可执行文件,直接mv到/usr/bin/consul

2、新建一个consul文件夹,在里面新建一个data文件夹和etc文件夹,data文件夹用于存放consul的数据文件,etc用于存放配置文件

3、切换到etc文件夹,新建一个web.json文件,内容如下:

{
    "service": {
        //这里写服务的ID,必须唯一
        "id": "CalculatorService",
        //这里写服务名称,一般也是ID名,非唯一
        "name": "CalculatorService",
        //注册服务,服务在哪台服务器上,就填写那台服务器IP,这里我填写server2的ip,因为是server2提供服务给server1调用
        "address": "server2.ip.server2.ip",
        //随便写
        "tags": [
            "webapi"
        ],
        //同上,服务也有端口,就填写那台服务器提供的端口,这里填写server2的端口
        "port": 9502
    }
}

4、回到consul目录,启动consul,其中-config-dir指定刚刚的配置目录,-data-dir指定存放数据的目录

consul agent -dev -ui -config-dir=./etc -data-dir=./data -client=0.0.0.0

client=0.0.0.0这里需要注意下,网上一般的都是127.0.0.1,如果写成127.0.0.1同样可以运行,但是没办法在外网访问consul的UI界面,所以这里改成0.0.0.0

5、在浏览器输入IP:8500,就可以看到consul安装成功

二、在Server 2安装并设置hyperf服务提供者

1、在app目录下新建RPC文件夹,先新建一个CalculatorServiceInterface接口文件,文件名CalculatorServiceInterface.php

<?php

namespace App\Rpc;

interface CalculatorServiceInterface
{
    public function add(int $a, int $b): int;
}

2、基于这个Interface,在该目录下添加一个实现方式的CalculatorService,文件名:CalculatorService.php

<?php


namespace App\Rpc;

use Hyperf\RpcServer\Annotation\RpcService;

/**
 * 注意,如希望通过服务中心来管理服务,需在注解内增加 publishTo 属性
 * @RpcService(name="CalculatorService", protocol="jsonrpc-http", server="jsonrpc-http" ,publishTo="consul")
 */
class CalculatorService implements CalculatorServiceInterface
{
    public function add(int $a, int $b) : int
    {
        return $a +$b;
    }
}

在这里使用了注解方式提供服务,name是服务名称,protocol指定使用了jsonrpc-http协议,这里加了publishTo选项,指定一个服务注册中心,因为之前已经搭建好了consul

3、打开config/autoload/server.php,在servers数组下,添加一个数组:

[
    'name' => 'jsonrpc-http',
    'type' => Server::SERVER_HTTP,
    'host' => '0.0.0.0',
    'port' => 9502,
    'sock_type' => SWOOLE_SOCK_TCP,
    'callbacks' => [
       SwooleEvent::ON_REQUEST => [\Hyperf\JsonRpc\HttpServer::class, 'onRequest'],
    ],
],

这样hyperf就会把代码里的服务,以9502端口发布出去并运行,这里的端口,其实就与上面的consul配置文件web.json文件对应起来,从而使到consul能发现9502这个服务

三、在Server 1安装并设置hyperf服务消费者

1、我这里新建一个项目来表示消费者consumer服务模,同样在app目录下新建RPC文件夹,先新建一个CalculatorServiceInterface接口文件,文件名CalculatorServiceInterface.php

<?php

namespace App\Rpc;

interface CalculatorServiceInterface
{
    public function add(int $a, int $b): int;
}

2、打开app/Controllers/IndexController.php

<?php
namespace App\Controller;

use App\Rpc\CalculatorServiceInterface;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Annotation\AutoController;

/**
 * Class IndexController
 * @package App\Controller
 * @AutoController()
 */
class IndexController extends AbstractController
{
    /**
     * //在这里我们注入了一个接口类,并直接调用接口的add方法,但在本项目并没有add 
方法的实现,真正的add方法在服务提供者里已经实现了,hyperf会帮我们找到相应服务并使用
     * @Inject()
     * @var CalculatorServiceInterface
     */
    private $calculatorService;

    public function index()
    {
        return $this->calculatorService->add(1,2);
    }
}

2、在config/autoload下新建services.php

<?php
return [
    'consumers' => [
        [
            // name 需与服务提供者的 name 属性相同
            'name' => 'CalculatorService',
            // 服务接口名,可选,默认值等于 name 配置的值,如果 name 直接定义为接口类则可忽略此行配置,如 name 为字符串则需要配置 service 对应到接口类
            'service' => \App\Rpc\CalculatorServiceInterface::class,


            // 这个消费者要从哪个服务中心获取节点信息,如不配置则不会从服务中心获取节点信息
            'registry' => [
                'protocol' => 'consul',
                //这里的address,表示consul搭建在那台服务器上,就填写哪台服务器的IP
                'address' => 'http://127.0.0.1:8500',
            ],
        ]
    ],
];

这样当请求进来的时候,IndexController就会注入Service,hyperf就会告知这个service应该要去services.php去找到这个服务(也就是consul)

3、把config/autoload/server.php里的port改成9503,待会通过9503访问index控制器,这里只是演示,现实中你可以随意更改成你想访问的端口

    'servers' => [
        [
            'name' => 'http',
            'type' => Server::SERVER_HTTP,
            'host' => '0.0.0.0',
            'port' => 9503,
            'sock_type' => SWOOLE_SOCK_TCP,
            'callbacks' => [
                SwooleEvent::ON_REQUEST => [Hyperf\HttpServer\Server::class, 'onRequest'],
            ],
        ],
    ],

四、跑起来

1、重新启动consul,如果之前启动过,先kill -9 consul的PID

consul agent -dev -ui -config-dir=./etc -data-dir=./data -client=0.0.0.0

2、切换到Server2的hyperf-provider,启动

php bin/hyperf.php start

3、切换到Server1的hyperf-consumer,启动

php bin/hyperf.php start

4、在浏览器打开Server1的HTTP 8500端口,查看consul界面,可以看到可以找到Server2下的hyperf的provider

20200405073935451024x343.png

5、在浏览访问一下Servier1的服务

20200405074555921024x253.png

可以看到Server1下的9503服务是正常的,它会访问Server1下的consul-8500中心,然后consul-8500中心会去访问Server2下的9502服务,再交还给Server1的9503