当发起 gRPC 调用时,客户端会接收成功状态的响应或者带有对应错误状态的错误。在编写客户端应用程序时,需要处理所有潜在的错误和错误条件。编写服务器端应用程序也需要处理错误,并生成适当的错误状态码。
当发生错误时,gRPC 返回一个错误状态码,并附带一条可选的错误消息,该消息提供错误条件的更多细节。状态对象由一个整型码和一条字符串消息组成,适用于不同语言的所有 gRPC 实现。
gRPC 使用一组定义良好的专用状态码,举例如下。
- OK
成功状态,非错误。 - CANCELLED
操作被取消,通常由调用者发起。 - DEADLINE_EXCEEDED
在操作完成前,就已超过了截止时间。 - INVALID_ARGUMENT
客户端指定了非法参数。
表 5-1 展示了可用的 gRPC 错误码以及每个错误码的描述。完整的错误码列表可以在 gRPC 官方文档或与 Go 和 Java 相关的文档中查看。
表5-1:gRPC错误码
错误 码 | 数字 | 描述 |
---|---|---|
OK | 0 | 成功状态 |
CANCELLED | 1 | 操作已被(调用者)取消 |
UNKNOWN | 2 | 未知错误 |
INVALID_ARGUMENT | 3 | 客户端指定了非法参数 |
DEADLINE_EXCEEDED | 4 | 在操作完成前,就已超过了截止时间 |
NOT_FOUND | 5 | 某些请求实体没有找到 |
ALREADY_EXISTS | 6 | 客户端试图创建的实体已存在 |
PERMISSION_DENIED | 7 | 调用者没有权限执行特定的操作 |
RESOURCE_EXHAUSTED | 8 | 某些资源已被耗尽 |
FAILED_PRECONDITION | 9 | 操作被拒绝,系统没有处于执行操作所需的状态 |
ABORTED | 10 | 操作被中止 |
OUT_OF_RANGE | 11 | 尝试进行的操作超出了合法的范围 |
UNIMPLEMENTED | 12 | 在该服务中,未实现或不支持(未启用)本操作 |
INTERNAL | 13 | 内部错误 |
UNAVAILABLE | 14 | 该服务当前不可用 |
DATA_LOSS | 15 | 不可恢复的数据丢失或损坏 |
UNAUTHENTICATED | 16 | 客户端没有进行操作的合法认证凭证 |
gRPC 所提供的“开箱即用”的错误模型非常有限,并且与底层的 gRPC数据格式无关,其中最常见的格式就是 protocol buffers。如果使用protocol buffers 作为数据格式,那么可以利用 google.rpc 包所提供的更丰富的错误模型。但是,只有 C++、Go、Java、Python 和 Ruby 的库可以支持该错误,如果使用其他语言,则需要注意这一点。
下面来看在真实的 gRPC 错误处理场景中,对于这些理念的具体运用方式。在订单管理场景中,假设需要在 AddOrder 远程方法中处理非法订单 ID 请求。如代码清单 5-7 所示,假设给定的订单 ID 是 -1,然后需要生成一个错误并将其返回给消费者。
代码清单 5-7 服务器端的错误创建和传播
if orderReq.Id == "-1" { ➊
log.Printf("Order ID is invalid! -> Received Order ID %s",orderReq.Id)
errorStatus := status.New(codes.InvalidArgument,"Invalid information received") ➋
ds, err := errorStatus.WithDetails( ➌
&epb.BadRequest_FieldViolation{
Field:"ID",
Description: fmt.Sprintf("Order ID received is not valid %s : %s",
orderReq.Id, orderReq.Description),
},
)
if err != nil {
return nil, errorStatus.Err()
}
return nil, ds.Err() ➍
}
- ❶ 非法请求,需要生成一个错误并将其返回给客户端。
- ❷ 创建一个错误码为 InvalidArgument 的新错误状态。
- ❸ 包含错误类型 BadRequest_FieldViolation 的所有错误详情。可以在 GoDoc 网站上搜索 errdetails,了解更多关于BadRequest_FieldViolation 的信息。
- ❹ 返回生成的错误。
通过 status 包,可以很容易地基于所需的错误码和详情创建错误状态。
本例使用了 status.New(codes.InvalidArgument, "Invalidinformation received")
,只需借助 return nil,errorStatus.Err()
将该错误发送回客户端即可。但是,为了包含更丰富的错误模型,可以使用 Google API
的 google.rpc
包。本例根据特定的错误类型设置了错误详情,该错误类型同样可以通过在 GoDoc
网站
上搜索 errdetails
进行浏览。
至于客户端的错误处理,只需处理 RPC 返回的错误即可。例如,在代码清单 5-8 中,可以看到该订单管理场景中客户端应用程序的 Go 实现。这里调用了 AddOrder
方法,并将返回的错误赋值给addOrderError
变量。因此,下一步就是探查 addOrderError
的结果并处理错误。为了实现这一点,可以获取在服务器端设置的错误码和特定的错误类型。
代码清单 5-8 客户端的错误处理
order1 := pb.Order{
Id: "-1",
Items:[]string{"iPhone XS", "Mac Book Pro"},
Destination:"San Jose, CA", Price:2300.00} ➊
res, addOrderError := client.AddOrder(ctx, &order1) ➋
if addOrderError != nil {
errorCode := status.Code(addOrderError) ➌
if errorCode == codes.InvalidArgument { ➍
log.Printf("Invalid Argument Error : %s", errorCode)
errorStatus := status.Convert(addOrderError) ➎
for _, d := range errorStatus.Details() {
switch info := d.(type) {
case *epb.BadRequest_FieldViolation:➏
log.Printf("Request Field Invalid: %s", info)
default:
log.Printf("Unexpected error type: %s", info)
}
}
} else {
log.Printf("Unhandled error : %s ", errorCode)
}
} else {
log.Print("AddOrder Response -> ", res.Value)
}
- ❶ 这是一个非法订单。
- ❷ 调用 AddOrder 远程方法并将错误赋值给 addOrderError。
- ❸ 使用 status 包获取错误码。
- ❹ 检查 InvalidArgument 错误码。
- ❺ 从错误中获取错误状态。
- ❻ 检查 BadRequest_FieldViolation 错误类型。
在 gRPC 应用程序中,尽可能使用适当的 gRPC 错误码和丰富的错误模型,这始终是一个最佳实践。gRPC 错误状态和详情通常会通过 trailer头信息在传输层发送。
下面来看一下多路复用,这是针对同一个 gRPC 服务器端运行时的服务托管机制。