关于错误码的那点事 - 知乎 https://zhuanlan.zhihu.com/p/411726319
关于错误码的那点事
一、什么是错误码
错误码一般情况分为对外错误码,系统内部错误码。
对外错误码常应用在一些开放接口,比如http接口,rpc接口等,通过错误码的形式给予上游更加友好的错误提示以及错误描述。
系统内部错误码,存在于关系紧密的微服务之间、或者程序的上下游中。因为某种业务错误、或者系统不可用造成的错误,开发人员可以根据错误码、错误信息进行具体定位;或者根据上游根据错误码做业务逻辑判断,从而保证整体流程完整性。
总而言之,错误码的作用: 指出错误的原因,快速定位问题,指导上游系统做出正确的业务判断,引导用户进行正确的操作。因此构建一个通用且架构清晰的错误码体系是一件很有必要的事情。
那么怎么定义一种对外对内都友好的错误码呢?至今业内也并没有一个比较好的方案或者规范。这里结合新老支付系统融合,逐步进行摸索。
二、支付系统错误码现状
由于历史原因,公司内部目前有两套运行中的钱包系统。为了提高钱包系统可用性以及性能,对两套钱包系统进行迁移整合升级优化。但迁移合并两套钱包系统的途中,发现两套钱包系统、以及之前现存的已经优化过的错误码之前存在冲突、类型不一致、定义混乱、随意性高等问题。
由于其一钱包系统之前由其他团队开发维护,后期也对其进行重构过,这造成了新钱包系统的错误码定义规则存在两套;从代码角度来看,新老钱包系统在设计之初的时候,对错误码的定义都各自定义了一个比较合适的规范,但是在后期开发维护中,越来越少的开发人员去遵循规范定义错误码,最终造成了由错误描述决定错误码的现象。而目前新系统中错误码,在设计之初,并未考虑到未来整合带来的错误码冲突等问题,造成了部分错误码重合,语义大相径庭的问题,所以重新定义错误码规范也是迫在眉睫。
钱包系统错误码现状:
钱包一错误码定义: 6位错误码。 首位表示错误类型,区分系统级别、校验、rpc服务调用错误。 这种设计方式,第一位是明确的,后面5位都是预留的错误码,长度足够,满足后续错误码的增加;缺点错误码对应的类型太少,会出现相同语义的错误码,对应不同的首位数字。后重构版本的错误码修改了原有的错误码位数,修改后7位。造成了该系统的接口层,6位错误码,7位错误码混乱,并且错误码没有分类,全部顺序后排。
钱包二错误码定义: 提供错误码工具类,规定了大多数钱包常用的错误码;然后以此为基础,进行增加。优点: 错误码分类清晰,结构明了,缺点: 不容易根据错误码定位具体错误。
简单来说,目前钱包系统错误码存在以下问题
1. 错误码字段类型定义不一致。有的定义数值类型,有的定义字符串类型
2.错误码重合问题。相同的错误码,在不同系统中有着不同的语义
三、调研业内接口定义
鉴于当前支付系统错误码的痛点,如错误码应该用数值类型还是字符串类型,长度命名格式是怎样的等问题,调研了多家大厂的API规范以及接口定义,来探索适用于支付系统的错误码规范。
- 微信支付
a. 参考微信支付v2接口
微信支付v2接口: 协议:http, content-type: text/xml
参考链接: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
原格式是xml,为了更加直观,这里先加工为json格式
{
"return_code":"SUCCESS", // SUCCESS/FAIL 此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断
"return_msg":"OK",
"result_code":"SUCCESS", // SUCCESS/FAIL 标识业务成功失败
"err_code":"SYSTEMERROR", // 当result_code为FAIL的时候,该值返回业务错误码
"err_code_des":"系统错误"
}
微信支付错误码结构为三级结构:
一级、公共错误码(网关层) 。该层仅返回通信成功失败信息
二级、业务错误码 (总): 表示该业务是否处理成功
三级、具体业务错误码
业务错误码举例:
error_code | err_code_des |
---|---|
NOAUTH | 商户无此接口权限 |
INVALID_REQUEST | 参数错误 |
NOTENOUGH | 余额不足 |
b.参考微信支付v3接口
微信支付v3接口: 协议:http, content-type: application/json
参考链接: https://pay.weixin.qq.com/wiki/doc/apiv3/Share/error_code.shtml
err_code | http_code | err_msg |
---|---|---|
USERPAYING | 202 | 用户正在付款中 |
OUT_TRADE_NO_USED | 403 | 商户订单号重复 |
ORDERNOTEXIST | 404 | 订单不存在 |
根据微信v3的接口文档可知,微信支付返回的业务错误的同时,会返回一个与之对应的httpcode。当http_code的状态码在[200,300)之间,认为该请求是有合法的返回的;当大于300时,判断接口返回一定是有异常错误的。微信支付V3 sdk封装了http_code与err_code的相关处理。
对外暴露http接口,在抛出业务错误的同时,也要抛出相同语义的httpcode,这就需要开发人员明确httpcode语义。 这样的劣势在于学习成本较高,依赖开发人员对httpcode的熟练程度,可能会出现 httpcode语义与业务错误语义不一致的现象。
2.支付宝错误码定义
参考链接: https://opendocs.alipay.com/open/common/105806
不同业务的业务错误码: 举一个接口的例子 https://opendocs.alipay.com/apis#%E4%B8%9A%E5%8A%A1%E9%94%99%E8%AF%AF%E7%A0%81
sub_code、sub_msg这两个参数标识支付宝返回的业务错误码、业务错误信息;
{
"code":"",//网关返回码
"msg":"",//网关返回码
"sub_code":"ACQ.INVALID_PARAMETER",
"sub_msg":"参数无效"
}
支付宝的错误码定义 与 微信支付的v2接口定义风格比较相像。
支付宝错误码结构分为两级: 一级: 网关, 二级: 业务错误码。
请求支付宝接口,先通过支付宝网关系统,网关系统进行验签、加解密、流控等功能,如果网关校验出错,则抛出公共错误码。网关校验成功之后,交给下游业务系统,sub_code都是语义明确的错误码,以及错误描述。
3. google Api规范
参考链接: https://www.bookstack.cn/read/API-design-guide/API-design-guide-07-%E9%94%99%E8%AF%AF.md
google的错误码定义中,将返回码的结构定义为
message Status {
// A simple error code that can be easily handled by the client. The
// actual error code is defined by `google.rpc.Code`.
int32 code = 1;
// A developer-facing human-readable error message in English. It should
// both explain the error and offer an actionable resolution to it.
string message = 2;
// Additional error information that the client code can use to handle
// the error, such as retry delay or a help link.
repeated google.protobuf.Any details = 3;
}
其中 code: 是错误码,message: 是具体的错误信息,detail是根据这个错误,推荐调用方采取怎样的措施。
google对于错误码的定义,是比较简洁的。一个大类分配一个code;并不会因为多个相似的错误类型提供多个code。
google规范里details信息: 表示该错误的具体原因,定义参考: https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto
detail里面定义了 retryable信息 可以表示该错误码返回是可以进行重试的,并且给出推荐的重试延迟时间、QuotaFailure信息表示配额出错、限流超限等;badRequest可以详细的给出为什么会报这种错误 等等开发人员可以根据这样详细的错误返回码作出正确的反应。
4. 微博 规范
参考api: https://open.weibo.com/wiki/Error_code
{
"request" : "/statuses/home_timeline.json",
"error_code" : "20502",
"error" : "Need you follow uid."
}
20502 其中 错误码的组成: 1位 错误级别编号(系统、服务) + 2位服务模块(比如 网关、微博、评价、私信 比较像服务标识) + 2位 错误代码(自定义的错误编码)
2 | 05 | 02 |
---|---|---|
服务级错误(1为系统级错误) | 服务模块代码 | 具体错误代码 |
微博的错误码有明确的结构语义。将服务系统标识,显示在错误码中。这个操作与支付宝错误码构成有一点相像。第一位 标识服务级、系统级的字段,不确定是否是表示该错误是有网关抛出、还是说明确几种系统级别的错误,按分类抛出。
5.阿里巴巴的JAVA技术手册
阿里巴巴规范
第一点: 说明了错误码的特点: 要简单明了;
第二点: 错误码最好定义为string字符串类型: 来源 + 错误编号 (这样,错误码的数值可以携带更多的信息)
第三点: 避免随意添加错误码、避免直接暴露错误码给到用户侧
综上所述, 通过调研的这几家的对外文档来看,错误码的定义业内并没有一个统一的规范。但是大体的设计思路如下:
a. 错误码类型为字符串类型
b. 系统如果由网关→ 内部服务构成,则错误码分为两级
c. 错误码可以标识出抛出错误的来源服务。
d. 错误码可以抽象出来两种 公共错误码、业务错误码
四、思考与结论
结合上述调研的行业错误码定义,以及当前钱包系统现状。由于支付系统处于整体业务流程的最基础层,提供支付、付款等RPC能力,不存在直接对外暴露http接口的可能。所以微信支付、支付宝支付的三层错误码结构并不适用于钱包系统。
借鉴上述调研的api,错误码定义成字符串类型,更适用于业务场景,方便于后期的业务扩展。而google规范中对于错误码场景定义的统一规范,在一定程度上又降低了开发人员随意定义新错误码的可能。所以在error_code的场景定义上参考google-api规范
错误码error_code: 字符串. 前N位为当前业务所属领域标识,优势: 易于区分其他业务错误码,如果后期由于业务扩展、或者业务缩小带来的服务拆分合并,错误码仍然可以保持当前的设置。
提取公共常用的错误:
参考google规范进行归集: https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
RPC接口常用错误 | error_code |
---|---|
内部错误 | 115 |
该请求不支持 | 112 |
状态错误 | 110 |
请求频繁 | 109 |
没有权限类错误 | 108 |
权限校验错误 | 107 |
已存在类错误 | 106 |
不存在类错误 | 105 |
超时类错误 | 104 |
参数错误 | 103 |
未知错误 (比如调用下游接口出错,可以抛这个异常) |
101 |
其他业务错误码,可以使用200~999错误段进行自定义设置;但是如果有错误语义命中上述错误,则需要优先选择上述错误码。
五、RPC错误码结构定义
exception RpcError{
1:required string err_code;
2:required string err_desc;
}
- 错误码定义:
构成: 业务+错误码类型+自定义业务编码; 其中自定义业务编码是系统自定义的。
标识处理业务 | 错误码类型(3) | 自定义业务编码(2) |
---|
参数校验失败:
{
"err_code":"WORDER.10501",
"err_desc":"交易不存在"
}
WORDER:表示当前错误发生时,所处理的业务标识
105: 不存在
01: 交易不存在
02: 用户不存在
03: 订单不存在
...
2. err_desc: 错误信息
错误信息 开发人员可以快速定位问题。
3. 错误要抛出来
接口如果处理出错了,包装好合适的错误码以及错误描述,将该错误throw出,而不是将错误包在接口返回参数中。由于公司使用的是thrift协议-http,并且监控告警强依赖于httpCode,将错误直接抛出去,可以使监控更加有效的监控RPC接口,避免处理出错,但是返回httpcode是200的场景。
六、网关类型的httpcode设计
- httpcode基础
错误码 | 代表含义 |
---|---|
2xx | 成功 |
3xx | 重定向 |
4xx | 客户端原因引起的错误 |
5xx | 服务器原因引起的错误 |
2. http服务返回
{
"code":"0", // 成功:0 失败:返回对应错误码
"message":"",
"data":{ //接口实际处理结果
},
"pagination":{
"is_end":false,
"is_first":true,
"offset":20,
"limit":20,
"total":1000
}
}
3 错误信息转换
http接口,如果接口返回成功。则httpcode错误码返回200;如果失败,可以按需返回上述httpcode。
http接口一般分为两种,第一种: 内部http接口; 第二种,与前端进行交互。内部http接口,错误码可以参照rpc接口;外部接口,避免将内部错误码外露出去,对用户展示的错误描述,最好可以在http层进行转换,不要将内部错误描述直接暴露出去。
七、展望
在系统迁移、重构、优化的时候,经常会遇到由于原有系统设计不合理,后续赶时间堆需求,造成的系统日益难以维护的问题。本次主要针对迁移过程中,遇到的错误码定义混乱这一问题,提出调研、以及自己的思考。目标能够统一错误码格式的规范,以及rpc、http类型接口的错误定义规范。在后续系统迁移过程中,使用规范的错误定义,降低上游系统理解错误的复杂度,并且在一定程度上可以降低运维效率。
在后续的规划中,针对规范的错误码使用,可以有更多的技术设想,比如错误集成SDK,内部包含错误码定义,以及错误的打点上报,错误监控大盘等。