贴吧式
中间件抛给业务方的异常,该透传底层细节还是收敛成统一错误码?
帖子正文与楼层回复。
楼主 谢知远-318
中间件异常设计错误码可维护性工程规范
背景
做了 3 年中间件,每次给业务方交付客户端(RPC 框架、缓存客户端、MQ producer 这类自研组件),最容易被追问、也最容易在半年后埋雷的一件事就是——中间件抛出来的异常,到底长什么样? 我见过两种极端:一种是把底层 socket、序列化、连接池的异常原样往上抛,业务方 catch 的时候满屏 Caused by,排查得一层层扒,还得懂你中间件内部;另一种是中间件把一切都吞掉、统一包成 MiddlewareException("internal error"),业务方 catch 到也不知道是超时、是熔断、还是序列化炸了,只能回头翻日志。两种我都踩过坑。
常见分歧
- 派 A「透传派」:异常要带全底层信息,业务方自己判断语义,中间件不越权替业务方决定「这算不算超时」。
- 派 B「统一派」:中间件必须定义一套稳定的错误码 + 异常分类(可重试 / 不可重试 / 配置错误 / 资源耗尽),业务方只依赖这一层,底层实现随便换。
- 派 C「折中派」:对外抛统一异常,但把原始异常塞进 cause 链,并带上结构化字段(error_code、retryable、trace_id),两边都要。
我的倾向与理由
我站 C,但有一条铁律——对外契约只承诺 error_code 和 retryable 两个字段,不承诺 message 文案,也不承诺异常类名。理由是:message 是实现细节,改起来零成本却最容易被打脸;而「能不能重试」才是业务方做容错决策真正需要的语义。
我们踩过一个真实坑:早期把「连接池耗尽」和「下游超时」都归成同一个 TimeoutException,业务方按超时做指数退避重试,结果连接池雪崩时疯狂重试,把业务线程池也一起打满,雪崩放大。后来拆成 RESOURCE_EXHAUSTED(标记不可重试)和 DEADLINE_EXCEEDED(标记可重试),业务方据此决定退避还是快速失败,才稳下来。
想听大家
- 你们中间件对外暴露异常时,怎么划「可重试 vs 不可重试」这条线?有没有明确不重试的场景(比如写操作幂等没保证)?
- 业务方拿到异常后,是希望你们把语义判定做进中间件,还是自己只看 error_code 再决策?
- 你们有没有遇到过业务方因为异常语义没对齐而踩的坑?怎么收场的?
赞 0 · 收藏 0 · 楼层 0
楼层
还没有人回复。