brpc/docs/cn/error_code.md
2022-12-14 20:13:26 +08:00

8.8 KiB
Raw Blame History

English version

brpc使用brpc::Controller设置和获取一次RPC的参数Controller::ErrorCode()Controller::ErrorText()则分别是该次RPC的错误码和错误描述RPC结束后才能访问否则结果未定义。ErrorText()由Controller的基类google::protobuf::RpcController定义ErrorCode()则是brpc::Controller定义的。Controller还有个Failed()方法告知该次RPC是否失败这三者的关系是

  • 当Failed()为true时ErrorCode()一定为非0ErrorText()则为非空。
  • 当Failed()为false时ErrorCode()一定为0ErrorText()未定义目前在brpc中会为空但你最好不要依赖这个事实

标记RPC为错误

brpc的client端和server端都有Controller都可以通过SetFailed()修改其中的ErrorCode和ErrorText。当多次调用一个Controller的SetFailed时ErrorCode会被覆盖ErrorText则是添加而不是覆盖。在client端框架会额外加上第几次重试在server端框架会额外加上server的地址信息。

client端Controller的SetFailed()常由框架调用比如发送request失败接收到的response不符合要求等等。只有在进行较复杂的访问操作时用户才可能需要设置client端的错误比如在访问后端前做额外的请求检查发现有错误时把RPC设置为失败。

server端Controller的SetFailed()常由用户在服务回调中调用。当处理过程发生错误时一般调用SetFailed()并释放资源后就return了。框架会把错误码和错误信息按交互协议填入responseclient端的框架收到后会填入它那边的Controller中从而让用户在RPC结束后取到。需要注意的是server端在SetFailed()时默认不打印送往client的错误。打日志是比较慢的在繁忙的线上磁盘上很容易出现巨大的lag。一个错误频发的client容易减慢整个server的速度而影响到其他的client理论上来说这甚至能成为一种攻击手段。对于希望在server端看到错误信息的场景可以打开gflag -log_error_text(可动态开关)server会在每次失败的RPC后把对应Controller的ErrorText()打印出来。

brpc的错误码

brpc使用的所有ErrorCode都定义在errno.proto中,*SYS_*开头的来自linux系统与/usr/include/errno.h中定义的精确一致定义在proto中是为了跨语言。其余的是brpc自有的。

berror(error_code)可获得error_code的描述berror()可获得当前system errno的描述。ErrorText() != berror(ErrorCode())ErrorText()会包含更具体的错误信息。brpc默认包含berror你可以直接使用。

brpc中常见错误的打印内容列表如下

错误码 数值 重试 说明 日志
EAGAIN 11 同时发送的请求过多。软限,很少出现。 Resource temporarily unavailable
ENODATA 61 1. Naming Service返回的server列表为空 2. Naming Service某次变更时所有实例都发生了修改Naming Service更新LB的逻辑是先Remove再Add会存在很短时间内LB实例列表为空的情况 Fail to select server from xxx
ETIMEDOUT 110 连接超时。 Connection timed out
EHOSTDOWN 112 可能原因一、Naming Server返回的列表不为空但LB选不出可用的serverLB返回了EHOSTDOWN错误。具体可能原因a.Server正在退出中(返回了ELOGOFF) b. Server因为之前的某种失败而被封禁封禁的具体逻辑1. 对于单连接唯一的连接socket被SetFail即封禁SetFail在代码里出现非常多有很多种可能性触发 2. 对于连接池/短连接只有错误号满足does_error_affect_main_socket时ECONNREFUSEDENETUNREACHEHOSTUNREACH或EINVAL才会封禁 3. 封禁之后有CheckHealth线程健康检查就是尝试去连接一下检查间隔由SocketOptions的health_check_interval_s控制检查正常会解封。二、使用SingleServer方式初始化Channel没有LB唯一的一个连接为LOGOFF或者封禁状态同上 "Fail to select server from …" "Not connected to … yet"
ENOSERVICE 1001 找不到服务不太出现一般会返回ENOMETHOD。
ENOMETHOD 1002 找不到方法。 形式广泛,常见如"Fail to find method=..."
EREQUEST 1003 request序列化错误client端和server端都可能设置 形式广泛:"Missing required fields in request: …" "Fail to parse request message, …" "Bad request"
EAUTH 1004 认证失败 "Authentication failed"
ETOOMANYFAILS 1005 ParallelChannel中太多子channel失败 "%d/%d channels failed, fail_limit=%d"
EBACKUPREQUEST 1007 触发backup request时设置不会出现在ErrorCode中但可在/rpcz里看到 “reached backup timeout=%dms"
ERPCTIMEDOUT 1008 RPC超时 "reached timeout=%dms"
EFAILEDSOCKET 1009 RPC进行过程中TCP连接出现问题 "The socket was SetFailed"
EHTTP 1010 非2xx状态码的HTTP访问结果均认为失败并被设置为这个错误码。默认不重试可通过RetryPolicy定制 Bad http call
EOVERCROWDED 1011 连接上有过多的未发送数据,常由同时发起了过多的异步访问导致。可通过参数-socket_max_unwritten_bytes控制默认64MB。 The server is overcrowded
EINTERNAL 2001 Server端Controller.SetFailed没有指定错误码时使用的默认错误码。 "Internal Server Error"
ERESPONSE 2002 response解析错误client端和server端都可能设置 形式广泛"Missing required fields in response: ...""Fail to parse response message, ""Bad response"
ELOGOFF 2003 Server已经被Stop了 "Server is going to quit"
ELIMIT 2004 同时处理的请求数超过ServerOptions.max_concurrency了 "Reached server's limit=%d on concurrent requests"

自定义错误码

在C++/C中你可以通过宏、常量、enum等方式定义ErrorCode:

#define ESTOP -114                // C/C++
static const int EMYERROR = 30;   // C/C++
const int EMYERROR2 = -31;        // C++ only

如果你需要用berror返回这些新错误码的描述你可以在.cpp或.c文件的全局域中调用BAIDU_REGISTER_ERRNO(error_code, description)进行注册,比如:

BAIDU_REGISTER_ERRNO(ESTOP, "the thread is stopping")
BAIDU_REGISTER_ERRNO(EMYERROR, "my error")

strerror和strerror_r不认识使用BAIDU_REGISTER_ERRNO定义的错误码自然地printf类的函数中的%m也不能转化为对应的描述你必须使用%s并配以berror()。

errno = ESTOP;
printf("Describe errno: %m\n");                              // [Wrong] Describe errno: Unknown error -114
printf("Describe errno: %s\n", strerror_r(errno, NULL, 0));  // [Wrong] Describe errno: Unknown error -114
printf("Describe errno: %s\n", berror());                    // [Correct] Describe errno: the thread is stopping
printf("Describe errno: %s\n", berror(errno));               // [Correct] Describe errno: the thread is stopping

当同一个error code被重复注册时那么会出现链接错误

redefinition of `class BaiduErrnoHelper<30>'

或者在程序启动时会abort

Fail to define EMYERROR(30) which is already defined as `Read-only file system', abort

你得确保不同的模块对ErrorCode的理解是相同的否则当两个模块把一个错误码理解为不同的错误时它们之间的交互将出现无法预计的行为。为了防止这种情况出现你最好这么做

  • 优先使用系统错误码,它们的值和含义一般是固定不变的。
  • 多个交互的模块使用同一份错误码定义,防止后续修改时产生不一致。
  • 使用BAIDU_REGISTER_ERRNO描述新错误码以确保同一个进程内错误码是互斥的。