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

82 lines
8.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[English version](../en/error_code.md)
brpc使用[brpc::Controller](https://github.com/brpc/brpc/blob/master/src/brpc/controller.h)设置和获取一次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](https://github.com/brpc/brpc/blob/master/src/brpc/errno.proto)中,*SYS_*开头的来自linux系统与/usr/include/errno.h中定义的精确一致定义在proto中是为了跨语言。其余的是brpc自有的。
[berror(error_code)](https://github.com/brpc/brpc/blob/master/src/butil/errno.h)可获得error_code的描述berror()可获得当前[system errno](http://www.cplusplus.com/reference/cerrno/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:
```c++
#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)进行注册,比如:
```c++
BAIDU_REGISTER_ERRNO(ESTOP, "the thread is stopping")
BAIDU_REGISTER_ERRNO(EMYERROR, "my error")
```
strerror和strerror_r不认识使用BAIDU_REGISTER_ERRNO定义的错误码自然地printf类的函数中的%m也不能转化为对应的描述你必须使用%s并配以berror()。
```c++
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描述新错误码以确保同一个进程内错误码是互斥的。