错误处理与生命周期
如何识别 RPC 的失败层次?
- RPC 异常: 请求没有按预期完成远程调用,例如超时、无实例、网络错误或治理拒绝;
- 业务异常: 请求已到达并完成业务判断,但结果不满足业务规则,例如用户不存在;
| 类别 | 含义 | 处理 |
|---|---|---|
| BizStatusError | 请求到达并完成业务判断 | 按业务码处理,不盲目重试 |
| timeout | 超过连接或调用预算 | 判断阶段、幂等与剩余预算 |
| discovery/LB | 无实例或选择失败 | 检查控制面与实例状态 |
| remote/network | 对端或网络异常 | 保留错误链并结合链路定位 |
| circuit/overlimit/ACL | 治理主动拒绝 | 降载、退避或返回明确状态 |
| panic/internal | Handler 或框架内部异常 | 报警并定位堆栈 |
如何传递并读取业务异常?
- 业务异常: Handler 用
kerrors.NewBizStatusError(code, message)或 WithExtra 版本表达业务拒绝;Client 用kerrors.FromBizStatusError(err)读取 code、message、extra; - 传输前提: Thrift / Kitex Protobuf 需要 TTHeader 及双方 MetaHandler;gRPC 使用 HTTP/2 元信息,不能假设任意 Thrift 对端都认识 BizStatus;
- 中间件: Handler 的 BizStatus 被框架放入 RPCInfo,
next可能返回 nil;读取rpcinfo.GetRPCInfo(ctx).Invocation().BizStatusErr()才能统计业务拒绝; - 监控: RPC 完成与业务成功是两个维度;不用 ErrorHandler 把业务拒绝改成网络失败;
- 消息: 业务码稳定、文本可公开;SQL、DSN 和堆栈只在服务端记录;
如何安全地异步读取 RPCInfo?
- RPCInfo: 一次调用的服务、方法、地址、耗时与配置对象;请求结束后可能进入
sync.Pool被其他请求复用; - 冻结: 异步读取前复制 RPCInfo,异步任务仍需独立管理 deadline 与退出,不把冻结当作无限延长整个请求;
frozen := rpcinfo.FreezeRPCInfo(ctx)
go func() {
ri := rpcinfo.GetRPCInfo(frozen)
log.Println(ri.To().Method())
}()
如何处理 Handler panic?
- 恢复: Kitex 将可恢复的 Handler panic 转为带
[happened in biz handler]的错误与堆栈;按故障处理,不用 panic 表达正常业务拒绝; - 边界: 并发写 map 等 fatal runtime error 不属于可恢复的普通 panic,应通过并发设计和 race 检查预防;
如何停止服务并释放资源?
- 优雅退出: 服务停止接收新流量,但给正在处理的请求留出完成时间,再关闭进程和资源;
- 第一步: readiness 先变为不可接收新流量;
- 第二步: Registry/编排平台完成摘流;
- 第三步: Server 等待在途请求与 stream;
- 第四步: 达到
ExitWaitTime后强制结束; - 第五步: 关闭 tracer/exporter、注册中心 Client 与其他资源;