学习和探讨在微服务中如何传递error
0x01 error 认知
通过阅读如何优雅的在Golang中进行错误处理文章我们可以知道对error我们是需要分类认知的。
error的阻断性
- 可恢复error:代码逻辑可以继续执行
- 不可恢复error:代码逻辑无法继续执行,有crash发生
边界分类
- 用户边界:用户感知error,属于业务逻辑上需要提示给用户的错误
- 非用户边界:用户无法感知,例如宕机,服务调用链阻断的情况。
0x02 处理方式
一把梭
所有错误都放到rpc的response消息头中,例如在使用grpc的时候,如果handler中发生错误,我们可以将错误放到status中,然后通过handler函数的error返回值,传递到框架中然后由grpc框架传递回调用方。调用方收到错误后,调用方不知道错误的具体属于哪一类错误,只知道出错了。如果需要对错误进行分类处理,调用方需要解error信息,然后判断code,再决定消息是否传递,或者返回其它类型的错误。
rsp, err := client.func(ctx, req)
if err != nil{
return
}
对错误进行分级处理
如果需要将调用错误和业务错误分开传递;例如:数据库不可用这样的系统错误和昵称不合法这样的业务错误分开处理。那么我们在定义接口的时候,需要在response消息中有一个字段在存储code。例如:
//使用Google的rpc类型定义
message Response {
google.rpc.Status status = 1;
}
//使用基本类型定义
message Response {
int32 code = 1;
}
当发生一些阻断性错误的时候,将错误放到handler函数的error返回中,如果发生业务错误,需要用户感知提示用户修正的时候,handler函数的error返回nil,在response中填入对应的错误。在用户端的时候需要两次判断:
rsp, err := client.func(ctx, req)
if err != nil{
return
}
if rsp.Code != OK {
return
}
0x03 error code 处理参考
bilibili
哔哩哔哩 的接入层是基于HTTP的restfull,后端服务层则是rpc调用。通过阅读B站被迫开源的主站代码和主动开源的kratos 框架代码,可以知道,B站在后端服务层采用的是一把梭的方式,错误直接塞到handler的error返回值中。在接入层中,通过对后端服务回来的错误解码,再进行错误分类处理。为此B站写了protobuf的插件工具,对xxxErrCode 的枚举类型自动生成status 变量,并改造了gin,增加对rpc错误的解码和分类返回处理。
通过阅读Google的apis仓库协议的定义,可以知道猜测Google是采用的错误分类处理;例如下面的定义:
// Response message for an ad group ad labels mutate.
message MutateAdGroupAdLabelsResponse {
// Errors that pertain to operation failures in the partial failure mode.
// Returned only when partial_failure = true and all errors occur inside the
// operations. If any errors occur outside the operations (e.g. auth errors),
// we return an RPC level error.
google.rpc.Status partial_failure_error = 3;
// All results for the mutate.
repeated MutateAdGroupAdLabelResult results = 2;
}
将错误分为rpc级别错误和业务错误;如果有业务错误需要判断的时候,就提供response中提供错误字段,其他情况下就使用rpc response消息头传递rpc级别错误。
0x04 小结
个人比较喜欢B的套件,方便快捷。只能看到Google公开的出来的API 是错误分类,不知道其后端服务是如何处理的,也许也全都往handler的error中丢也不一定。error的处理方式多样,不定,但是一定要对error的分类有认知,这样在处理错误的时候,才知道什么时候需要code,啥时候只需要error。