• gRPC之metadata


    1、metadata

    服务间使用 Http 相互调用时,经常会设置一些业务自定义 header 如时间戳、trace信息等,gRPC使用 HTTP/2

    协议自然也是支持的,gRPC 通过 google.golang.org/grpc/metadata 包内的 MD 类型提供相关的功能接口。

    1.1 类型定义

    // MD is a mapping from metadata keys to values. Users should use the following
    // two convenience functions New and Pairs to generate MD.
    type MD map[string][]string
    
    • 1
    • 2
    • 3

    metadata.MD 类型的定义非常简单,可以像一个普通的 map 一样直接操作,同时 metadata 包里封装了很多工

    具方法供我们使用。

    // 使用 New 方法创建
    md := metadata.New(map[string]string{"k1":"v1", "k2", "v2"})
    
    // 直接使用 make 创建
    md := make(metadata.MD)
    
    // 使用 Pairs 方法创建
    md := metadata.Pairs("k1", "v1-1", "k1", "v1-2")
    
    // 一些操作
    md.Set("key", "v1", "v2")
    md.Append("key", "v3")
    md.Delete("key")
    vals := md.Get("key")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    1.2 发送与接收

    1.2.1 客户端

    客户端请求的 metadata 是通过设置 context 使用的,metadata 包提供了两个 context 相关的方法,设置好

    context 后直接在调用 rpc 方法时传入即可:

    md := metadata.New(map[string]string{"k1":"v1", "k2", "v2"})
    
    // 使用 NewOutgoingContext 初始化一个新的 context
    ctx := metadata.NewOutgoingContext(context.Background(), md)
    
    // 使用 AppendToOutgoingContext 向 context 追加 metadata
    ctx = metadata.AppendToOutgoingContext(ctx, "k3", "v3")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    客户端接收响应中的 metadata 需要区分普通 rpc 和 stream rpc :

    // 普通 rpc,使用 grpc.Header 方法包装为 CallOption
    var md metadata.MD
    res, err := client.Ping(ctx, &pb.PingRequest{Value: "ping"}, grpc.Header(&md))
    
    // stream rpc
    stream, err := client.MultiPong(context.Background(), &pb.PingRequest{Value: "ping"})
    if err != nil {
        log.Fatal(err)
    }
    
    // 通过 stream 对象的 Header 方法获取
    md, err := stream.Header()
    if err != nil {
        log.Fatal(err)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    1.2.2 服务端

    对应客户端请求的 metadata 是使用 context 设置的,那么服务端在接收时自然也是从 context 中读取,

    metadata 包中的 FromIncommingContext 方法就是用来读取 context 中的 metadata数据的:

    // unary rpc
    func (s *PingPongServer) Ping(ctx context.Context, req *pb.PingRequest) (*pb.PongResponse, error) {
        // 读取请求metadata
        md, ok := metadata.FromIncomingContext(ctx)
        if ok {
            log.Printf("Got md: %v", md)
        }
    
    // stream rpc
    func (s *PingPongServer) MultiPingPong(stream pb.PingPong_MultiPingPongServer) error {
        md, ok := metadata.FromIncomingContext(stream.Context())
        if ok {
            log.Printf("Got md: %v", md)
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    服务端设置响应的 metadata 也非常简单,只需要调用封装好的 SetHeaderSendHeader 方法即可:

    // unary rpc
    func (s *PingPongServer) Ping(ctx context.Context, req *pb.PingRequest) (*pb.PongResponse, error) {
        // 读取请求metadata
        md, ok := metadata.FromIncomingContext(ctx)
        if ok {
            log.Printf("Got md: %v", md)
        }
    
        // SetHeader设置响应 metadata
        grpc.SetHeader(ctx, metadata.New(map[string]string{"rkey": "rval"}))
        // 注意 SendHeader 只能调用一次
        // grpc.SendHeader(ctx, metadata.New(map[string]string{"rkey": "rval"}))
    
    
    // stream rpc, 调用 stream 的 SetHeader 方法
    func (s *PingPongServer) MultiPong(req *pb.PingRequest, stream pb.PingPong_MultiPongServer) error {
        stream.SetHeader(metadata.New(map[string]string{"rkey": "rval"}))
        // 注意 SendHeader 只能调用一次
        // stream.SendHeader(metadata.New(map[string]string{"rkey": "rval"}))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    1.3 使用案例

    1.3.1 proto 文件编写和编译

    demo.proto文件内容:

    syntax="proto3";
    package protos;
    option go_package = "./protos;protos";
    
    service Greeter {
      rpc SayHello(HelloRequest) returns(HelloReply){}
    }
    
    message HelloRequest {
      string name = 1;
    }
    
    message HelloReply {
      string message = 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    编译生成demo.pb.go 文件:

    $ protoc --go_out=plugins=grpc:. demo.proto
    
    • 1
    1.3.2 server端

    server.go文件的内容:

    package main
    
    import (
    	"context"
    	"flag"
    	"fmt"
    	"demo/protos"
    	"google.golang.org/grpc"
    	"google.golang.org/grpc/metadata"
    	"log"
    	"net"
    )
    
    var host = "127.0.0.1"
    
    var (
    	ServiceName = flag.String("ServiceName", "hello_service", "service name")
    	Port        = flag.Int("Port", 50001, "listening port")
    )
    
    type server struct {
    }
    
    func main() {
    	flag.Parse()
    
    	lis, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", *Port))
    	if err != nil {
    		log.Fatalf("failed to listen:%s", err)
    	} else {
    		fmt.Printf("listen at :%d\n", *Port)
    	}
    
    	defer lis.Close()
    
    	s := grpc.NewServer()
    	defer s.GracefulStop()
    
    	protos.RegisterGreeterServer(s, &server{})
    	addr := fmt.Sprintf("%s:%d", host, *Port)
    	fmt.Printf("server add:%s\n", addr)
    
    	if err := s.Serve(lis); err != nil {
    		fmt.Printf("failed to server: %s", err)
    	}
    }
    
    func (s *server) SayHello(ctx context.Context, in *protos.HelloRequest) (*protos.HelloReply, error) {
    	md, ok := metadata.FromIncomingContext(ctx)
    	if !ok {
    		fmt.Printf("get metadata error")
        }else{
            fmt.Println("get metadata success: ",md)
        }
    	if t, ok := md["timestamp"]; ok {
    		fmt.Printf("timestamp from metadata:\n")
    		for i, e := range t {
    			fmt.Printf(" %d. %s\n", i, e)
    		}
    	}
    	if t1, ok1 := md["key1"]; ok1 {
    		fmt.Printf("key1 from metadata:\n")
    		for i, e := range t1 {
    			fmt.Printf(" %d . %s\n", i, e)
    		}
    	}
    	if len(md) > 0 {
    		for k, v := range md {
    			fmt.Printf("%v:%v\n", k, v)
    		}
    	}
    	return &protos.HelloReply{Message: "server: " + in.Name}, nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    1.3.3 client端

    client.go文件的内容:

    package main
    
    import (
    	"context"
    	"fmt"
    	"demo/protos"
    	"google.golang.org/grpc"
    	"google.golang.org/grpc/metadata"
    	"time"
    )
    
    const (
    	timestampFormat = time.StampNano
    )
    
    func main() {
    	conn, err := grpc.Dial("127.0.0.1:50001", grpc.WithInsecure())
    	if err != nil {
    		panic(err)
    	}
    
    	client := protos.NewGreeterClient(conn)
    	md := metadata.Pairs("timestamp", time.Now().Format(timestampFormat))
    	md = metadata.New(map[string]string{"key1": "val1", "key2": "val2"})
    	ctx := metadata.NewOutgoingContext(context.Background(), md)
    	resp, err := client.SayHello(ctx, &protos.HelloRequest{Name: "Hello"})
    	if err == nil {
    		fmt.Printf("Reply is : %s\n", resp.Message)
    	} else {
    		fmt.Printf("call server error:%s\n", err)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    1.3.4 运行
    [root@zsx demo]# go run server.go
    listen at :50001
    server add:127.0.0.1:50001
    get metadata success:  map[:authority:[127.0.0.1:50001] content-type:[application/grpc] key1:[val1] key2:[val2] user-agent:[grpc-go/1.53.0]]
    key1 from metadata:
     0 . val1
    :authority:[127.0.0.1:50001]
    content-type:[application/grpc]
    user-agent:[grpc-go/1.53.0]
    key2:[val2]
    key1:[val1]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    [root@zsx demo]# go run client.go
    Reply is : server: Hello
    
    • 1
    • 2
    # 项目结构
    [root@zsx protoc]# tree demo/
    demo/
    ├── client.go
    ├── demo.proto
    ├── go.mod
    ├── go.sum
    ├── protos
    │   └── demo.pb.go
    └── server.go
    
    1 directory, 6 files
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
  • 相关阅读:
    我的十年编程路 2013年篇
    sqlServer 检测慢 sql
    实时即未来,大数据项目车联网之原始数据实时ELT流式任务流程总结【七】
    SpringAMQP
    从火山引擎新品发布会,看字节的数据飞轮如何转起来?
    cmake工程出现“CMAKE_CUDA_ARCHITECTURES must be non-empty if set.“的解决方法
    【JavaEE重点知识归纳】第8节:面向对象程序三大特性:封装、继承、多态
    【JavaSE基础】对象的构造及初始化
    Rust 语法笔记
    计算机毕业设计ssm基于SSM框架的宿舍管控平台6z76b系统+程序+源码+lw+远程部署
  • 原文地址:https://blog.csdn.net/qq_30614345/article/details/134470773