闽公网安备 35020302035485号
service OrderManagement {
rpc getOrder(google.protobuf.StringValue) returns (Order);
}
type OrderManagementClient interface {
GetOrder(ctx context.Context,
in *wrapperspb.StringValue, opts ...grpc.CallOption) (*Order, error)
}
type OrderManagementServer interface {
GetOrder(context.Context, *wrapperspb.StringValue) (*Order, error)
mustEmbedUnimplementedOrderManagementServer()
}
可以看到,虽然我们没有在pb文件中的接口定义设置error返回值,但生成出来的go代码是包含error返回值的。这非常符合Go语言的使用习惯:通常情况下我们定义多个error变量,并且在函数内返回,调用方可以使用errors.Is()或者errors.As()对函数的error进行判断var (
ParamsErr = errors.New("params err")
BizErr = errors.New("biz err")
)
func Invoke(i bool) error {
if i {
return ParamsErr
} else {
return BizErr
}
}
func main() {
err := Invoke(true)
if err != nil {
switch {
case errors.Is(err, ParamsErr):
log.Println("params error")
case errors.Is(err, BizErr):
log.Println("biz error")
}
}
}
但,在RPC场景下,我们还能进行error的值判断么?// 堆代码 duidaima.com
// common/errors.go
var ParamsErr = errors.New("params is not valid")
// server/main.go
func (s *OrderManagementImpl) GetOrder(ctx context.Context, orderId *wrapperspb.StringValue) (*pb.Order, error) {
return nil, common.ParamsErr
}
// client/main.go
retrievedOrder, err := client.GetOrder(ctx, &wrapperspb.StringValue{Value: "101"})
if err != nil && errors.Is(err, common.ParamsErr) {
// 不会走到这里,因为err和common.ParamsErr不相等
panic(err)
}
很明显,server和client并不在同一个进程甚至都不在同一个台机器上,所以errors.Is()或者errors.As()是没有办法做判断的{
"errno": 0,
"msg": "ok",
"data": {}
}
{
"errno": 1000,
"msg": "params error",
"data": {}
}
类似的,我们调整下我们pb定义:在返回值里携带错误信息service OrderManagement {
rpc getOrder(google.protobuf.StringValue) returns (GetOrderResp);
}
message GetOrderResp{
BizErrno errno = 1;
string msg = 2;
Order data = 3;
}
enum BizErrno {
Ok = 0;
ParamsErr = 1;
BizErr = 2;
}
message Order {
string id = 1;
repeated string items = 2;
string description = 3;
float price = 4;
string destination = 5;
}
于是在服务端实现的时候,我们可以返回对应数据或者错误状态码func (s *OrderManagementImpl) GetOrder(ctx context.Context, orderId *wrapperspb.StringValue) (*pb.GetOrderResp, error) {
ord, exists := orders[orderId.Value]
if exists {
return &pb.GetOrderResp{
Errno: pb.BizErrno_Ok,
Msg: "Ok",
Data: &ord,
}, nil
}
return &pb.GetOrderResp{
Errno: pb.BizErrno_ParamsErr,
Msg: "Order does not exist",
}, nil
}
在客户端可以判断返回值的错误码来区分错误,这是我们在常规RPC的常见做法// Get Order
resp, err := client.GetOrder(ctx, &wrapperspb.StringValue{Value: ""})
if err != nil {
panic(err)
}
if resp.Errno != pb.BizErrno_Ok {
panic(resp.Msg)
}
log.Print("GetOrder Response -> : ", resp.Data)
但,这么做有什么问题么?很明显,对于clinet侧来说,本身就可能遇到网络失败等错误,所以返回值(*GetOrderResp, error)包含error并不会非常突兀。但再看一眼server侧的实现,我们把错误枚举放在GetOrderResp中,此时返回的另一个error就变得非常尴尬了,该继续返回一个error呢,还是直接都返回nil呢?两者的功能极度重合!Code Number Description OK 0 成功 CANCELLED 1 调用取消 UNKNOWN 2 未知错误 ... ... ...message就是服务端需要告知客户端的一些错误详情信息
package main
import (
"errors"
"fmt"
"log"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func Invoke() {
ok := status.New(codes.OK, "ok")
fmt.Println(ok)
invalidArgument := status.New(codes.InvalidArgument, "invalid args")
fmt.Println(invalidArgument)
}
Status 和语言 Error 的互转
func (s *OrderManagementImpl) GetOrder(ctx context.Context, orderId *wrapperspb.StringValue) (*pb.Order, error) {
ord, exists := orders[orderId.Value]
if exists {
return &ord, status.New(codes.OK, "ok").Err()
}
return nil, status.New(codes.InvalidArgument,
"Order does not exist. order id: "+orderId.Value).Err()
}
到客户端这里我们再利用status.FromError(err)把error转回Statusorder, err := client.GetOrder(ctx, &wrapperspb.StringValue{Value: ""})
if err != nil {
// 转换有可能失败
st, ok := status.FromError(err)
if ok && st.Code() == codes.InvalidArgument {
log.Println(st.Code(), st.Message())
} else {
log.Println(err)
}
return
}
log.Print("GetOrder Response -> : ", order)
但,status真的够用么?类似于HTTP 状态码code的个数也是有限的。有个很大的问题就是 表达能力非常有限,所以我们需要一个能够额外传递业务错误信息字段的功能。// The `Status` type defines a logical error model that is suitable for
// different programming environments, including REST APIs and RPC APIs.
message Status {
// A simple error code that can be easily handled by the client. The
// actual error code is defined by `google.rpc.Code`.
int32 code = 1;
// A developer-facing human-readable error message in English. It should
// both explain the error and offer an actionable resolution to it.
string message = 2;
// Additional error information that the client code can use to handle
// the error, such as retry info or a help link.
repeated google.protobuf.Any details = 3;
}
可以看到比标准错误多了一个 details 数组字段, 而且这个字段是 Any 类型, 支持我们自行扩展。func InvokRPC() error {
st := status.New(codes.InvalidArgument, "invalid args")
if details, err := st.WithDetails(&pb.BizError{}); err == nil {
return details.Err()
}
return st.Err()
}
前面提到details 数组字段, 而且这个字段是 Any 类型, 支持我们自行扩展。同时,Google API 为错误详细信息定义了一组标准错误负载,您可在 google/rpc/error_details.proto 中找到这些错误负载。它们涵盖了对于 API 错误的最常见需求,例如配额失败和无效参数。与错误代码一样,开发者应尽可能使用这些标准载荷。BadRequest:描述客户端请求中的违规行为,这些内容可能在以下方法中返回:Code.INVALID_ARGUMENT
package main
import (
"fmt"
pb "github.com/liangwt/note/grpc/error_handling/error"
epb "google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func (s *OrderManagementImpl) GetOrder(ctx context.Context, orderId *wrapperspb.StringValue) (*pb.Order, error) {
ord, exists := orders[orderId.Value]
if exists {
return &ord, status.New(codes.OK, "ok").Err()
}
st := status.New(codes.InvalidArgument,
"Order does not exist. order id: "+orderId.Value)
details, err := st.WithDetails(
&epb.BadRequest_FieldViolation{
Field: "ID",
Description: fmt.Sprintf("Order ID received is not valid"),
},
)
if err == nil {
return nil, details.Err()
}
return nil, st.Err()
}
客户端// Get Order
order, err := client.GetOrder(ctx, &wrapperspb.StringValue{Value: ""})
if err != nil {
st, ok := status.FromError(err)
if !ok {
log.Println(err)
return
}
switch st.Code() {
case codes.InvalidArgument:
for _, d := range st.Details() {
switch info := d.(type) {
case *epb.BadRequest_FieldViolation:
log.Printf("Request Field Invalid: %s", info)
default:
log.Printf("Unexpected error type: %s", info)
}
}
default:
log.Printf("Unhandled error : %s ", st.String())
}
return
}
log.Print("GetOrder Response -> : ", order)
引申问题