写给go开发者的Tars教程-错误处理
本篇为【写给go开发者的Tars教程】系列第四篇
第一篇:Tars协议基础
第二篇:通信模式
第三篇:拦截器
第四篇:错误处理
本系列将持续更新,欢迎关注👏获取实时通知
基本错误处理
首先回顾下tars文件和生成出来的client与server端的接口
module order {
struct Order {
1 require string id;
2 optional vector<string> items;
3 optional string description;
4 require float price;
5 optional string destination;
};
interface OrderManagement {
Order getOrder(string orderId);
};
};
type OrderManagement struct {
servant m.Servant
}
func (obj *OrderManagement) GetOrder(orderId string, opts ...map[string]string) (Order, error) {
return obj.GetOrderWithContext(context.Background(), orderId, opts...)
}
func (obj *OrderManagement) GetOrderWithContext(tarsCtx context.Context, orderId string, opts ...map[string]string) (ret Order, err error) {
// ......
return ret, nil
}
func (obj *OrderManagement) GetOrderOneWayWithContext(tarsCtx context.Context, orderId string, opts ...map[string]string) (ret Order, err error) {
// ......
return ret, nil
}
type OrderManagementServant interface {
GetOrder(orderId string) (ret Order, err error)
}
type OrderManagementServantWithContext interface {
GetOrder(tarsCtx context.Context, orderId string) (ret Order, err error)
}
可以看到,虽然我们没有在tars文件中的接口定义设置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 TestError(t *testing.T) {
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的值判断么?
// errors/errors.go
var ParamsErr = errors.New("params err")
// internal/servant/order.go
func (o *OrderCtx) GetOrder(tarsCtx context.Context, orderId string) (ret order.Order, err error) {
return ret, errors.ParamsErr
}
// errors/errors_test.go
func TestRPCErrors(t *testing.T) {
comm := tars.GetCommunicator()
client := new(order.OrderManagement)
obj := "Test.OrderServer.OrderObj@tcp -h 127.0.0.1 -p 8080 -t 60000"
comm.StringToProxy(obj, client)
retrievedOrder, err := client.GetOrderWithContext(context.Background(), "1")
if err != nil && errors.Is(err, ParamsErr) {
// 不会走到这里,因为err和ParamsErr不相等
t.Fatal(err)
}
t.Logf("order: %+v", retrievedOrder)
}
很明显,server
和client
并不在同一个进程甚至都不在同一个台机器上,所以errors.Is()
或者errors.As()
是没有办法做这样判断的
业务错误码
那么如何做?在http的服务中,我们会使用错误码的方式来区分不同错误,通过判断errno
来区分不同错误
{
"errno": 0,
"msg": "ok",
"data": {}
}
{
"errno": 1000,
"msg": "params error",
"data": {}
}
类似的,我们调整下我们tars定义:在返回值里携带错误信息
module order {
struct Order {
1 require string id;
2 optional vector<string> items;
3 optional string description;
4 require float price;
5 optional string destination;
};
enum BizErrno {
Ok = 0,
ParamsErr = 1,
BizErr = 2,
};
struct GetOrderResp {
1 require BizErrno errno;
2 optional string msg;
3 optional Order data;
};
interface OrderManagement {
GetOrderResp getOrder(string orderId);
};
};
于是在服务端实现的时候,我们可以返回对应数据或者错误状态码
func (o *OrderCtx) GetOrder(tarsCtx context.Context, orderId string) (ret order.GetOrderResp, err error) {
ord, exists := orders[orderId]
if exists {
return order.GetOrderResp{
Errno: order.BizErrno_Ok,
Msg: "OK",
Data: ord,
}, nil
}
return order.GetOrderResp{
Errno: order.BizErrno_ParamsErr,
Msg: "Order does not exist",
}, nil
}
在客户端可以判断返回值的错误码来区分错误,这是我们在常规RPC的常见做法
// Get Order
resp, err := client.GetOrderWithContext(context.Background(), "1")
if err != nil {
t.Fatal(err)
}
if resp.Errno != order.BizErrno_Ok {
t.Fatal(resp.Msg)
}
t.Logf("GetOrder Response -> : %+v", resp.Data)
🌿 但,这么做有什么问题么?
很明显,对于clinet侧来说,本身就可能遇到网络失败等错误,所以返回值(ret order.GetOrderResp, err error)
包含error
并不会非常突兀
但再看一眼server侧的实现,我们把错误枚举放在GetOrderResp
中,此时返回的另一个error
就变得非常尴尬了,该继续返回一个error
呢,还是直接都返回nil
呢?两者的功能极度重合
那有什么办法既能利用上error
这个返回值,又能让client
端枚举出不同错误么?一个非常直观的想法:让error
里记录枚举值就可以了!
但我们都知道Go里的error
是只有一个string
的,可以携带的信息相当有限,如何传递足够多的信息呢?TarsGo
官方提供了github.com/TarsCloud/TarsGo/tars.Error
的解决方案
使用 tars.Error
处理错误
TarsGo
提供了github.com/TarsCloud/TarsGo/tars.Error
来表示错误,这个结构包含了 Code
和 Message
两个字段
🌲 code
是类似于http status code
的一系列错误类型的枚举,所有语言 sdk 都会内置这个枚举列表
虽然总共预定义了16个code
,但TarsGo
框架并没有使用这些code,框架底层定义了自己的错误码。
Code | Number | Description |
---|---|---|
OK | 0 | 成功 |
CANCELLED | 1 | 调用取消 |
UNKNOWN | 2 | 未知错误 |
… | … | … |
🌲 message
就是服务端需要告知客户端的一些错误详情信息
func TestTarsError(t *testing.T) {
ok := tars.Errorf(basef.TARSSERVERSUCCESS, "ok")
fmt.Println(ok)
serverNoFuncErr := tars.Errorf(basef.TARSSERVERNOFUNCERR, "服务器端没有该函数")
fmt.Println(serverNoFuncErr)
}
tars.Error
和语言 Error
的互转
上文提到无论是server
和client
返回的都是error
,TarsGo
框架提供的tars.Error
已经实现了error
接口。
所以在服务端可以利用tars.Error
并返回
func (o *OrderCtx) GetOrder(tarsCtx context.Context, orderId string) (ret order.Order, err error) {
ord, exists := orders[orderId]
if exists {
return ord, nil
}
return ret, tars.Errorf(http.StatusNotFound, "Order does not exist: %v", orderId)
}
到客户端这里我们再利用errors.As(err, &tarsErr)
把error
转回tars.Error
// Get Order
ord, err := client.GetOrderWithContext(context.Background(), "2")
if err != nil {
var tarsErr = new(tars.Error)
if ok := errors.As(err, &tarsErr); ok && tarsErr.Code == http.StatusNotFound {
t.Logf("code: %d, msg: %s", tarsErr.Code, tarsErr.Message)
} else {
t.Fatal(err)
}
return
}
t.Logf("GetOrder Response -> : %+v", ord)
Tars框架层错误码
module basef
{
////////////////////////////////////////////////////////////////
// TARS定义的返回码
const int TARSSERVERSUCCESS = 0; //服务器端处理成功
const int TARSSERVERDECODEERR = -1; //服务器端解码异常
const int TARSSERVERENCODEERR = -2; //服务器端编码异常
const int TARSSERVERNOFUNCERR = -3; //服务器端没有该函数
const int TARSSERVERNOSERVANTERR = -4; //服务器端没有该Servant对象
const int TARSSERVERRESETGRID = -5; //服务器端灰度状态不一致
const int TARSSERVERQUEUETIMEOUT = -6; //服务器队列超过限制
const int TARSASYNCCALLTIMEOUT = -7; //异步调用超时
const int TARSINVOKETIMEOUT = -7; //调用超时
const int TARSPROXYCONNECTERR = -8; //proxy链接异常
const int TARSSERVEROVERLOAD = -9; //服务器端超负载,超过队列长度
const int TARSADAPTERNULL = -10; //客户端选路为空,服务不存在或者所有服务down掉了
const int TARSINVOKEBYINVALIDESET = -11; //客户端按set规则调用非法
const int TARSCLIENTDECODEERR = -12; //客户端解码异常
const int TARSSENDREQUESTERR = -13; //发送出错
const int TARSSERVERUNKNOWNERR = -99; //服务器端位置异常
};
排除框架层的错误码,我们可以定义无数的业务层错误码,结合tars.Error
进行合理的业务错误处理。
总结
我们先介绍了TarsGo最基本的错误处理方式:返回error
。
之后我们又介绍了一种能够携带更多错误信息的方式:tars.Error
,它包含Code
、Message
等信息,通过tars.Error
实现了error
接口可以直接用来传输错误。