rpc 服务中,“业务错误”的返回应该如何设计? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Coding.NET 轻量级社交
开源项目广场
使用帮助
意见反馈
justdoit123
V2EX    Coding

rpc 服务中,“业务错误”的返回应该如何设计?

  •  1
     
  •   justdoit123 2024-07-02 18:14:32 +08:00 1437 次点击
    这是一个创建于 532 天前的主题,其中的信息可能已经有所发展或是发生改变。

    是返回类似下面的业务错误码结构,

    { "rc": 10001, "msg": "部分商品没有库存", "data": { "out_of_stock_ids": [ 1, 2, 3 ] } } 

    还是抛出下面这样的自定义异常?

    class BaseBusinessError(Exception): rc = 500 msg = 'Unknown error' data = None def __repr__(self): return f'<Error {self.rc}: {self.msg}. data: {self.data}>' def __str__(self): return self.__repr__() class ShopOutOfStockError(BaseBusinessError): rc = 10001 msg = "部分商品没有库存" data = None def __init__(self, product_ids: list): self.data = product_ids 

    如果采用异常方案,调用方没有自定义异常类的定义怎么办?如何反序列化?是不是要共享异常的定义代码给所有调用方?要是调用方是其它语言呢?

    这篇博客 下的这句:

    查询方法不建议抛出 checked 异常,否则调用方在查询时将过多的 try...catch ,并且不能进行有效处理。

    中的 checked 异常是什么意思?是否可以理解为查询方法不抛异常。比如,查询某个数据没有权限,直接返回空,而不是抛一个没有权限的异常。

    8 条回复    2024-07-03 11:37:00 +08:00
    timethinker
        1
    timethinker  
       2024-07-02 19:27:38 +08:00   1
    在 Java 中,受检(checked)异常指的是必须要用 try..catch 来调用一个可能会抛出受检异常的方法。python 中应该所有的都是非受检异常,也就是不强迫你使用 try..catch 。

    言归正传,不要把异常跟传输搞混了,RPC 本质上也是通过网络传递数据,至于怎么处理这个数据,你可以参考一下 HTTP 协议,返回不同的状态码来表示协议级别的成功与否。或者不需要状态码,不管成功与否永远返回一个就像你发的那样的数据结构,然后再进一步根据数据里面的 code 再决定是否要抛出异常。
    justdoit123
        2
    justdoit123  
    OP
       2024-07-02 20:06:31 +08:00
    @timethinker 感谢~
    justdoit123
        3
    justdoit123  
    OP
       2024-07-02 20:07:15 +08:00
    话说,才发现 coding 节点算是 CodingNet 的营销节点。。。
    povsister
        4
    povsister  
       2024-07-02 20:08:34 +08:00   1
    通常对于 rpc 来说,其框架已经定义过了错误,并且提供了错误的扩展(描述、details 等),就不要再把“业务错误”包装成正常响应返回了,否则你在可观测上会遇到不小麻烦。

    而且,看你的设计,你在异常里,又返回了 data ,并且还描述了业务相关数据( out of stock ids ),那么你本质就不是想返回错误,而是告诉请求方你提交的哪些数据不合法。
    这时候你就要反思你的设计:你究竟是想交互,还是想抛出错误?对于一个错误来说你返回的信息里包含了太多业务场景相关信息,没有通用性。应该考虑这是一次正常交互。

    错误描述的扩展,应该是描述错误本身,首先,应该提供一个统一注册管理的 bizErrorCode ,这个是可观测的基础,同时应该提供 description ,这个是对错误的描述,提供一些场景、debug 信息,而且仅应该用于 localization 或者程序员调试使用。
    justdoit123
        5
    justdoit123  
    OP
       2024-07-03 10:17:59 +08:00
    @povsister

    我可不可以这样理解:

    1. 对于我描述的这类异常,应该属于 “业务异常”。说是异常,其实是正常的数据交互。类似的还有,邮箱已被使用、账号或密码不存在等。这些不属于程序错误,监控也不需要关心这些业务异常。

    2. 还存在一类异常,为了区分称为“程序错误”吧。这些是程序员真正需要关心的,需要有你说的描述、场景、debug 信息。我能想到的典型,可能是 “配置不存在”、验证 Token 解析失败这类错误。总之是不符合预期的、需要监控关心的问题。
    justdoit123
        6
    justdoit123  
    OP
       2024-07-03 10:26:50 +08:00
    回到这个 “业务异常” 上来。我在 rpc 服务供应端实现的时候,是不是可以通过抛出异常然后在出口统一拦截这类业务异常转成正常数据交换的实现方式?当然,这类所谓的“业务异常”并不是程序错误,就不需要上报监控了,不混入其它的错误监控中去。
    povsister
        7
    povsister  
       2024-07-03 10:35:33 +08:00   1
    @justdoit123
    粗略意义上正确,不过各家有各家的做法,rpc 的错误机制设计是要和可观测+trace+SLO 大盘联合起来考虑的。

    业务错误不代表监控不需要关心,简单来说:
    如果你只需要拒绝服务+返回错误,并且需要有一个自定义的 errcode 去表示这是什么类型的错误,同时附带一个简短的 readable 的说明(可以理解为 errcode 作为程序使用的描述,可以依赖 errcode 进一步执行某些程序化步骤,readable 的描述作为程序员阅读使用,方便快速识别问题),除此之外不需要向调用方传递更多的错误内容,那么就适合用 rpc error 来表示。
    如果你要向调用方提供更多的关于错误的具体信息,深入绑定业务场景,那么就不适合当做 err 来处理,应该作为一个正常的 rpc 响应,此时的观测方案设计应该交由调用方手动填写 metric/log ,作为业务指标监控来看。

    更深一步来说,还可以细分为业务错误(入参不合法,执行条件不满足,业务逻辑错误等等),框架及中间件错误( token 无效,malformed request ,调用超时,BBR 自适应限流,熔断错误等等),以 int64 作为 errcode 的取值范围的话,常见的方案是 0 为正常响应,errcode>0 的部分作为业务错误( eg:10001=余额不足),errcode<0 的部分作为框架及通用错误( eg:-400=请求错误/参数错误,-504=调用超时,-429=BBR 限流)

    rpc error 本身已经有一套错误码了,这个通常称为大码,含义可以简单认为和 http code 相同,通常作为 transport 层的可观测手段,适用对象 API 网关,SLB 等基础 L3-L7 设施
    业务 errcode 通常是选择 rpc error 中的 unknown error (例如 GRPC 为 status 2 )作为可扩展对象,这个称为小码,其作用就是,在 rpc 的底层 transport 没有问题的时候,用于传递业务+业务框架的错误,主要目的用于微服务治理和更细粒度的 SLO 观测
    justdoit123
        8
    justdoit123  
    OP
       2024-07-03 11:37:00 +08:00
    感谢 感谢~ 感觉拓宽了视野,也清晰了很多。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2844 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 14:24 PVG 22:24 LAX 06:24 JFK 09:24
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86