Introducing gRPC

何为 RPC

RPC(Remote Procedure Call),远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC 协议假定某些传输协议的存在,如 TCP 或 UDP,为通信程序之间携带信息数据。在 OSI 网络通信模型中,RPC 跨越了传输层和应用层。RPC 使得开发包括网络分布式多程序在内的应用程序更加容易。

gRPC

gRPC 是由 Google 牵头开发,语言中立、平台中立、开源的远程过程调用系统。其遵循一般的 RPC 系统的特点设计:即定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。

安装与测试运行

开始之前…

在安装和使用 gRPC 之前,我们首先需要安装 Protocol Buffers,它是Google 开发的一种数据描述语言,类似于XML能够将结构化数据序列化,可用于数据存储、通信协议等方面。gRPC 依靠 Protocol Buffers 来定义服务。

安装

通过如下命令即可安装 Protocol Buffers 和 gRPC:(由于引用了 Google 的 Golang 库,您可能需要代理运行)

1
2
go get -u github.com/golang/protobuf/protoc-gen-go
go get -u google.golang.org/grpc

代理运行的方式:

1
http_proxy=http://address:port go get ...

运行样例

在我们刚才安装时获取的文件中,gRPC 为我们附带了其的运行样例,这个样例存储于 $GOPATH/src/google.golang.org/grpc/examples,下面我们以其中的 helloworld 为例进行讲解:

编译和运行

首先,我们进入样例工作目录。注意,请确保您已经正确配置了 GOPATH 环境变量。

1
cd $GOPATH/src/google.golang.org/grpc/examples/helloworld

在样例中,我们的文件都已经被正确编译为了 .pb.go 文件,实际上,这个文件是由服务定义文件 .proto 编译而来的。如果您想要自己编译,请使用我们通过 protoc-gen-go 引入的 protoc 编译器(请确保您的环境变量包含 \$GOPATH/bin)。

接下来,我们编译并运行生成的服务端和客户端 go 文件:

1
2
3
4
# Run on terminal 1: Server side
go run greeter_server/main.go
# Run on terminal 2: Client side
go run greeter_client/main.go

您会看到在客户端的终端界面中出现了 Greeting: Hello world,这表示我们的安装配置已经成功。

源码分析

gRPC 是如何实现通过 go 语言满足我们在 proto 中定义的服务呢,我们可以通过分析生成的 go 语言代码来初步了解:

虽然我们通过 greeter_server/main.gogreeter_client/main.go 启动的 gRPC 服务,但这个服务真正的核心是他们共同引用的 helloworld/helloworld.pb.go

首先我们看看 .proto 文件都写了些什么:

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
// 指定 Protocol Buffer 的语法版本
syntax = "proto3";

// 环境设置
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";

// 包名
package helloworld;

// 定义了 greeting 类,为其设计了一个自定义的方法,其接收一个 HelloRequest,返回一个 HelloReply
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// 定义了消息类型 HelloRequest,其中有一个自定义 string 属性 Name
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}

// 定义了消息类型 HelloReply,其中有一个自定义 string 属性 message
// The response message containing the greetings
message HelloReply {
string message = 1;
}

然后,我们抛开冗余的生成代码不看,helloworld.pb.go 实际上做了这几件事:

  • 定义 HelloRequest 数据结构

    1
    2
    3
    4
    5
    6
    type HelloRequest struct {
    Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
    XXX_NoUnkeyedLiteral struct{} `json:"-"`
    XXX_unrecognized []byte `json:"-"`
    XXX_sizecache int32 `json:"-"`
    }

    其中,Name 是我们在 .proto 中定义的字段。

  • 定义 HelloReply 数据结构

    1
    2
    3
    4
    5
    6
    type HelloReply struct {
    Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
    XXX_NoUnkeyedLiteral struct{} `json:"-"`
    XXX_unrecognized []byte `json:"-"`
    XXX_sizecache int32 `json:"-"`
    }

    其中,Message 是我们在 .proto 中定义的字段。

  • 定义数据结构操作方法

    1
    2
    3
    4
    5
    6
    7
    8
    func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
    out := new(HelloReply)
    err := c.cc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, opts...)
    if err != nil {
    return nil, err
    }
    return out, nil
    }

    除去一些基本的生成方法,这个方法定义了我们在 proto 中定义的方法,它允许客户端调用这个方法来向服务端发送一个 HelloRequest,并接受一个 HelloReply。

然后,我们就可以看到这些内容在服务端和客户端的使用了:

  • 服务端:

    服务端的 go 语言代码首先实现了我们在类文件中没有实现的定义:

    1
    2
    3
    4
    func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    log.Printf("Received: %v", in.Name)
    return &pb.HelloReply{Message: "Hello " + in.Name}, nil
    }

    这样在其收到一个 HelloRequest 的时候就会调用这个方法来发送一个 HelloReply 给客户端。

    然后,其监听给定端口,创建服务器对象,注册服务器并且将其绑定到端口上:

    1
    2
    3
    4
    5
    lis, err := net.Listen("tcp", port)
    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})
    reflection.Register(s)
    err := s.Serve(lis)
  • 客户端:

    客户端首先尝试创建一个到服务端的连接,然后创建一个客户端对象,调用 SayHello 方法发送一个 HelloRequest 并回收一个 HelloReply:

    1
    2
    3
    4
    5
    conn, err := grpc.Dial(address, grpc.WithInsecure())
    c := pb.NewGreeterClient(conn)
    name := defaultName
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})

至此,运行两个 main.go 即可达到我们现前使用的效果,gRPC 服务也可正常运行。

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×