gRPC 在设计上鼓励将错误处理作为服务的一部分,而不是将其隐藏在消息体中。每个 gRPC 服务天生就拥有一个错误返回值,作为专门的错误传输通道。所有 gRPC 中的错误返回值应该要么是 nil,要么是 由 status.Status 生成的错误。这样可以确保调用方可以轻松识别错误。
1. 基本用法
简单地返回 Go 错误并不能被下游客户端识别。正确的做法是:
- 调用 status.New 方法并传入合适的错误代码以生成 status.Status 对象。
- 调用 status.Err 方法生成调用方可以识别的错误,然后返回。
传入的错误代码类型为 codes.Code。或者,你可以使用 status.Error 方法,它可以避免手动转换。
2. 高级用法
上述错误有一个限制:codes.Code 定义的错误代码只涵盖了某些场景,无法全面表达业务中遇到的各种错误场景。
gRPC 提供了一种机制来补充错误中的信息:status.WithDetails 方法。
客户端可以通过将错误转换回 status.Status 并使用 status.Details 方法直接获取内容。
status.Details 返回一个切片,它是 interface{} 的切片。但是,Go 会自动执行类型转换,允许通过断言直接使用。
服务器端示例
- 生成 status.Status 对象
- 填充额外的错误信息
客户端端示例
- 在 RPC 错误后解析错误信息
- 通过断言直接获取错误详细信息
原理
这些错误是如何传递给调用方客户端的呢?它们被放置在元数据中,然后在 HTTP 头部中。元数据以键值对的形式存在。在错误传输中,键是一个固定值:grpc-status-details-bin。值由 proto 编码,并且是二进制安全的。大多数语言都实现了这种机制。
图片
注意
gRPC 对响应头有限制,最大为 8K,因此错误不能太大。
参考
- Protocol Buffers Tutorial[1]
- errdetails[2]
总结
gRPC 提供了灵活的错误处理机制,允许你以结构化的方式传递错误信息,帮助你构建更健壮、更可靠的微服务。通过正确使用 status.Status 和 status.WithDetails 方法,你可以确保你的错误信息清晰易懂,并能被客户端轻松理解和处理。
参考资料
[1] Protocol Buffers Tutorial: https://protobuf.dev/getting-started/gotutorial/
[2] errdetails: https://pkg.go.dev/google.golang.org/genproto/googleapis/rpc/errdetails