gRPC

gRPC gRPC(Google Remote Procedure Call) 是 Google 开发的高性能、通用的开源 RPC 框架,其由 Google 主要面向移动应用开发并基于 HTTP/2 协议标准而设计,基于 Protobuf(Protocol Buffers)序列化协议开发,且支持众多开发语言。只需要定义请求和响应,然后 gRPC 会帮你处理一切剩余问题。

优点:

  • 速度快、执行效率高
  • 基于 HTTP/2 构建,低延迟
  • 支持流
  • 与开发语言无关,可以支持多种语言
  • 可以很简单的插入身份认证、负载均衡、日志、监控等功能

gRPC 是基于 protobuf 的,可参考这里:protobuf

gRPC

  • gRPC 使用”合约优先”的 API 开发模式,它默认使用 Protocol Buffer(PB)作为接口设计语言(IDL),这个.proto文件包括两部分内容:

    • 服务端和客户端之间传递的消息(各种结构体)
    • gRPC 服务的定义(提供哪些接口)

gRPC 可实现的通信类型:

  1. 一元消息 RPC,rpc SayHello(HelloRequest) returns (HelloResponse){},客户端发出一个请求,从服务端获取一个应答,就像是普通的函数调用
  2. 服务端流式 RPC,rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){},客户端发出一个请求,获取一个数据流从服务端读取一系列消息
  3. 客户端流式 RPC,rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse){},客户端发出一系列消息给服务端,客户端完成写入后,服务端返回应答
  4. 双向流式 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 参数中设置压缩的方式将要被废弃,推荐使用 UseCompressor
  • grpc.WithBlock() grpc.Dial 默认建立连接是异步的,加了这个参数后会等待所有连接建立成功后再返回
  • grpc.WithUnaryInterceptor一元拦截器,适用于普通 rpc 连接,相应的还有流拦截器。拦截器只有第一个生效,所以一般设置一个。拦截器是对请求的一次封装,客户端和服务端都可以设置拦截器,请求的发送/执行都是在拦截器内操作的,所以在请求的前后都可以嵌入用户自定义的代码,类似 hook
  • grpc.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

  1. 类似 map 类型,用 New 函数创建
md := metadata.New(map[string]string{"key1": "val1", "key2": "val2"})
  1. 用 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)
}