架构质量工程——异常管控

异常的发现

异常通常都会有专门的日志库打印相关的异常,但是有很多同学也会习惯性的在代码中直接打印到控制台里,这种并不是一个好的行为。
在异常发现中我们应常注意几点:

1. 不能吃掉异常。

  1. Try Catch 不能去忽略异常。一旦使用 Try Catch 我们也应该继续往上抛出异常。
  2. 不要使用过于泛的错误码,比如遇到了错误,有的同学会抛出一个 500 然后用不同的文案去区分。这样不易于异常的区分和治理。因此针对与每个异常我们都需要使用不同的错误码,有利于对异常进行管控。

2. 做好上下游阻断

  1. 比如你做了一个交易系统,那肯定会有购物车模块和下单模块。他们之间是一个上下游关系,而在这里,购物车的异常它需要考虑在本身内部去消化,针对上下游传递的时候完成一次上下游的阻断。这里就要求我们尽量的在内部实现异常的传递,而在不同模块之间,使用错误码进行传递。比如说,在购物车中出现了一个业务异常,在代码中可能是以一个异常抛出的格式,但是在通过购物车传递给下游的时候,我们需要把它以一个明确的错误信息向下游传递。如果以异常传递可能会导致下游处理出现问题。另一方面针对当前接口进行统计的时候,也会因为异常原因而导致统计出现错误。所以,以统一的错误码和文案的方式作为上下游的传递,可以有利于上下游针对该业务进行处理,从而最大程度上降低错误的影响范围。

3. 异常分类要做好

4. 异常优于 Null

针对一些情况我们建议抛出异常,而不要返回 Null,因为一旦返回 Null 就有可能会导致处理的不到位导致异常产生。

5. 异常不要 print 到控制台

对于一些初学者会习惯性的把异常 print 到控制台,实际上我们还是需要把异常通过日志库输出到日志文件里去。

异常控制

异常类型

针对异常我们需要考虑异常的类型。
比如:业务异常、外部异常、系统异常、超时异常和保护异常。
这五类异常是我们在做异常管控时最常见的五种方式。
当然针对业务异常和系统异常我们会有更多细化的方式,比如像参数异常以及针对某类业务场景的异常分类。都是可以提高我们针对性的进行异常处理和报警。

异常分级

我们还会有异常的分级,分级就包括:一级、二级、三级。就是将异常做好等级的划分后,更多的去关心一级,适当关心二级,而针对三级主要是查询一些问题或者获取更多信息的时候去关注。
当然「内敛」和「链路保护」这两个点也需要去额外的保护。
「内敛」的作用就诸如上面提到的要将异常控制在模块内,而针对模块的上下游做好相关的错误码传递。
第二点就是要做好异常报警的频次,对于同一个异常尽量用一个应用报警就足够了。不要出现大批量应用在同时报同一个错误。这样会出现大量的干扰信息。
而「链路保护」就是要求我们不但要防控异常,也要合理利用异常。

业务异常

业务异常我们通常要分为两类

1. 常规异常

系统设计下可能会出现用户行为异常。
比如下单接口可能会检测用户的登录态,如果用户没有登录我们会抛出一个 “User not login!”这样一个异常,那异常就属于我们系统设计时任何一个地方都可能会出现的这类用户行为场景。针对这些场景做限购。

我们也可以举另外一个例子,当我们的商品限购两件的时候,如果用户提交三件,那可能就会命中我们预先设置好的一个异常场景。那我们就可以认为这是一个超限的异常,就可以针对这样一个异常场景抛出一个固定的错误码,而上游在处理到这样一个异常信息的时候,它就可以转化为一个友好的用户提示,提示用户做相应的操作,比如:“请将购物车三件降为两件。”所以这一类属于是用户在正常的操作中可能会面对的一些场景。而这些场景是我们系统与用户之间通过交互方式让用户从这种状态码恢复到正常状态。

2. 非常规异常

这类异常是指我们往往没有去提前预设他会出现的异常,但是它却出现了。而这类异常不是一个应该有的行为。
比如:「作弊行为」
我们这类接口它需要用户在前一个页面做相应的认证,比如像验证码这种行为,用户只有在获取验证码之后才能点击注册按钮,否则我们就会认为用户绕过了我们的验证方式,直接调用我们的验证接口,它就属于一个「作弊行为」那这时候我们就需要给他抛出一个业务异常。那这个业务异常并不是一个常规的正常行为下引发的,而属于是用户使用了一些手段绕过了系统设计的框架下,从而产生的不合理的这种行为。
又比如:「越权行为」
一个用户它想调用接口去删除商品,而这个商品本身不能让用户去删除,他就想越过这个权限去删除的行为,这个时候我们也需要对它返回异常,这个异常就需要明确告诉他,你这样做不行!当然在页面交互上,可能以一个更合理的交互方式告诉他。
当然这种非法请求类的,是在我们非常规异常设计的时候也需要去关注的,不能给一些非法分子留有一些越权作弊的口子。所以这也是我们需要关注的重点对象。

系统异常

这个异常也是我们平时不希望遇到的问题。因为通常系统异常的报出都是系统出现的一些问题,或者将要出现的一些问题。

异常问题

