gRPC 概述
概述
GRPC 是一个高性能、通用的开源 RPC 框架,基于底层 HTTP/2 协议标准和协议层 Protobuf 序列化协议开发,支持众多的开发语言。
gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。
gRPC 使用 protocol buffers
作为接口描述语言(IDL)以及底层的信息交换格式
优点
- 基于 HTTP/2 之上的二进制协议(Protobuf 序列化机制);
- 一个连接上可以多路复用,并发处理多个请求和响应;
- 多种语言的类库实现;
- 服务定义文件和自动代码生成(.proto 文件和 Protobuf 编译工具)。
- RPC 还提供了很多扩展点,用于对框架进行功能定制和扩展,例如,通过开放负载均衡接口可以无缝的与第三方组件进行集成对接(Zookeeper、域名解析服务、SLB 服务等)
通信方式
Simple RPC
简单 rpc ,这就是一般的 rpc 调用,一个请求对象对应一个返回对象
proto 语法:
rpc simpleHello(Person) returns (Result) {}
Server-side streaming RPC
服务端流式 rpc,一个请求对象,服务端可以传回多个结果对象
proto 语法:
rpc serverStreamHello(Person) returns (stream Result) {}
Client-side streaming RPC
客户端流式 rpc 客户端传入多个请求对象,服务端返回一个响应结果
proto 语法:
rpc clientStreamHello(stream Person) returns (Result) {}
Bidirectional streaming RPC
双向流式 rpc,结合客户端流式 rpc 和服务端流式 rpc,可以传入多个对象,返回多个响应对象
proto 语法:
rpc biStreamHello(stream Person) returns (stream Result) {}
安装
golang
版本的 grpc
要求 go
版本要在 1.6
以上
安装 gRPC
使用 go get
命令安装 grpc
包
$ go get -u google.golang.org/grpc
由于某些不可逆原因,上面命令会报连接超时,可以到
github
上将项目clone
到$GOPATH/src/google.golang.org/
下
$ cd $GOPATH/src/google.golang.org
$ git clone git@github.com:grpc/grpc-go.git grpc
安装 Protocol Buffers v3
$ apt install protobuf-compiler
安装 protoc 插件
安装 golang
版本对应的 protobuf
生成工具
$ go get -u github.com/golang/protobuf/protoc-gen-go
运行 demo
进入 example
目录
$ cd $GOPATH/src/google.golang.org/grpc/examples/helloworld
删除原来的 helloworld.pb.go
文件,并使用 protoc
生成自己生成一个
$ rm helloworld/helloworld.pb.go // 删除原来的helloworld.pb.go文件
$ protoc -I helloworld/ helloworld/helloworld.proto --go_out=plugins=grpc:helloworld // 根据 .proto 文件生成对应的.go文件
编写 grpc
接口时,在 .proto
文件定义接口通信数据格式和接口信息,然后通过 protoc
自动生成对应的 go
代码,大大方便了开发
-I PATH
:指定搜索引入的目录。可以指定多个,目录会按顺序搜索。未指定,则使用当前工作目录。--go_out
:指定输出go
代码plugins=grpc
:.proto
中的service
是grpc
扩展的功能,需要使用grpc
插件进行解析才能生成对应的接口定义代码。
运行 grpc server
和 grpc client
$ go run greeter_server/main.go // 启动grpc server
$ go run greeter_client/main.go // 启动grpc client
如果 helloworld 下面没有 greeter_server 和 greeter_client 文件夹执行下列两种其中一种命令
$ go get -u google.golang.org/grpc/examples/helloworld/greeter_client
$ go get -u google.golang.org/grpc/examples/helloworld/greeter_server
$ go get -u google.golang.org/grpc
实践
开发一个求和的 grpc
接口
定义 .proto 文件
在 proto
目录下创建接口描述文件 sum.proto
文件:
Message 命名采用驼峰命名方式,字段命名采用小写字母加下划线分隔方式
Enums 类型名采用驼峰命名方式,字段命名采用大写字母加下划线分隔方式
即便业务上不需要参数也必须指定一个请求消息,一般会定义一个空 message。
syntax = "proto3"; // 使用 proto3,没写默认proto2。建议grpc配合proto3使用,兼容性更高
// java生成选项
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
package proto; // 生成的go所属的package
// 需要序列化的结构体
message SumResp {
int64 sum = 1;
}
message SumReq {
int64 a = 1;
int64 b = 2;
}
service CalcSvc {
// 每个rpc接口声明都必须有且一个参数和一个返回值
rpc Sum(SumReq) returns (SumResp) {}
}
根据接口描述文件生成源码
进入 proto
目录,执行
$ protoc sum.proto --go_out=plugins=grpc:.
可以看到,在本目录下生成 sum.pb.go
文件,且 package
为 proto
开发服务端接口
首先查看生成的 sum.pb.go
文件,可以看到根据 sum.proto
文件中的 CalcSvc
接口定义生成了对应的接口:
// CalcSvcServer is the server API for CalcSvc service.
type CalcSvcServer interface {
// 每个rpc接口声明都必须有且一个参数和一个返回值
Sum(context.Context, *SumReq) (*SumResp, error)
}
开发服务端接口只要就是根据这些接口定义实现具体的业务逻辑
在项目下创建 service/main.go
:
package main
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
"grpc-demo/proto" //路径可能不同,看自己的proto包放在哪个目录下
"log"
"net"
)
// 类型断言
var _ proto.CalcSvcServer = new(CalcSvc)
type CalcSvc struct{}
func (CalcSvc) Sum(ctx context.Context, req *proto.SumReq) (resp *proto.SumResp, err error) {
// 建议使用GetA,不要直接使用req.A,可能存在req=nil的情况
a := req.GetA()
b := req.GetB()
log.Println("request coming ...")
return &proto.SumResp{
Sum: a + b,
}, err
}
func main() {
lis, err := net.Listen("tcp", ":8888")
if err != nil {
log.Fatal(err)
}
// 注册服务到gRPC
s := grpc.NewServer()
proto.RegisterCalcSvcServer(s, &CalcSvc{})
// 启用Server Reflection,可以使用gRPC CLI去检查services
// https://github.com/grpc/grpc-go/blob/master/Documentation/server-reflection-tutorial.md
reflection.Register(s)
// 启动服务
if err := s.Serve(lis); err != nil {
log.Fatal(err)
}
}
客户端访问
在项目下创建 client/main.go
:
package main
import (
"context"
"google.golang.org/grpc"
"grpc-demo/proto" //路径可能不同,看自己的proto包放在哪个目录下
"log"
)
func main() {
// 创建gRPC连接
// WithInsecure option 指定不启用认证功能
conn, err := grpc.Dial(":8888", grpc.WithInsecure())
if err != nil {
log.Fatal(err)
}
// 创建gRPC client
client := proto.NewCalcSvcClient(conn)
// 请求gRPC server
resp, err := client.Sum(context.Background(), &proto.SumReq{
A: 5,
B: 10,
})
if err != nil {
log.Fatal(err)
}
log.Printf("5 + 10 = %d", resp.GetSum())
}
运行
$ go run service/main.go &
$ go run client/main.go