上个月入职了一家新公司,技术栈用的B站的 Kratos ,发现这个框架为了微服务而封装了很多东西,最主要的一块就是它会同时启动 http 服务跟 gRPC 服务,外部访问通过 RESTful API 方式请求 http 接口进行通信,内部访问通过 gRPC 远程过程调用来进行通信,一份 proto 文件生成两份服务定义,这种方式还是挺新颖的。

因 gRPC 可实现多语言通信,突发奇想用 Hyperf 的 gRPC-client 来请求 Kratos 的 gRPC-server ,通过实践发现还是行得通的,具体步骤如下:

首先利用 Docker 容器来搭建 Kratos 服务的 proto 文件生成环境

docker pull golang:1.20.6
docker run -itd -v D:\project:/go --name golang golang:1.20.6

docker ps
docker exec -it Golang容器ID /bin/bash

进入容器后,因为用的是 Go 官方构建的 Debian 发行版,需要通过 apt 来安装 protoc 及设置 Go 的环境变量

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct

apt-get update
apt-get install protobuf-compiler

接下来开始准备安装 Kratos 命令行工具生成项目

go install github.com/go-kratos/kratos/cmd/kratos/v2@latest
kratos new demo

运行完毕后,就在当前目录中生成了一个叫 demo 的 Kratos 项目,接着我们安装一系列的 protoc-gen-go 工具及 wire 依赖注入工具

通过查看项目中的 Makefile 文件我们知道,Kratos 集成了一个 make init 命令来进行这一步骤,然后可以使用 make all 命令来生成 api 及 config 的 pb.go 文件及构建执行环境

make init
make all

因为我使用 Win10 环境已经安装过 Go 及 Goland,所以不需要在容器中进行启动服务了。

先将默认生成的 api/helloworld/v1/greeter.proto 中的 SayHello 接口去除掉 option 定义,方便 gRPC 直接调用

syntax = "proto3";

package helloworld.v1;

import "google/api/annotations.proto";

option go_package = "demo/api/helloworld/v1;v1";
option java_multiple_files = true;
option java_package = "dev.kratos.api.helloworld.v1";
option java_outer_classname = "HelloworldProtoV1";

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {
    /*option (google.api.http) = {
      get: "/helloworld/{name}"
    };*/
  }
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

再通过配置 Goland 的运行/调试配置来解决本地服务启动

kratos-goland-build-config

启动服务后监听 http 端口 8000 及 gRPC 端口 9000

接下来再利用 Docker 容器来搭建 Hyperf 服务,新起一个命令行窗口

docker pull hyperf/hyperf:8.2-alpine-v3.18-swoole
docker run --name hyperf-grpc-demo -v D:\project:/data/project -p 9501:9501 -it --privileged -u root --entrypoint /bin/sh hyperf/hyperf:8.2-alpine-v3.18-swoole

docker ps
docker exec -it Hyperf容器ID /bin/bash

直接利用已经集成的 composer 环境来创建 Hyperf 项目

composer create-project hyperf/hyperf-skeleton hyperf-grpc-demo

途中有命令行提示,一路回车下去,碰到提示 Which RPC protocol do you want to use ? 选择 gRPC 选项

Hyperf 项目创建好之后,需要切换到 Golang 容器中使用 protoc 来生成 gRPC 的 PHP 文件

cd /go/demo
protoc --proto_path=./api --proto_path=./third_party --php_out=../hyperf-grpc-demo/grpc/ --openapi_out=fq_schema_naming=true,default_response=false:. ./api/helloworld/v1/greeter.proto

可将 protoc 命令配置到 Makefile 中进行便捷操作

至此 PHP 对应的 gRPC 文件也生成好了,需要调整 Hyperf 项目中的 composer.json 文件自动加载配置

"autoload": {
    "psr-4": {
        "App\\": "app/",
        "GPBMetadata\\": "grpc/GPBMetadata",
        "Helloworld\\": "grpc/Helloworld"
    },
    "files": [
    ]
},

调整完后记得执行 composer dump-autoload 命令让新的自动加载生效

接着开始编写 gRPC-client 的请求代码,这里我们直接调整 Hyperf 默认的 app/Controller/IndexController.php

在开始编写代码之前,先查看宿主机的局域网IP地址,新起一个命令行窗口执行 ipconfig ,记录下 192.168.XXX.XXX 这段IP地址

namespace App\Controller;

use App\Grpc\GreeterClient;
use Helloworld\V1\HelloRequest;

class IndexController extends AbstractController
{
    public function index()
    {
        // 这个client是协程安全的,可以复用
        // 注意这里的IP地址需要填写为刚刚通过 ipconfig 记录下的局域网IP地址
        // 端口为 Kratos 指定的 gRPC 服务 9000
        $client = new GreeterClient('192.168.1.20:9000', [
            'credentials' => null,
        ]);

        $user = $this->request->input('user', 'Hyperf');
        $method = $this->request->getMethod();

        $request = new HelloRequest();
        $request->setName($user);

        [$reply, $status] = $client->SayHello($request);

        $message = $reply->getMessage();

        return [
            'method' => $method,
            'memory_get_usage' => memory_get_usage(true),
            'message' => $message,
        ];
    }
}

再将 gRPC-client 调用过程完善

namespace App\Grpc;

use Helloworld\V1\HelloReply;
use Helloworld\V1\HelloRequest;
use Hyperf\GrpcClient\BaseClient;

class GreeterClient extends BaseClient
{
    public function SayHello(HelloRequest $request)
    {
        return $this->_simpleRequest(
            '/helloworld.v1.Greeter/SayHello',
            $request,
            [HelloReply::class, 'decode']
        );
    }
}

最后切换至 Hyperf 容器中将 Hyperf 的服务进行启动

php bin/hyperf.php start

我们打开浏览器,直接访问 http://127.0.0.1:9501

如果看到返回的 json 串中提示 "Hello Hyperf" ,并且 Kratos 的服务日志显示

INFO ts=2023-07-29T16:46:14+08:00 caller=biz/greeter.go:44 service.id=Fantasticbin-PC service.name= service.version= trace.id= span.id= msg=CreateGreeter: Hyperf

gRPC 调用大功告成!