1

关于错误码的那点事

 2 years ago
source link: https://zhuanlan.zhihu.com/p/411726319
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

关于错误码的那点事

专业爱鸟人士

一、什么是错误码

错误码一般情况分为对外错误码,系统内部错误码。

对外错误码常应用在一些开放接口,比如http接口,rpc接口等,通过错误码的形式给予上游更加友好的错误提示以及错误描述。

系统内部错误码,存在于关系紧密的微服务之间、或者程序的上下游中。因为某种业务错误、或者系统不可用造成的错误,开发人员可以根据错误码、错误信息进行具体定位;或者根据上游根据错误码做业务逻辑判断,从而保证整体流程完整性。

总而言之,错误码的作用: 指出错误的原因,快速定位问题,指导上游系统做出正确的业务判断,引导用户进行正确的操作。因此构建一个通用且架构清晰的错误码体系是一件很有必要的事情。

那么怎么定义一种对外对内都友好的错误码呢?至今业内也并没有一个比较好的方案或者规范。这里结合新老支付系统融合,逐步进行摸索。

二、支付系统错误码现状

由于历史原因,公司内部目前有两套运行中的钱包系统。为了提高钱包系统可用性以及性能,对两套钱包系统进行迁移整合升级优化。但迁移合并两套钱包系统的途中,发现两套钱包系统、以及之前现存的已经优化过的错误码之前存在冲突、类型不一致、定义混乱、随意性高等问题。

v2-15b802134c2392516468b0fa73c04835_720w.jpg

由于其一钱包系统之前由其他团队开发维护,后期也对其进行重构过,这造成了新钱包系统的错误码定义规则存在两套;从代码角度来看,新老钱包系统在设计之初的时候,对错误码的定义都各自定义了一个比较合适的规范,但是在后期开发维护中,越来越少的开发人员去遵循规范定义错误码,最终造成了由错误描述决定错误码的现象。而目前新系统中错误码,在设计之初,并未考虑到未来整合带来的错误码冲突等问题,造成了部分错误码重合,语义大相径庭的问题,所以重新定义错误码规范也是迫在眉睫。

钱包系统错误码现状:

钱包一错误码定义: 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_codeerr_code_desNOAUTH商户无此接口权限INVALID_REQUEST参数错误NOTENOUGH余额不足

b.参考微信支付v3接口

微信支付v3接口: 协议:http, content-type: application/json

参考链接: https://pay.weixin.qq.com/wiki/doc/apiv3/Share/error_code.shtml

err_codehttp_codeerr_msgUSERPAYING202用户正在付款中OUT_TRADE_NO_USED403商户订单号重复ORDERNOTEXIST404订单不存在

根据微信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位 错误代码(自定义的错误编码)

20502服务级错误(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;
}
  1. 错误码定义:

构成: 业务+错误码类型+自定义业务编码; 其中自定义业务编码是系统自定义的。

标识处理业务错误码类型(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设计

  1. 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,内部包含错误码定义,以及错误的打点上报,错误监控大盘等。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK