gRPC整理

208 阅读3分钟

image.png

1.gRPC通信的第一步是定义IDL,即我们的接口文档(后缀为.proto)

2.第二步是编译proto文件,得到存根(stub)文件,即上图深绿色部分。

3.第三步是服务端(gRPC Server)实现第一步定义的接口并启动,这些接口的定义在存根文件里面

4.最后一步是客户端借助存根文件调用服务端的函数,虽然客户端调用的函数是由服务端实现的,但是调用起来就像是本地函数一样。

以上就是gRPC的基本流程,从图中还可以看出,由于我们的proto文件的编译支持多种语言(Go、Java、Python等),所以gRPC也是跨语言的。

gRPC简单实践

一般来讲,实现一个gRPC服务端和客户端,主要分为这几步:

  • 1.安装 protobuf 依赖
  • 2.编写 proto 文件(IDL)
  • 3.编译 proto 文件(生成stub文件)
  • 4.编写server端,实现我们的接口
  • 5.编写client端,测试我们的接口

1.安装 protobuf 依赖

# 1.安装protoc
$ brew install protoc

# 2.检查安装是否成功
$ protoc --version
libprotoc 3.7.1

# 3.安装编译插件
$ export GO111MODULE=on  # 开启Go Module
$ go get google.golang.org/protobuf/cmd/protoc-gen-go \
         google.golang.org/grpc/cmd/protoc-gen-go-grpc

2.编写 proto 文件

syntax = "proto3";

package greeter.srv;

option go_package = "proto/greeter";

service Greeter {
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}

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

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

这里使用了官方文档的例子,算是比较简单的一个例子。首先第一行syntax = "proto3" 表明了我们使用的是proto3,而不是proto2,proto2是之前的版本。

而后面的service Greeter则表示定义一个Greeter的服务,这个服务下有SayHello接口,这个接口的请求是HelloRequest结构体,返回是HelloReply接口体(前面提到过API本质就是Request+Response!)。

同一份proto文件可以定义多个服务(不太建议,除非有关联),每个服务下面可以定义多个接口。

注:更多protobuf的语法,可以参考网上的其他教程

3.编译 proto 文件

编译命令是:

protoc --go_out=. --go_opt=paths=source_relative
--go-grpc_out=. --go-grpc_opt=paths=source_relative
proto/greeter/greeter.proto

注:proto文件的编译是我最想吐槽protobuf的一个点,首先是语法晦涩难懂,比如上述的paths=source_relative,其次是stub文件的存放位置,也需要多次尝试才能写入到我们想要的位置;最后是protobuf的各种插件,这些插件没办法输出版本,在使用的时候经常因为版本的不同遇到一些奇奇怪怪的问题。

4.server端的编写

server端的编写如下:

// greeter_server.go
type server struct {
}

// 实现我们的接口
func (s *server) SayHello(ctx context.Context, req *greeter.HelloRequest) (rsp *greeter.HelloReply, err error) {
 rsp = &greeter.HelloReply{Message: "Hello " + req.Name}
 return rsp, nil
}

func main() {
 listener, err := net.Listen("tcp", ":52001")
 if err != nil {
  log.Fatalf("failed to listen: %v", err)
 }
 // gRPC 服务器
 s := grpc.NewServer()
 // 将服务器与处理器绑定
 greeter.RegisterGreeterServer(s, &server{})

 //reflection.Register(s)
 fmt.Println("gRPC server listen in 52001...")
 err = s.Serve(listener)
 if err != nil {
  log.Fatalf("failed to serve: %v", err)
 }
}

前面在proto文件中,我们只是定义了SayHello接口,并没有实现,所以当我们要实现一个server端,第一步就需要实现我们的接口。最后就是一个服务器的基本流程(不管是HTTP,还是gRPC),即声明一个服务器实例、绑定处理器以及最后的运行。

5.client端的编写

// greeter_client.go
func main() {
 // 发起连接,WithInsecure表示使用不安全的连接,即不使用SSL
 conn, err := grpc.Dial("127.0.0.1:52001", grpc.WithInsecure())
 if err != nil {
  log.Fatalf("connect failed: %v", err)
 }

 defer conn.Close()

 c := greeter.NewGreeterClient(conn)

 ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
 defer cancel()

    // 虽然SayHello由远程服务端实现,但调用起来就像一个本地函数一样
 r, err := c.SayHello(ctx, &greeter.HelloRequest{Name: "World"})
 if err != nil {
  log.Fatalf("call service failed: %v", err)
 }
 fmt.Println("call service success: ", r.Message)
}

client端的实现也比较简单,就是发起连接、创建客户端实例、调用方法,以及得到结果。