浅谈API错误码设计
在软件架构中,API(应用程序编程接口)和数据库(DB)的设计至关重要,因为它们分别代表了系统的外部交互界面和内部数据存储机制。良好的设计不仅能够提高系统的稳定性、可扩展性和可维护性,而且在未来进行代码重构或系统升级时,也能大大减少对上游服务和数据迁移的影响。
1)API设计的重要性
2)数据库设计的重要性
错误消息应该帮助用户轻松并快速地理解并解决API 错误,以下是一些设计原则:
1. 不要假设用户非常了解你的API。用户可能是客户端开发者、运维人员、IT人员或者APP的普通用户。
2. 不要假设用户了解服务实现的细节或熟悉错误上下文(例如日志分析)。
3. 如果可能,应构建错误消息,以便技术用户(但不一定是API的开发人员)可以对错误进行响应并更正。
1)Response响应
在Response响应方面,我们的 API 将返回一个结构化的 JSON 对象,该对象包含以下三个关键属性:
1.code(状态码): 这是一个标准化的数字代码,用于表明请求的处理结果。每个状态码都对应一个特定的情况,使得客户端可以快速识别请求的状态。
2.message(状态码描述): 该字段提供了一个简短且清晰的描述,解释了状态码的含义。这将帮助客户端了解请求成功与否,如果出现问题,它将提供足够的信息以指导进一步的行动。
data(响应数据): 这是实际的响应内容,包含了请求成功时所需的数据。这个数据对象的结构将根据具体的 API 调用而有所不同,但它总是以一种易于客户端解析和使用的格式提供。| 字段 | 类型 | 描述 |
code | String | 业务状态码 |
message | String | 错误码描述,需要描述清晰明了 |
data | Object | 封装对象 |
public class PromiseResponse<T> {/*** 错误编码*/private String code;/*** 提示信息*/private String message;/*** 业务数据*/private T data
{"code": 200,"message": "成功","data": {"transferTime": "Mon Jul 29 00:01:00 CST 2024","endOrderTime": "Mon Jul 29 18:00:00 CST 2024","outStoreTime": "Tue Jul 30 00:00:00 CST 2024","jitEndDate": "Tue Jul 30 00:00:00 CST 2024","storeDeliveryHandoverTime": "Tue Jul 30 01:00:00 CST 2024","deliveryTime": "Thu Aug 01 22:00:00 CST 2024","routeProductionResult": {"ruleType": null,"ruleName": null},"promiseControlResult": {"controlResultCode": 2,"suspendReasonCode": 0,"suspendReason": null,"abnormalLink": 0,"abnormalLinkName": null,"abnormalReason": null}}}
当发生可以重试的错误码时客户端应该以指数级增长的间隔来重试请求。除非文档中进行了说明。对于其他错误,重试操作可能并不可行,请先确保请求是幂等的并查看错误消息以获得指引。
3)错误传播
如果 API 服务依赖于其他服务,则不应盲目地将这些服务中的错误传播给客户端。翻译错误时,有如下建议:
错误码转换,比如下游返回错误码A,需要转换你对外的错误码B
错误码描述可追加下游错误码描述信息,让链路错误码描述清晰可见
最终对用户肯定是需要隐藏下游实现细节和机密信息,让用户体验良好的错误提示信息
4)❌不合理案例
5)✅行业案例
5.1京东云错误码
5.2谷歌 API 错误码定义
谷歌API的错误码设计紧密依赖于HTTP状态码,采用全数字的错误码定义方式。然而,这种设计缺乏明确的错误分类体系,导致其快速识别和自解释能力相对较弱。
1)现在场景链路错误码信息
现有应用基本都是没有错误码传递功能,比如下图 Y应用出现故障,需要排查对应的依赖服务ABC,服务B返回的是服务B自定义的错误码B,但通过B是无法快速定位故障应用是C2。服务B经过各种排查,最终定位是服务B2的问题,服务B2通过错误码B2也无法快速定位是故障应用C2,继续每个应用排查,最终定位是服务C2错误。
2)错误码传递(转换)
接收下层模块发来的错误码A,错误码A是当下层模块有故障分支时生成的;然后将自身生成的错误码B和所接收到的错误码A合成错误码AB,将错误码AB传递给上层模块。
接收单元,用于接收下层模块发来的错误码A,错误码A是当下层模块有故障分支时生成的;
合成单元,用于将自身生成的错误码B和所接收到的错误码A合成错误码AB;
发送单元,用于将所述错误码AB传递给上层模块。
错误码在传递的过程中携带各层模块的故障分支信息,这样,根据错误码就可以确定错误码的传递路径,以便精确的定位故障错误原因,提高可维护性。
前面介绍了API的错误码设计及错误码传递,本章节探讨全链路错误码如何串联起来,不一定对,只是个人的思考,并且实践起来也是比较困难的不太现实
1)痛点:全链路排查问题慢
在京东复杂的系统架构中,故障诊断往往像是在迷宫中寻路。想象一下,一个由多达20+个相互依赖的系统组成的服务链路,从入口到底层的第N个服务,每个系统都是潜在的故障点。当前,一旦系统出现问题,都是上游拉群,定位问题拉下游N个系统,线上语音讨论是哪个出现的问题故障导致的,整个流程可能需要耗费长达1-2个小时甚至更长时间才能追踪到问题实际出现在M系统上。这个过程不仅耗时,而且效率低下。
在这种复杂系统架构中,目前故障诊断的技术面临多个挑战和缺点,主要包括:
1、时间消耗长、效率低下:当系统出现问题时,故障诊断需要逐个检查各个系统,需要长时间才能定位到问题所在的系统,这直接影响了故障恢复时间和系统的整体可用性。
2、复杂性管理不足:在多个相互依赖的系统中,即使是小问题也可能迅速演变成复杂问题,现有的技术似乎没有很好地管理这种复杂性。
3、信息孤岛:系统间可能存在信息隔离,导致故障信息不能快速传递,增加了诊断时间。
4、依赖专业知识:可能过度依赖工程师的专业知识和经验进行故障排查,这不仅效率低,而且不利于知识传承和团队协作。
2)设计思想
如下图:如果服务C2有故障,则通过全链路traceId可快速查看Y的故障对应的错误码,根据错误码定位是因为C2应用故障导致的。
日志错误码架构思路如下:
1677474.49460.17235995037011944.3460091.193151.9140|WMS_10001|调用Promise(系统B)系统异常,缺少预计妥投时间1677474.49460.17235995037011944.3460091.193151.9140|PROMIES_10002|Promise调用路由系统(系统C)异常,缺少预计妥投时间1677474.49460.17235995037011944.3460091.193151.9140|ROUTE_20003|派送范围维护->未查找到配置数据,请排查派送范围维护,派送地址:四川-达州市-宣汉县-龙泉土家族乡,产品:生鲜特殊次晨,生鲜特惠次晨,生鲜标快这样通traceId(17235995037011944.3460091.193151.9140)可拿到链路中的AB(B1,B2,B3)C系统的错误码,其中B1/B2/B3等系统无错误,则可不打错误码。
具体系统设计图如下:
7.如果错误码信息被识别为异常或高优先级事件,系统将自动触发报警服务,通过多种方式(如咚咚、电子邮件、京Me等)通知相关干系人。对于紧急情况,系统还可以通过电话等方式直接联系一线人员,确保问题得到及时处理。
3)挑战性:链路改造范围广
参考内容:
1、京东云错误码:https://docs.jdcloud.com/cn/face-compare/api/error-code