Go gRPC 入门

传送门:

Go微服务(三)——gRPC详细入门_小象裤衩的博客-CSDN博客

对原文的一些补充:

1. gRPC中的错误处理

    一般接口都会定义统一的错误返回格式,如果在proto文件中的每个message消息体内硬是增加一个错误消息结构,十分的不优雅,go 的 grpc 包提供了一个 status 功能,可以通过metadata(下面再介绍metadata)在header中返回给客户端,这样就不用修改每个接口的message消息体了

常规用法:

  • 调用 status.New 方法,并传入一个适当的错误码,生成一个 status.Status 对象
  • 调用该 status.Err 方法生成一个能被调用方识别的error,然后返回
st := status.New(codes.NotFound, "some description")
err := st.Err()

// 等同于 status.Error(codes.NotFound, "some description")

进阶用法:

status.New只能声明一个错误code(还不能自定义)以及一段msg文本,如果需要更丰富的报错,则需要使用 WithDetails 功能自定义错误结构体

服务端示例: 

// 生成一个 status.Status 
st := status.New(codes.ResourceExhausted, "Request limit exceeded.")
// 填充错误的补充信息 WithDetails
ds, err := st.WithDetails(
    &pb.CustomError{    // CustomError 需要在对应的 proto 文件中定义成 message
        xxx: "xxx",
        xxx: "xxx",
        ......
    },
)
if err != nil {
    return nil, st.Err()
}
return nil, ds.Err()

客户端示例:

r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "world"})
// 调用 RPC 如果遇到错误就对错误处理
if err != nil {
    // 转换错误
    s := status.Convert(err)
    // 解析错误信息
    for _, d := range s.Details() {
        // 通过断言直接使用
        switch info := d.(type) {
            case *pb.CustomError:
            log.Printf("Custom error: %s", info)
            default:
            log.Printf("Unexpected type: %s", info)
        }
    }
}

原理:

这个错误是如何传递给调用方Client的呢?

是放到 metadata中的,而metadata是放到HTTP的header中的。

metadata是key:value格式的数据。错误的传递中,key是个固定值:grpc-status-details-bin。

而value,是被proto编码过的,是二进制安全的。

目前大多数语言都实现了这个机制

2. metadata的使用

metadata,简称MD,一般用来传递消息以外的额外数据(挂载数据):

  • 链路追踪的参数,如traceId spanId等
  • 错误的详情
  • 签名校验
  • 其他通用参数

 gRPC是基于HTTP2的,HTTP2中除了 header 还有 trailer。

metadata就是放在header和trailer中传输的

客户端发起请求的时候可以带,服务端返回数据的时候也可以带。

metadata是一个key-value结构的数据(map),但是value是string类型的切片

type MD map[string][]string

客户端发送请求的MD都会放到header中

服务端响应的MD可以选择放到header,也可以选择放到trailer

对于Unary模式的调用,因为是一次请求一次响应,所以放在哪里无所谓

构造metadata

// key1 的值将会是一个slice,有两个值: []string{"val1", "val1-2"}
md := metadata.Pairs(
    "key1", "val1",
    "key1", "val1-2",
    "key2", "val2",
)

客户端发起/接收metadata

  • 客户端将md放入ctx上下文中发起请求
//方式1:创建一个带md的context
md := metadata.Pairs("k1", "v1", "k1", "v2", "k2", "v3")
ctx := metadata.NewOutgoingContext(context.Background(), md)

//方式2:对原有的 context 追加,AppendToOutgoingContext 
ctx := metadata.AppendToOutgoingContext(ctx, "k1", "v1", "k1", "v2", "k2", "v3")

//方式3:对原有的 context 追加参数,metadata.Join
send, _ := metadata.FromOutgoingContext(ctx)
newMD := metadata.Pairs("k3", "v3")
ctx = metadata.NewOutgoingContext(ctx, metadata.Join(send, newMD))
  • 客户端接收从服务端得到的md
// 提前声明用于接收的变量
var header, trailer metadata.MD
r, err := client.SomeRPC(
    ctx,
    someRequest,
    grpc.Header(&header),    // 接收的header放在这里
    grpc.Trailer(&trailer),  // 接收的trailer放这里
)

fmt.Println(header.Get("key"))    // 打印从服务端这边得到的md中定义的key

服务端发起/接收metadata

  • 服务端也是从ctx中获取metadata
//unary 模式
md, ok := metadata.FromIncomingContext(ctx)
  • 可以通过header或者trailer发起metadata给客户端
func (s *server) SomeRPC(ctx context.Context, in *pb.someRequest) (*pb.someResponse, error) {
    // 创建并设置 header
    header := metadata.Pairs("header-key", "val")
    grpc.SendHeader(ctx, header)
    // 创建并设置 trailer
    trailer := metadata.Pairs("trailer-key", "val")
    grpc.SetTrailer(ctx, trailer)
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值