当发起 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 APIgoogle.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 服务器端运行时的服务托管机制。

文档更新时间: 2023-09-02 06:09   作者:Minho