Google远程过程调用(Google Remote Procedure Call,gRPC)是基于 HTTP 2.0传输层协议和 protobuf 序列化协议进行开发承载的高性能开源RPC软件框架。

Grpc 为了实现跨语言特性,通过中间语言 IDL 进行了服务和数据类型的定义,写在 .proto 文件中。由 proto 代码生成工具根据 idl 文件生成各目标语言的 grpc 服务端和客户端代码。

正常调用 grpc 服务必须使用生成出的客户端代码进行调用。

当前业务需要使用网关对 grpc 请求进行代理,统一处理 grpc 客户端请求流量,若每次 grpc 服务新增或修改后,都需重新引入客户端调用代码重启网关,会异常麻烦加大开发运维成本。

这里使用 grpc 泛化调用解决该问题,只提供服务名、方法名和请求参数数据(json或protobuf格式),即可调用 grpc 服务。

完整代码示例:https://gitee.com/tangmingyou/grpc-generic

泛化调用应用场景

服务网关 gateway

客户端通常使用 http 和 websocket 请求进行访问,在网管层可以使用泛化调用技术将非grpc请求代理访问后端 grpc 服务
grpc generic gateway

  • 在 http 请求中,可以将 grpc 服务名和方法放置在 url 或请求体中,grpc 请求参数使用 json 请求体
  • 在 websocket 中,可选择直接使用 protobuf 序列化的请求体,减少请求流量大小

grpc 调试测试工具

如 apifox 类文档工具都提供了 grpc 服务调用功能

grcp-gateway grpc http 网关实现

grpccurlgrpc-client-cli 工具可使用命令行对 grpc 服务调用

grpc 反射服务

gRPC 提供了 grpc.reflection.v1alpha.ServerReflection 服务,在 Server 端添加后可以通过该服务获取所有服务的信息,包括服务定义,方法,属性等;

服务端开启反射服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main() {
server := grpc.NewServer()
// 注册反射服务
reflection.Register(server)
hello.RegisterHelloServer(server, new(HelloServer))

listen, err := net.Listen("tcp", ":8888")
if err != nil {
panic(err)
}

err = server.Serve(listen)
if err != nil {
panic(err)
}
}

示例:通过反射服务获取grpc服务端所有服务名

hider
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/reflection/grpc_reflection_v1"
"log"
)

func main() {
conn, err := grpc.Dial("127.0.0.1:8888", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}

clientV1 := grpc_reflection_v1.NewServerReflectionClient(conn)
stream, err := clientV1.ServerReflectionInfo(context.Background(), grpc.WaitForReady(true))
if err != nil {
log.Fatalf("cannot get ServerReflectionInfo: %v", err)
}
testListServices(stream)
}

func testListServices(stream grpc_reflection_v1.ServerReflection_ServerReflectionInfoClient) {
if err := stream.Send(&grpc_reflection_v1.ServerReflectionRequest{
MessageRequest: &grpc_reflection_v1.ServerReflectionRequest_ListServices{},
}); err != nil {
log.Fatalf("failed to send request: %v", err)
}
r, err := stream.Recv()
if err != nil {
// io.EOF is not ok.
log.Fatalf("failed to recv response: %v", err)
}

switch r.MessageResponse.(type) {
case *grpc_reflection_v1.ServerReflectionResponse_ListServicesResponse:
services := r.GetListServicesResponse().Service
for _, e := range services {
fmt.Println("service name: ", e.Name)
}
default:
log.Fatalf("ListServices = %v, want type <ServerReflectionResponse_ListServicesResponse>", r.MessageResponse)
}
}

反射获取 grpc 服务端服务名列表:

1
2
3
service name:  Hello
service name: grpc.reflection.v1.ServerReflection
service name: grpc.reflection.v1alpha.ServerReflection

grpc 泛化调用示例

客户端泛化调用代码,json 格式和 protobuf 格式参数的一元 RPC 泛化调用示例:

Server Stream、Client Stream、与双向流 RPC 通信模式可以根据需要参考实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

ctx := context.Background()
conn := getServerConn()
client := generic.NewGpcGenericClient(hello.Hello_ServiceDesc.ServiceName, conn)
if err := client.Init(ctx); err != nil {
logger.Error("grpc generic client init error: ", err)
return
}

// json 请求体调用
res1, err := client.InvokeUnaryJson(ctx, "SayHello", map[string]interface{}{"uid": "1001", "message": "unary json"})
if err != nil {
panic(err)
}
logger.Infof("generic InvokeUnaryJson res: %v", res1)

// protobuf bytes 请求体调用
req := &hello.HelloReq{Uid: "1001", Message: "proto req"}
protoBytes, err := proto.Marshal(req)
if err != nil {
panic(err)
}
res2, err := client.InvokeUnary(ctx, "SayHello", protoBytes)
if err != nil {
panic(err)
}
logger.Infof("generic InvokeUnary res: %v", res2)

完整代码示例:https://gitee.com/tangmingyou/grpc-generic

参考这位大神的 grpcall 反射库

核心实现使用社区开源的 grpc stub 反射访问库 jhump/protoreflect