grpc

2021-09-26 00:09:03阅读 3评论 0

Protocol Buffers

Protocol Buffers 最初是在 Google 开发的,用于处理索引服务器请求/响应协议。是一种灵活、高效、自动化的解决方案,可以准确地解决这个问题。使用协议缓冲区,您可以编写.proto要存储的数据结构的描述。由此,协议缓冲区编译器创建了一个类,该类以高效的二进制格式实现协议缓冲区数据的自动编码和解析。生成的类为构成协议缓冲区的字段提供了 getter 和 setter,并将读写协议缓冲区的细节作为一个单元处理。重要的是,协议缓冲区格式支持随着时间的推移扩展格式的想法,这样代码仍然可以读取使用旧格式编码的数据。

随着系统的发展,它获得了许多其他功能和用途:

  • 自动生成的序列化和反序列化代码避免了手动解析的需要。
  • 除了用于短期 RPC(远程过程调用)请求之外,人们还开始使用 protocol buffers 作为一种方便的自描述格式,用于持久存储数据(例如在 Bigtable 中)。
  • 服务器 RPC 接口开始被声明为协议文件的一部分,protocol 编译器生成存根类,用户可以使用服务器接口的实际实现来覆盖这些类。

类比

个人认为如果要将 ProtoBuf、XML、JSON 三者放到一起去比较,应该区分两个维度。一个是数据结构化,一个是数据序列化。这里的数据结构化主要面向开发或业务层面,数据序列化面向通信或存储层面,当然数据序列化也需要“结构”和“格式”,所以这两者之间的区别主要在于面向领域和场景不同,一般要求和侧重点也会有所不同。数据结构化侧重人类可读性甚至有时会强调语义表达能力,而数据序列化侧重效率和压缩。

JSON、XML更适合数据结构化而非数据序列化

  • XML、JSON、ProtoBuf 都具有数据结构化和数据序列化的能力
  • XML、JSON 更注重数据结构化,关注人类可读性和语义表达能力。ProtoBuf 更注重数据序列化,关注效率、空间、速度,人类可读性差,语义表达能力不足(为保证极致的效率,会舍弃一部分元信息)
  • ProtoBuf 的应用场景更为明确,XML、JSON 的应用场景更为丰富。

.proto文件语法

官网:https://developers.google.com/protocol-buffers/docs/proto3

消息

syntax = "proto3";

message SearchRequest {
  optional string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
  • optional: 该字段可能会或可能不会设置。如果未设置可选字段值,则使用默认值。对于简单类型,您可以指定自己的默认值,就像我们type在示例中为电话号码所做的那样。否则,使用系统默认值:数字类型为零,字符串为空字符串,布尔值为 false。对于嵌入的消息,默认值始终是消息的“默认实例”或“原型”,它没有设置任何字段。调用访问器以获取尚未显式设置的可选(或必需)字段的值始终返回该字段的默认值。
  • repeated:该字段可以重复任意次数(包括零次)。重复值的顺序将保留在协议缓冲区中。将重复字段视为动态大小的数组。
  • required:必须提供该字段的值,否则消息将被视为“未初始化”。尝试构建未初始化的消息将抛出一个RuntimeException. 解析未初始化的消息将抛出一个IOException. 除此之外,必填字段的行为与可选字段完全相同。

proto3 已舍弃 required 字段,optional 字段也无法显示使用(因为缺省默认就设置为 optional)


添加注释

/* SearchRequest represents a search query, with pagination options to
 * indicate which results to include in the response. */

message SearchRequest {
  string query = 1;
  int32 page_number = 2;  // Which page number do we want?
  int32 result_per_page = 3;  // Number of results to return per page.
}

默认值

解析消息时,如果编码的消息不包含特定的单数元素,则解析对象中的相应字段将设置为该字段的默认值。这些默认值是特定于类型的:

  • 对于字符串,默认值为空字符串。
  • 对于字节,默认值为空字节。
  • 对于 bool,默认值为 false。
  • 对于数字类型,默认值为零。
  • 对于enums,默认值是第一个定义的 enum value,它必须是 0。

枚举

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 4;
}

每个枚举定义都必须包含一个映射到零的常量作为其第一个元素


导入

// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto

请注意,此功能在 Java 中不可用


嵌套

message SearchResponse {
  message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
  }
  repeated Result results = 1;
}

任何

import "google/protobuf/any.proto";

