golang 使用 gRPC
RPC
RPC(Remote Procedure Call: 远程过程调用)是一个计算机通信协议,该协议允许运行于一台计算机的程序调用另一个地址空间(通常为一个开放网络的一台计算机)的子程序,而程序员就像调用本地程序一样,无需额外地为这个交互作用编程(无需关注细节)。
gRPC
gRPC 一开始由 google 开发,是一款语言中立、平台中立、开源的远程过程调用(RPC)系统。(http://doc.oschina.net/grpc)
在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。
gRPC 默认使用 protocol buffers,这是 Google 开源的一套成熟的结构数据序列化机制(当然也可以使用其他数据格式如 JSON)。
使用gRPC分为三步
- 编写.proto文件
- 利用工具将.proto文件生成对应语言的代码
- 根据生成的代码编写服务端和客户端的代码
开始之前
首先我们需要安装将.proto文件生成对应代码的工具,下载地址(https://github.com/protocolbuffers/protobuf/releases),下载你对应操作系统的压缩包即可。下载完成后解压将其bin目录下的可执行文件放入环境变量中的文件夹即可。
此外,我们还需要安装对应语言的插件,比如
go语言插件安装方式
go get -u github.com/golang/protobuf/protoc-gen-go
python插件安装方式
pip install grpcio
pip install protobuf
pip install grpcio_tools
接下来我将以一个例子来做演示介绍如何在go中使用gRPC,注意我将采用go module的方式来编写这个demo,
首先我们在一个你喜欢的文件夹下面新建一个文件夹命名为hello_grpc
,然后在hello_grpc
文件夹下新建一个go.mod文件并写入一下内容
module "hello_grpc"
然后用你喜欢的IDE打开这个文件夹,进行之后的操作
1. 编写.proto文件
我们在项目的根目录下新建名为pb
的文件夹,然后新建名为hello_grpc.proto
的文件,写入如下内容,这是一个官方的例子
syntax = "proto3";
package service;
option go_package = ".;hello_grpc";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
其中第一行指定了我们使用 protocol buffers的版本
下面我们定义了包的名称,这将成为后面我们生成的go语言的代码的包名
然后我们定义了一个服务名为Greeter,其中定义了一个函数SayHello它的参数定义在HelloRequest,返回值定义在HelloReply
关于proto的服务,一共有4种类型,此例子中是最简单的一种
-
简单 RPC
客户端使用存根发送请求到服务器并等待响应返回,就像平常的函数调用一样。
rpc GetFeature(Point) returns (Feature) {}
-
服务器端流式 RPC
客户端发送请求到服务器,拿到一个流去读取返回的消息序列。 客户端读取返回的流,直到里面没有任何消息。从例子中可以看出,通过在 响应 类型前插入
stream
关键字,可以指定一个服务器端的流方法。rpc ListFeatures(Rectangle) returns (stream Feature) {}
-
客户端流式 RPC
客户端写入一个消息序列并将其发送到服务器,同样也是使用流。一旦客户端完成写入消息,它等待服务器完成读取返回它的响应。通过在 请求 类型前指定
stream
关键字来指定一个客户端的流方法。rpc RecordRoute(stream Point) returns (RouteSummary) {}
-
双向流式 RPC
是双方使用读写流去发送一个消息序列。两个流独立操作,因此客户端和服务器可以以任意喜欢的顺序读写:比如, 服务器可以在写入响应前等待接收所有的客户端消息,或者可以交替的读取和写入消息,或者其他读写的组合。 每个流中的消息顺序被预留。你可以通过在请求和响应前加
stream
关键字去制定方法的类型。rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
2. 生成对应语言的代码
在我们项目的根目录下新建service
目录(这将用来存放我们生成的golang代码),然后打开终端,输入命令
protoc -I pb/ pb/hello_grpc.proto --go_out=plugins=grpc:service
-I
后面指定proto文件存放目录,和proto文件
--go_out=plugins=grpc:
后面指定生成go代码存放的目录
检查在service目录下是否成功生成一个名为hello_grpc.pb.go
的文件
打开这个文件发现抱错,原因是我们的项目中还没有安装相应的包,输入以下命令安装
go mod tidy
3.编写服务端和客户端的代码
服务端
在根目录下新建文件夹server
然在这个文件夹下新建server.go
,写入以下内容
package main
import (
"context"
"fmt"
"hello_grpc/service"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
"net"
)
type server struct {}
func (s *server) SayHello(ctx context.Context, in *service.HelloRequest) (*service.HelloReply, error) {
return &service.HelloReply{Message: "hello " + in.Name}, nil
}
func main() {
// 监听本地端口
lis, err := net.Listen("tcp", ":8080")
if err != nil {
fmt.Printf("监听端口失败: %s", err)
return
}
// 创建gRPC服务器
s := grpc.NewServer()
// 注册服务
service.RegisterGreeterServer(s, &server{})
reflection.Register(s)
err = s.Serve(lis)
if err != nil {
fmt.Printf("开启服务失败: %s", err)
return
}
}
我们这里先定义了一个结构体,该结构体需要实现GreeterServer
这个接口(见生成的代码,如下)
type GreeterServer interface {
SayHello(context.Context, *HelloRequest) (*HelloReply, error)
}
服务端
同理在项目根目录下新建client/client.go
写入
package main
import (
"context"
"fmt"
"hello_grpc/service"
"google.golang.org/grpc"
)
func main(){
// 连接服务器
conn, err := grpc.Dial(":8080", grpc.WithInsecure())
if err != nil {
fmt.Printf("连接服务端失败: %s", err)
return
}
defer conn.Close()
// 新建一个客户端
c := service.NewGreeterClient(conn)
// 调用服务端函数
r, err := c.SayHello(context.Background(), &service.HelloRequest{Name: "horika"})
if err != nil {
fmt.Printf("调用服务端代码失败: %s", err)
return
}
fmt.Printf("调用成功: %s", r.Message)
}
4. 运行
-
先运行服务端
go run server/server.go
-
开新的终端运行客户端
go run client/client.go
-
观察客户端输出
5. 番外篇:编写跨语言调用
以python为例,这里就不编写python的服务端了,直接编写python的客户端调用go的服务端
注意:你是否安装python的插件?
pip install grpcio
pip install protobuf
pip install grpcio_tools
生成python代码
在项目跟目录下新建文件夹python
然后在根目录下打开终端输入命令
python -m grpc_tools.protoc -I pb/ --python_out=python/ --grpc_python_out=python/ pb/hello_grpc.proto
在刚刚新建的文件夹下查看是否多出来了两个文件hello_grpc_pb2.py
和hello_grpc_pb2_grpc.py
编写python客户端代码
在python文件夹中新建client.py写入
import logging
import grpc
import hello_grpc_pb2
import hello_grpc_pb2_grpc
def run():
with grpc.insecure_channel('localhost:8080') as channel:
stub = hello_grpc_pb2_grpc.GreeterStub(channel)
response = stub.SayHello(hello_grpc_pb2.HelloRequest(name='horika'))
print("调用成功: {}!".format(response.message))
if __name__ == '__main__':
logging.basicConfig()
run()
运行go的服务端,然后运行python客户端查看输出