hyperf jsonRpc 微服务基本配置与使用

swoole的成熟,让原本和微服务八杆子打不上关系的php也能分一杯羹了,不然以php的并发性能,真的让人绝望。

Posted by 昆山吴彦祖 on 2021.08.23

抽空测试了下hyperf的微服物,基于docker容器,整体来说架构还是很清晰,不需要用户对微服物架构做过多的设置与了解。

顺便一说,hyperf 默认的微服务是基于http协议的,但其实大众认可的还是走tcp传输层协议的比较多,毕竟多了一层封装|解析 和多余的http头文件,对于注重性能的微服物,是没啥意义的。


拉取docker镜像,创建docker容器就不写了。

大致上创建3个容器,1个服务提供者的容器(hyperf),1个服务消费者的容器(hyperf),1个consul容器(非必需,作为服务中心)

官方文档参考


1 配置服务提供者(用户服务)

修改server.php,servers中新增 jsonrpc部分,因为用户中心可能需要http对外服务,所以保留了http,新增一个额外的端口给jsonRpc(端口记得在docker容器绑定到服务器)

'servers' => [
        [
            'name' => 'http',
            'type' => Server::SERVER_HTTP,
            'host' => '0.0.0.0',
            'port' => 9501,
            'sock_type' => SWOOLE_SOCK_TCP,
            'callbacks' => [
                SwooleEvent::ON_REQUEST => [Hyperf\HttpServer\Server::class, 'onRequest'],
            ],
        ],
        [
            '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'],
            ],
        ],
    ],


新增services.php(配置服务自动发布,非必需)

 [
        'discovery' => true,
        'register' => true,
    ],
    'consumers' => [],
    'providers' => [],
    'drivers' => [
        'consul' => [
            'uri' => 'http://172.18.0.7:8500',
            'token' => '',
        ],
    ],
];

执行命令创建consul组件的配置文件,并配置其中的ip为我们consul容器的ip

php bin/hyperf.php vendor:publish hyperf/consul


创建服务UserService 和对应接口类

namespace App\JsonRpc;

interface UserServiceInterface
{
    public function login(string $username, string $password): array;
}
namespace App\JsonRpc;

use Hyperf\RpcServer\Annotation\RpcService;

/**
 * 注意,如希望通过服务中心来管理服务,需在注解内增加 publishTo 属性
 * @RpcService(name="UserService", protocol="jsonrpc-http", server="jsonrpc-http",publishTo="consul")
 */
class UserService implements UserServiceInterface
{
    public function login(string $username, string $password): array
    {
        // 这里是服务方法的具体实现
        if($username == 'test' && $password == 123456){
            return [
                'status'=>200,
            ];
        }else{
            return [
                'status'=>500,
                'msg'=>'账户密码错误'
            ];
        }
    }
}


最后记得启动服务提供者,到此为止服务提供者配置好了


2配置服务消费者并消费服务

新增services.php(配置服务消费者)

return [
    // 此处省略了其它同层级的配置
    'consumers' => [
        [
            // name 需与服务提供者的 name 属性相同
            'name' => 'UserService',
            // 服务接口名,可选,默认值等于 name 配置的值,如果 name 直接定义为接口类则可忽略此行配置,如 name 为字符串则需要配置 service 对应到接口类
            'service' => \App\JsonRpc\UserServiceInterface::class,
            // 对应容器对象 ID,可选,默认值等于 service 配置的值,用来定义依赖注入的 key
            'id' => \App\JsonRpc\UserServiceInterface::class,
            // 服务提供者的服务协议,可选,默认值为 jsonrpc-http
            // 可选 jsonrpc-http jsonrpc jsonrpc-tcp-length-check
            'protocol' => 'jsonrpc-http',
            // 负载均衡算法,可选,默认值为 random
            'load_balancer' => 'random',
            // 这个消费者要从哪个服务中心获取节点信息,如不配置则不会从服务中心获取节点信息
            'registry' => [
                'protocol' => 'consul',
                'address' => 'http://172.18.0.7:8500',
            ],
            // 如果没有指定上面的 registry 配置,即为直接对指定的节点进行消费,通过下面的 nodes 参数来配置服务提供者的节点信息
            'nodes' => [
                ['host' => '127.0.0.1', 'port' => 9504],
            ],
            // 配置项,会影响到 Packer 和 Transporter
            '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,
                // 当使用 JsonRpcPoolTransporter 时会用到以下配置
                'pool' => [
                    'min_connections' => 1,
                    'max_connections' => 32,
                    'connect_timeout' => 10.0,
                    'wait_timeout' => 3.0,
                    'heartbeat' => -1,
                    'max_idle_time' => 60.0,
                ],
            ],
        ]
    ],
];


因为我们配置中使用的是自动创建消费类,所以只需要再创建一个消费类的接口类即可

namespace App\JsonRpc;

interface UserServiceInterface
{
    public function login(string $username, string $password): array;
}

消费服务

namespace App\Controller;
use Hyperf\Di\Annotation\Inject;
use \App\JsonRpc\UserServiceInterface;
use Hyperf\HttpServer\Contract\RequestInterface;

class IndexController extends AbstractController
{
    /**
     * @Inject()
     * @var UserServiceInterface
     */
    private $UserService;
    
    public function login(RequestInterface $request)
    {
        return $this->UserService->login($request->query('username'),$request->query('password'));
    }
}

最后一样启动项目。

理论上到这一部就完成了基本的微服务架构的配置与使用了,当然还有很多如并发配置等细节需要实际使用中去注意。

最后说下初步使用的感受:

由于swoole提供的高性能协程,使得php的并发性能指数增长,我的垃圾单核心2G云服务器,跑个300Rps毫无压力。
这也使得对于并发天生有一定的性能要求的微服务架构能够通过php语言来实现了。

微服物架构(rpc远程过程调用) 和我们常用的基于api的多服务串接最大的区别
1、api通常基于http请求,rpc通常基于tcp请求,所以性能更优异。当然不绝对,google的grpc 和hyperf默认支持的都是基于http请求的rpc.
2、rpc模式,服务提供方和使用方都需要使用rpc框架实现服务的注册和发现,管理等等。 api模式则不需要,需要的是定义好各种api和参数。通用性更强。
3、rpc模式,我们所有的服务提供方和消费方都需要遵循同样的interface接口文件去进行服务使用,更适合团队协作,而且提供方也更容易在功能未实现前提供模拟实现,方便双方功能测试的解耦。

综合而言,他们实现的目标是相似的,广义上基于http请求的api模式实现的也属于rpc.
他们的区别更像是设计模式上的区别而非协议上的。rpc是牺牲了通用性、可读性来换取性能的优势。分场合使用即可。



微服务