message ErrorStatus {
  string message = 1;
  repeated google.protobuf.Any details = 2;
}

map

map<string, Project> projects = 3;

服务

service SearchService {
  rpc Search(SearchRequest) returns (SearchResponse);
}

metadata

元数据,即数据的数据,标识数据的数据,类似http的请求头

1)定义proto

syntax = "proto3";
option go_package="./;proto";
service Greeter{
    rpc GetString(ReqData)returns(ResData);
}
message  ReqData{
    string data = 1;
}
message  ResData{
    string data = 1;
}

2)定义服务端

package main

import (
    "context"
    "fmt"
    "google.golang.org/grpc"
    "google.golang.org/grpc/metadata"
    "gotest/metadata/proto"
    "net"
)

type server struct {

}
func (s *server) GetString(ctx context.Context,req *proto.ReqData) (*proto.ResData, error){
    //从上下文中读取出键值对
    md, ok := metadata.FromIncomingContext(ctx)
    if ok{
        fmt.Println(md)
    }
    fmt.Println(req.GetData())
    return &proto.ResData{
        Data: "Data from server",
    },nil
}

func main(){
    lis,err := net.Listen("tcp",":5000")
    if err != nil{
        fmt.Println(err)
    }
    s := grpc.NewServer()
    proto.RegisterGreeterServer(s,&server{})
    s.Serve(lis)

}

3)定义客户端

package main

import (
    "context"
    "fmt"
    "google.golang.org/grpc"
    "google.golang.org/grpc/metadata"
    "gotest/metadata/proto"
)

func main(){
    conn,err := grpc.Dial("127.0.0.1:5000",grpc.WithInsecure())
    if err != nil{
        fmt.Println(err)
    }
    defer conn.Close()
    c := proto.NewGreeterClient(conn)

    //构建键值对
    md := metadata.Pairs(
        "key1", "val1-2",
        "key2", "val2",
        )

    // 将键值对构建metadata
    ctx := metadata.NewOutgoingContext(context.Background(), md)

    r,err := c.GetString(ctx,&proto.ReqData{
        Data: "Data from client",
    })
    if err != nil{
        fmt.Println(err)
    }
    fmt.Println(r.GetData())
}

拦截器

1)定义proto

syntax = "proto3";
option go_package="./;proto";
service Greeter{
    rpc GetString(ReqData)returns(ResData);
}
message  ReqData{
    string data = 1;
}
message  ResData{
    string data = 1;
}

2)服务端

package main

import (
    "context"
    "fmt"
    "google.golang.org/grpc"
    "gotest/metadata/proto"
    "net"
)

type server struct {

}
func (s *server) GetString(ctx context.Context,req *proto.ReqData) (*proto.ResData, error){
    fmt.Println(req.GetData())
    return &proto.ResData{
        Data: "Data from server",
    },nil
}


func main(){
    lis,err := net.Listen("tcp",":5000")
    if err != nil{
        fmt.Println(err)
    }

    // 注册interceptor
    var interceptor grpc.UnaryServerInterceptor
    interceptor = func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
        fmt.Println("收到一个请求")
        if err != nil {
            return
        }
        // 继续处理请求
        return handler(ctx, req)
    }
    opts := grpc.UnaryInterceptor(interceptor)
    s := grpc.NewServer(opts)
    proto.RegisterGreeterServer(s,&server{})
    s.Serve(lis)

}

3)客户端

package main

import (
    "context"
    "fmt"
    "google.golang.org/grpc"
    "gotest/metadata/proto"
    "log"
    "time"
)

func Clientinterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
    start := time.Now()
    err := invoker(ctx, method, req, reply, cc, opts...)
    log.Printf("method == %s ; req == %v ; rep == %v ; duration == %s ; error == %v\n", method, req, reply, time.Since(start), err)
    return err
}

func main(){
    opt := grpc.WithUnaryInterceptor(Clientinterceptor)
    //注册interceptor
    conn,err := grpc.Dial("127.0.0.1:5000",grpc.WithInsecure(),opt)
    if err != nil{
        fmt.Println(err)
    }
    defer conn.Close()
    c := proto.NewGreeterClient(conn)

    r,err := c.GetString(context.Background(),&proto.ReqData{
        Data: "Data from client",
    })
    if err != nil{
        fmt.Println(err)
    }
    fmt.Println(r.GetData())
}

还没有评论,来说两句吧