而针对这些异常问题,可能会有系统本身出现的问题。比如说代码存在 bug,而导致空指针、数组越界等等。
常规的举个例子:
NPE、OOM、ClassCastException、IllegalArgumentException…
这些问题都属于是在代码或者在配合上出现了某些问题。从而导致系统以不正常的行为响应了相关的请求,从而产生了一些问题。

异常出现频次

我们需要去关注异常出现的频次,因为如果像连续出现、偶现的大批量它都说明当前系统已经明确的出现了问题,这时候我们需要快速的介入去查看这个问题是因为什么原因导致。当然如果是周期性发生的则需要考虑它与什么周期相契合,去找到发生的原因和发生的因素。
如果是发布后出现,那就需要去关注是否是发布后引入的这个问题,如果是则需要立即回滚,而将问题快速解决。

超时异常

针对超时异常来说,它通常反应的是一个系统稳定性的问题,因为超时我们可以认为是一个系统的响应时间超出了我们的设定。所以什么时候超时会出现,也一定程度上由我们人工设置的超时时间决定。
因为你超时时间设置的越短,那超时异常就会越容易。当然我们在考虑这方面的时候,还是要以「链路最终可用性」为目的。因为一个单独模块可能没有超时,但是整个链路加起来就超时了,比如我们针对单一模块设置到底是 3秒?还是5秒?亦或者是10秒? 等待时间越长,就意味着单模块的容错越高。同时对于全链路的压力就会越大。因为如果你有五个模块,每个模块是 10 秒,那可能整体链路上最终等下来是 50 秒这样一个时间。

我们在考虑这个时间的时候,实际上是以「用户流失」为风向标。一个用户他可能愿意在一个页面上等3秒或者5秒,而如果你要一个用户在页面上等 30 秒,那他能就不到 30 秒把页面关掉了。所以在考虑这个问题的时候,我们要尽可能的去调低我们的超时时间。

除此以外也需要去考虑「全链路耗时膨胀」。

基础设施故障

因此在考虑超时异常的时候,我们首先去考虑的是我们基础设施是否存在故障。如果明确了一些信号,告诉我们基础设施存在了一些问题,比如:网络延迟。当网络目前机房处于一个弱网环境的时候,或者网络断了,那也一定以为着它花费的请求时间一定是巨长无比的。

另外一点就是 FullGC,一旦出现FullGC 那就会出现 Stop the World。
那所有请求的线程都会停下来等待垃圾回收完成。而这种状态下,本身的响应时间就会被拉的非常长。因此如果出现频繁 FullGC,那请求就会出现不间断的卡顿。

第三就是机器 Load。机器 Load 越高,就说明当前机器的负载越重。一般来说 Load 为 1 就代表 1个 CPU 的满负荷,如果 Load 越高,比如 Load 高于 5甚至达到 10 ,说明当前机器达到 10 个 CPU 满负荷的状态,而这个状态也意味着单独请求的响应时间会非常大的拖长,第二线程之间会出现非常激烈的竞争。因此线程的切换也会带来非常大的耗时。
这种情况下,就会出现大规模并且密集性超时。

中间件问题

一般我们去做高并发架构的时候,缓存是我们需要去重点优化的点,因为缓存它可以极大程度的去降低我们系统的请求时间。但是一旦 redis 出现了问题,那所有通过 Redis 帮助我们快速返回的能力就消失了。也就以为着所有查询可能被落到数据库中,进而查询时间变长。
还有一点比如我们使用了MySql,如果 Mysql 本身性能出现问题,或者 CPU 内存彪高,那也意味着它在响应 sql 的速度上就会变得很低。还有一点就是如果出现全表扫描,扫描的数据特别多,它就会大幅度的使用 Mysql 的 Buffer,而 Buffer 一但高了它就会读写磁盘,这就导致响应会非常慢。

程序性能问题

最常见的就是「慢SQL」,我们需要设计好索引,把优化条件改好,大表改成小表,把大查询改成小查询,都可以高效的提高我们 sql 的执行效率。
慢 SQL 会拖垮 Mysql 本身的性能,慢sql 会导致 每个查询变得很长,进而连接池很快就会被耗尽。连接池耗尽后就会出现请求等待,从而进一步拖垮整体性能。
第二就是慢代码,就是如果某段代码执行特别慢,这里就可以通过代码的改写提高代码本身的执行效率,尽可能对代码优化也是可以提高我们系统的响应时间避免超时异常。
第三就是线程竞争,在大流量下出现的这种高速的线程切换的状态,这种情况下因为竞争带来的额外消耗,会拖垮本身我们的系统。
无论是这三种的哪一种,都是我们需要去重点关注的对象。

异常分级

  1. 一级:可能导致故障的重点异常(必须强加关注)
  2. 二级:可能体现系统不健康的异常或者偶现的奇怪异常。(关注统计值)
  3. 三级:常规业务异常或者其他不会恶化的异常(抽样关注)

异常可发现(可触达)

报警触达:

  1. 群消息
  2. 邮件
  3. 短信
  4. 电话

异常收敛(防轰炸)

  1. 同一时间同一类型异常,聚合报警
  2. 最小范围触达,指向性报警

异常过滤(防疲劳)

过滤无疑义报警,避免干扰
控制报警有效性,防止疲劳

猜你喜欢

转载自blog.csdn.net/qq_45455361/article/details/127050862