服务层异常该如何处理

0 前言

我们面对异常往往会遵循约定俗成的一些规范或者习惯性抛出异常,那么我们该如何正确处理异常呢,实际上我们处理异常是为了写正确的代码,那么我们提供以下思路,我们应该思考下面几个问题:

1 啥叫“正确”?

由解决的问题决定的。问题不同,解决方案不同。

如一个web接口接受用户请求,参数age,也许业务要求字段是0~150之间整数。如输入字符串或负数就肯定不接受。一般在后端某地做输入合法性检查,不过就抛异常。

但归根到底这问题“正确”解决方法总是要以某种形式提示用户。而提示用户是某种前端工作,就要看界面是app,H5+AJAX还是类似于[jsp]的服务器产生界面。不管啥,你要根据需求去”设计一个修复错误“的流程。如一个常见的流程要后端抛异常,然后一路到某个集中处理错误的代码,将其转换为某个HTTP的错误(业务错误码)提供给前端,前端再映射做”提示“。如用户输入非法请求,从逻辑上后端都没法自己修复,这是个“正确”的策略。

2 报500

如用户上传一个头像,后端将图片发给[云存储],结果云存储报500,咋办?你可能想重试,因为也许仅是[网络抖动],重试就能正常执行。但若重试多次无效,若设计了某种热备方案,可能改为发到另一个服务器。“重试”和“使用备份的依赖”都是“立刻处理“。

但若重试无效,所有的[备份服务]也无效,也许就能像上面那样把错误抛给前端,提示用户“服务器开小差”。从这方案易看出,你想把错误抛到哪里是因为那个catch的地方是处理问题最方便的地方。一个问题的解决方案可能要几个不同的错误处理组合起来才能办到。

3 NPE了!

你的程序抛个NPE。这一般就是程序员的bug:

  • 要不就是程序员想表达一个东西”没有“,结果在后续处理中忘判断是否为null
  • 要不就是在写代码时觉得100%不可能为null的地方出现了一个null

不管哪种,这错误用户总会看到一个很含糊的报错信息,这远远不够。“正确”办法是程序员自己能尽快发现它,并尽快修复。要做到这点,需要[监控系统]不断爬log,把问题报警出来。而非等用户找客服投诉。

4 OOM了!

比如你的[后端程序]突然OOM挂了。挂的程序没法恢复自己。要做到“正确”,须在服务之外的容器考虑这问题。

如你的服务跑在[k8s],他们会监控你程序状态,然后重启新的服务实例弥补挂掉的服务,还得调整流量,把去往宕机服务的流量切换到新实例。这的恢复因为跨系统所以不能仅用异常实现,但道理一样。

但光靠重启就“正确”了?若服务是完全无状态,问题不大。但若有状态,部分用户数据可能被执行一半的请求搞乱。因此重启要留意先“恢复数据到合法状态”。这又回到你要知道咋样才是“正确”的做法。只依靠简单的语法功能不能无脑解决这事。

5 提升维度

  • 一个工作线程的“外部容器“是管理工作线程的“master”
  • 一个网络请求的“外部容器”是一个Web Server
  • 一个用户进程的“外部容器”是[操作系统]
  • Erlang把这种supervisor-worker的机制融入到语言的设计

Web程序很大程度能把异常抛给顶层,是因为:

  • 请求来自前端,对因为用户请求有误(数据合法性、权限、用户上下文状态)造成的问题,最终基本只能告诉用户。因此抛异常到一个集中处理错误的地方,把异常转换为某个业务错误码的方法,合理
  • 后端服务一般无状态。这也是软件系统设计的一般原则。无状态才意味着可随时随地安心重启。用户数据不会因为因为下一条而会出问题
  • 后端对数据的修改依赖DB的事务。因此一个改一半的、没提交的事务不会造成副作用。

但这3条件并非总成立。总能遇到:

  • 一些处理逻辑并非无状态
  • 也并非所有的数据修改都能用一个事务保护

尤其要注意对[微服务]的调用,对内存状态的修改是没有事务保护的,一不留神就会搞乱用户数据。比如下面代码段

6 以下示例代码段

// controller
void controllerMethod(/* 参数 */) {
  try {
    return svc.doWorkAndGetResult(/* 参数 */);
  } catch (Exception e) {
    return ErrorJsonObject.of(e);
  }
}

// svc
void doWorkAndGetResult(/* some params*/) {
    int res1 = otherSvc1.doStep1(/* some params */);
    this.status1 += res1;
    int res2 = otherSvc2.doStep2(/* some params */);
    this.status2 += res2;
    int res3 = otherSvc3.doStep3(/* some params */);
    this.status3 = status1 + status2 + res3;
    return SomeResult.of(this.status1, this.status2, this.status3);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值