错误码

qianmoQqianmoQ· 更新于 2026-10-05· 阅读 15 分钟· 0 次阅读

登录后可跨设备保存划线和私人笔记登录

错误码

了解 bRPC 客户端的错误码。

brpc 使用 brpc::Controller 来为一次 RPC 设置和获取参数。Controller::ErrorCode() 和 Controller::ErrorText() 分别返回该 RPC 的错误码和错误描述,仅在 RPC 完成后才能访问,否则结果未定义。ErrorText() 由 Controller 的基类定义:google::protobuf::RpcController,而 ErrorCode() 由 brpc::Controller 定义。Controller 还提供了一个方法 Failed(),用于判断 RPC 是否失败。这三个方法之间的关系:

  • 当 Failed() 为 true 时,ErrorCode() 必须非零,且 ErrorText() 必须非空。
  • 当 Failed() 为 false 时,ErrorCode() 为 0,且 ErrorText() 未定义(目前在 brpc 中为空,但你最好不要依赖这一点)。

将 RPC 标记为失败

brpc 中的客户端和服务端都拥有 Controller,可以通过 setFailed() 来设置,以修改 ErrorCode 和 ErrorText。多次调用 Controller::SetFailed 会保留最后一个 ErrorCode,而 拼接 各次的 ErrorText,而不是只保留最后一个。框架会为 ErrorText 添加额外前缀来完善它:客户端侧为重试次数,服务端侧为服务器地址。

客户端侧的 Controller::SetFailed() 通常由框架调用,例如发送失败、响应不完整等情况。在某些情况下也可能在客户端侧设置错误。例如,如果发送请求前的额外校验失败,你可以为该 RPC 设置错误。

服务端侧的 Controller::SetFailed() 通常由用户在服务回调中调用。一般来说,当出现错误时,用户会调用 SetFailed(),释放所有资源并从回调中返回。框架会根据通信协议把错误码和错误消息填充到响应中。当响应被收到时,其中的错误会被设置到客户端侧的 Controller 中,以便用户在 RPC 结束后获取它们。请注意,服务端默认不会向客户端打印错误,因为频繁的日志输出可能因繁重的磁盘 IO 而显著影响服务器性能。一个疯狂产生错误的客户端会拖慢整个服务器,影响所有其他客户端,甚至可能成为针对服务器的攻击手段。如果你确实希望在服务器上查看错误信息,可以开启 gflag -log_error_text(可在运行时修改),服务器会记录每个失败 RPC 对应 Controller 的 ErrorText。

brpc 中的错误码

brpc 中的所有错误码都定义在 errno.proto 中,其中以 SYS_ 开头的错误码由 Linux 系统定义,与 /usr/include/errno.h 中定义的完全相同。我们将其放在 .proto 中是为了跨语言。其余错误码由 brpc 定义。

berror(error_code) 获取错误码的描述,berror() 获取当前 system errno 的描述。请注意 ErrorText() != berror(ErorCode()),因为 ErrorText() 包含更具体的信息。brpc 默认包含 berror,因此你可以直接在项目中使用它。

下表列出了常见错误码及其描述:

| 错误码 | 值 | 是否重试 | 描述 | 日志消息 |

