gRPC 在设计上鼓励将错误处理作为服务的一部分,而不是将其隐藏在消息体中。每个 gRPC 服务天生就拥有一个错误返回值,作为专门的错误传输通道。所有 gRPC 中的错误返回值应该要么是 nil,要么是 由 status.Status 生成的错误。这样可以确保调用方可以轻松识别错误。
1. 基本用法
简单地返回 Go 错误并不能被下游客户端识别。正确的做法是:
- 调用 status.New 方法并传入合适的错误代码以生成 status.Status 对象。
- 调用 status.Err 方法生成调用方可以识别的错误,然后返回。
st := status.New(codes.NotFound, "some description")
err := st.Err()
传入的错误代码类型为 codes.Code。或者,你可以使用 status.Error 方法,它可以避免手动转换。
err := status.Error(codes.NotFound, "some description")
2. 高级用法
上述错误有一个限制:codes.Code 定义的错误代码只涵盖了某些场景,无法全面表达业务中遇到的各种错误场景。
gRPC 提供了一种机制来补充错误中的信息:status.WithDetails 方法。
客户端可以通过将错误转换回 status.Status 并使用 status.Details 方法直接获取内容。
status.Details 返回一个切片,它是 interface{} 的切片。但是,Go 会自动执行类型转换,允许通过断言直接使用。
服务器端示例
- 生成 status.Status 对象
- 填充额外的错误信息
func ErrorWithDetails() error {
st := status.Newf(codes.Internal, fmt.Sprintf("something went wrong: %v", "api.Getter"))
v := &errdetails.PreconditionFailure_Violation{ //errDetails
Type: "test",
Subject: "12",
Description: "32",
}
br := &errdetails.PreconditionFailure{}
br.Violations = append(br.Violations, v)
st, _ = st.WithDetails(br)
return st.Err()
}
客户端端示例
- 在 RPC 错误后解析错误信息
- 通过断言直接获取错误详细信息
resp, err := odinApp.CreatePlan(cli.StaffId.AssetId, gentRatePlanMeta(cli.StaffId))
if status.Code(err) != codes.InvalidArgument {
logger.Error("create plan error:%v", err)
} else {
for _, d := range status.Convert(err).Details() {
//
switch info := d.(type) {
case *errdetails.QuotaFailure:
logger.Info("Quota failure: %s", info)
case *errdetails.PreconditionFailure:
detail := d.(*errdetails.PreconditionFailure).Violations
for _, v1 := range detail {
logger.Info(fmt.Sprintf("details: %+v", v1))
}
case *errdetails.ResourceInfo:
logger.Info("ResourceInfo: %s", info)
case *errdetails.BadRequest:
logger.Info("ResourceInfo: %s", info)
default:
logger.Info("Unexpected type: %s", info)
}
}
}
logger.Infof("create plan success,resp=%v", resp)
原理
这些错误是如何传递给调用方客户端的呢?它们被放置在元数据中,然后在 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