今天来讲一讲 gRPC 错误处理的方式,以及如何自定义错误处理。当发起 gRPC 调用时,客户端会接受成功状态的响应或带有错误信息状态的错误响应。 考虑程序的健壮性,我们需要在编写客户端处理信息时,要处理所有可能的错误。编写服务端代码也要处理错误,并构建合适的错误状态码。

当 gRPC 发生错误时,会返回一个错误码。并附带一条可选的信息, 错误状态信息由一个整形状态码和一条字符串消息组成,适用于不同的语言实现。

下图展示了 gRPC 内置的错误码:

img.png

缺陷

gRPC 提供的错误模型非常有限,并且与底层的数据格式无关,最常用的数据格式就是 protocol buffers。 如果使用了 protocol buffers, google.rpc 提供了更丰富的错误模型,但语言兼容性待测试。

错误处理例子

接下来继续沿用前面的代码,来讲解如何运用错误处理。 假如我们需要在订单添加处理中处理非法 ID 请求。 如果我们传了一个不合法的 ID 如 -1,需要返回错误给客户端消费者。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
func (s *server) AddOrder(ctx context.Context, orderReq *pb.Order) (*wrappers.StringValue, error) {
	if orderReq.Id == "-1" {
		log.Printf("Order ID is vaild : %s", orderReq.Id)

		errorStatus := status.New(codes.InvalidArgument, "Order ID is not valid")
		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()
	}
	orderMap[orderReq.Id] = *orderReq
	log.Println("Order : ", orderReq.Id, " -> Added")
	return &wrapper.StringValue{Value: "Order Added: " + orderReq.Id}, nil
}

通过 status 包可以很方便的创建所需的错误码和相应错误详情。使用 Google API 的相应包可以设置更丰富的错误详情。

客户端处理错误,只需处理相应 RPC 请求返回的错误即可。接下来的代码展示如何处理前面的 AddOrder 方法返回的错误。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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)
	}

在 gRPC 处理中,尽可能使用适当的 gRPC 错误码和丰富的错误模型,这样可以大大的提供系统的健壮性。

参考