|
|----------------|-------|-------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------|
| EAGAIN | 11 | 是 | 同时请求过多,因其为软限制,极少发生。 | Resource temporarily unavailable |
| ENODATA | 61 | 是 | 1. Naming Service 返回的服务器列表为空。2. 当 Naming Service 发生变更且所有实例都被修改时,Naming Service 会先执行 Remove all 再执行 Add all 来更新 LB,在此期间 LB 的实例列表可能在短时间内变为空。 | Fail to select server from xxx |
| ETIMEDOUT | 110 | 是 | 连接超时。 | Connection timed out |
| EHOSTDOWN | 112 | 是 | 可能的原因:A. Naming Server 返回的列表不为空,但 LB 无法选择可用服务器,于是 LB 返回 EHOSTDOWN 错误。具体可能原因:a. 服务器正在退出(返回 ELOGOFF)b. 服务器因之前的某个故障而被阻塞,阻塞的具体逻辑:1. 对于单连接类型,唯一的连接 socket 被 SetFail 阻塞,代码中有多处调用 SetFailed 会触发该阻塞。2. 对于池化/短连接类型,只有当错误码满足 does_error_affect_main_socket(ECONNREFUSED、ENETUNREACH、EHOSTUNREACH 或 EINVAL)时才会被阻塞 3. 阻塞后,会有一个 CheckHealth 线程执行健康检查,仅尝试连接,检查间隔由 SocketOptions 的 health_check_interval_s 控制,连接成功后 Socket 将解除阻塞。B. 使用 SingleServer 方法初始化 Channel(不使用 LB),且唯一的连接处于 LOGOFF 或被阻塞状态(同上) | “Fail to select server from …” “Not connected to … yet” |
| ENOSERVICE | 1001 | 否 | 无法定位服务,极少发生,通常表现为 ENOMETHOD | |
| ENOMETHOD | 1002 | 否 | 无法定位方法。 | Misc forms, common ones are “Fail to find method=…” |
| EREQUEST | 1003 | 否 | 请求序列化失败,可能在客户端或服务端被设置 | Misc forms: “Missing required fields in request: …” “Fail to parse request message, …” “Bad request” |
| EAUTH | 1004 | 否 | 认证失败 | “Authentication failed” |
| ETOOMANYFAILS | 1005 | 否 | ParallelChannel 内部子通道失败过多 | “%d/%d channels failed, fail_limit=%d” |
| EBACKUPREQUEST | 1007 | 是 | 触发备份请求时被设置。不会由 ErrorCode() 直接返回,可在 /rpcz 的 span 中查看 | “reached backup timeout=%dms” |
| ERPCTIMEDOUT | 1008 | 否 | RPC 超时。 | “reached timeout=%dms” |
| EFAILEDSOCKET | 1009 | 是 | RPC 过程中连接断开 | “The socket was SetFailed” |
| EHTTP | 1010 | 否 | 状态码非 2xx 的 HTTP 响应被视为失败,并设置此错误码。默认不重试,可通过自定义 RetryPolicy 修改。 | Bad http call |
| EOVERCROWDED | 1011 | 是 | 发送端待缓冲的消息过多。通常由大量并发异步请求引起。可通过 -socket_max_unwritten_bytes 修改,默认为 8MB。 | The server is overcrowded |
| EINTERNAL | 2001 | 否 | Controller::SetFailed 未指定错误码时的默认错误。 | Internal Server Error |
| ERESPONSE | 2002 | 否 | 响应序列化失败,可能在客户端或服务端被设置 | Misc forms: “Missing required fields in response: …” “Fail to parse response message, " “Bad response” |
| ELOGOFF | 2003 | 是 | 服务器已停止 | “Server is going to quit” |
| ELIMIT | 2004 | 是 | 正在并发处理的请求数量超过 ServerOptions.max_concurrency | “Reached server’s limit=%d on concurrent requests” |

用户自定义错误码

在 C/C++ 中,错误码可以通过宏、常量或枚举来定义:

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

如果需要通过 berror 获取错误描述,请通过 BAIDU_REGISTER_ERRNO(error_code, description) 在 C/CPP 文件的全局作用域中注册它,例如:

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

请注意,strerror 和 strerror_r 不会识别 BAIDU_REGISTER_ERRNO 定义的错误码,printf 中使用的 %m 也不会。你必须使用与 berror 配对的 %s:

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

当错误码的注册发生重复时,如果该错误码是在 C++ 中定义的,则会产生链接错误:

redefinition of `class BaiduErrnoHelper<30>'

或者程序在启动前中止:

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

你必须确保不同的模块对同一个 ErrorCode 有一致的理解。否则,两个对错误码解释不同的模块之间的交互行为可能是未定义的。为了防止这种情况发生,建议你遵循以下原则:

  • 优先使用具有固定取值和含义的系统错误码,通常而言它们是通用的。
  • 在多个模块之间共享错误码的定义代码,以防止修改之后出现不一致。
  • 使用 BAIDU_REGISTER_ERRNO 来描述新增错误码,从而确保在同一个进程内,同一个错误码只被定义一次。

最后修改于 2022 年 1 月 9 日:[基于 hugo 的新版 brpc 网站(94b25d711)]](https://github.com/apache/brpc-website/commit/94b25d7110944f3d4b2071b5188c691b23ffe3a9)

评论

登录后参与评论

正在加载评论…