gRPC(Google Remote Procedure Call) 是 Google 开发的高性能、通用的开源 RPC 框架,其由 Google 主要面向移动应用开发并基于 HTTP/2 协议标准而设计,基于 Protobuf(Protocol Buffers)序列化协议开发,且支持众多开发语言。只需要定义请求和响应,然后 gRPC 会帮你处理一切剩余问题。
优点:
- 速度快、执行效率高
- 基于 HTTP/2 构建,低延迟
- 支持流
- 与开发语言无关,可以支持多种语言
- 可以很简单的插入身份认证、负载均衡、日志、监控等功能
gRPC 是基于 protobuf 的,可参考这里:protobuf
-
gRPC 使用”合约优先”的 API 开发模式,它默认使用 Protocol Buffer(PB)作为接口设计语言(IDL),这个
.proto
文件包括两部分内容:- 服务端和客户端之间传递的消息(各种结构体)
- gRPC 服务的定义(提供哪些接口)
gRPC 可实现的通信类型:
- 一元消息 RPC,
rpc SayHello(HelloRequest) returns (HelloResponse){}
,客户端发出一个请求,从服务端获取一个应答,就像是普通的函数调用 - 服务端流式 RPC,
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){}
,客户端发出一个请求,获取一个数据流从服务端读取一系列消息 - 客户端流式 RPC,
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse){}
,客户端发出一系列消息给服务端,客户端完成写入后,服务端返回应答 - 双向流式 RPC,
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){}
,类似 TCP 的全双工工作方式,一旦通过函数调用建立连接后,可以持续双向通信
一个简单的 gRPC 示例,实现一元消息通信
syntax = "proto3";
package pb;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string replyMessage = 1;
}
-
生成 grpc 代码
protoc --proto_path=. --go_out=plugins=grpc:. xxx/xxx/*.proto
生成的服务端代码缺少具体实现,需要自己写一个类实现实现接口。 生成的客户端代码有具体实现,可以直接调用。 -
服务端调用代码
type greeterService struct{}
// 实现指定的接口
func (p *greeterService) SayHello (ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {
log.Println("say hello to ", req.GetName())
return &pb.HelloReply{ReplyMessage: proto.String("hello " + req.GetName())}, nil
}
func main() {
listen, _ = net.Listen("TCP", ":10000")
server = grpc.NewServer(grpc.WithInsecure())
pb.RegisterGreeterServiceServer(server, new(greeterService))
server.Serve(listen)
}
- 客户端调用代码
func main() {
conn, err := grpc.Dial("localhost:10000", grpc.WithInsecure())
if err != nil {
log.Fatal(err.Error())
}
defer conn.Close()
client := pb.NewGreeterServiceClient(conn)
resp, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: proto.String("myName")})
if err != nil {
log.Fatal(err.Error())
}
log.Println(resp.GetReplyMessage())
}
- 如果想通过 TLS 进行加密连接,可以通过私钥生成一个证书,用于 gRPC 通信
建立连接的各项参数
grpc.WithInsecure
禁用传输认证,没有这个选项必须设置一种认证方式grpc.WithCompressor
在 grpc.Dial 参数中设置压缩的方式将要被废弃,推荐使用 UseCompressorgrpc.WithBlock()
grpc.Dial 默认建立连接是异步的,加了这个参数后会等待所有连接建立成功后再返回grpc.WithUnaryInterceptor
一元拦截器,适用于普通 rpc 连接,相应的还有流拦截器。拦截器只有第一个生效,所以一般设置一个。拦截器是对请求的一次封装,客户端和服务端都可以设置拦截器,请求的发送/执行都是在拦截器内操作的,所以在请求的前后都可以嵌入用户自定义的代码,类似 hookgrpc.WithDefaultServiceConfig
旧的版本可以通过 grpc.RoundRobin(),和 grpc.WithBalancer()来设置负载均衡,这个版本 grpc.RoundRobin()已经取消了grpc.WithTimeout
对请求设定超时WithWriteBufferSize
设置读缓冲区容量WithReadBufferSize
设置写缓冲区容量
metadata
元数据,是在 gRPC 调用时附带在调用上的卫星数据,可以用来做权限验证、session 维持、链路追踪等。元数据类似 context 中的 Value,以 Key-Value 的形式保存在 context 中传输。
- Key 不区分大小写,会被统一转换为小写
创建 metadata
metadata.MD 是关键数据结构,定义是:type MD map[string][]string
- 类似 map 类型,用 New 函数创建
md := metadata.New(map[string]string{"key1": "val1", "key2": "val2"})
- 用 Pairs 函数创建,相同的 Key 对应的 Value 会被放入同一个 slice
md := metadata.Pairs(
"key1", "val1_1",
"key1", "val1_2", // "key1" will have map value []string{"val1", "val1-2"}
"key2", "val2",
)
发送 metadata
md := metadata.Pairs("key", "val")
ctx := metadata.NewOutgoingContext(context.Background(), md)
response, err := client.SomeRPC(ctx, someRequest)
接受 metadata
func (s *server) SomeRPC(ctx context.Context, in *pb.SomeRequest) (*pb.SomeResponse, err) {
md, ok := metadata.FromIncomingContext(ctx)
}