服务都挂了你还在打代码?

服务挂了?

线上服务在疯狂的报错,你还在悠哉悠哉的打代码,等到用户开始反馈问题,这时候才去线上查日志,黄花菜都凉了。老板:“去财务结一下账吧”。

异常告警

对于很多基础设施比较完善的公司,都会有比较完善的日志采集、分析、告警等组件,包括服务健康检查、接口拨测等等。但是对于刚起步的产品,我们可能啥也没有,追求的就是一个快速上线,那怎么优雅快速的实现异常告警呢?

异常分级

在处理异常之前,首先我们需要先对异常做分级,哪些是业务上定义的可接受的异常,比如参数校验的异常、权限异常等等;哪些是非预期的异常,比如空指针、数据库异常、缓存异常等等。我们一般重点关注的是非预期的异常。
业务异常我们一般会定义自己的异常基类:

/**
 * 异常基类,所有业务异常继承自此类
 */
@Getter
public class BaseException extends RuntimeException{

    private final Integer code;
    private final String message;

    public BaseException(Integer code, String message) {
        super(message);
        this.code = code;
        this.message = message;
    }
}

AOP

AOP真是一个好东西,可以减少代码侵入性,重用逻辑减化开发工作量。这么好用的特性那我们肯定也要用上:


/**
 * 异常告警Aspect,打印对应异常日志并推送告警
 */
@Aspect
@Component
@Slf4j
public class ExceptionAspect {

    @Resource
    private ExceptionNotice exceptionNotice;

    @Value("${notice.bz.ex}")
    private String bizEx;

    @Value("${notice.ex.enable}")
    private boolean enable;



    @Around("execution(* com.demo.service.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        try {
            return pjp.proceed();
        } catch (Throwable e) {
            //处理异常
            handleException(pjp, e);
            //处理完还要继续向上抛
            throw e;
        }
    }


    private void handleException(ProceedingJoinPoint pjp, Throwable e) {
         //如果没开启告警,则直接返回
         if (!enable) {
            return;
         }
         try {
            //如果是非业务异常,或者是配置中的业务异常,才进行打印和告警
            boolean needToNotice = !(e instanceof BaseException) || bizEx.contains(((BaseException)e).getCode().toString())
            if (needToNotice) {
               //打印异常
               Object[] args = pjp.getArgs();
               log.error("异常参数: {}", ArrayUtil.toString(args));
               log.error(e.getMessage(), e);

               //异常告警通知,注意:这里需要异步发送消息!!
               exceptionNotice.send(formatMsg(e));
            }
         }catch(Exception e) {
            //告警处理不能影响正常流程,忽略异常,只打印
            log.error("handleException处理异常", e);
         }
    }

    /**
     * 格式化异常信息
     */
    private String formatMsg(Throwable e) {
        String template = "【业务名称】接口异常啦,请马上处理:traceId: %s, message: %s, \n %s";
        //全局traceId,用于后续定位问题
        String traceId = ServerContext.getTraceId();
        String ex = ExceptionUtil.getMessage(e);
        String trace = ExceptionUtil.stacktraceToString(e);
        return String.format(template, traceId, ex, trace);
    }
}

逻辑很简单,我们在代理类中捕获对应方法中的所有异常,然后再根据异常分级和配置,来决定是否要打印告警信息并且通过邮件、短信或企微告警。里面比较重要的几个点:

  1. 异常处理逻辑不能影响原有流程,因此需要catch住异常处理逻辑中的所有异常。
  2. 告警通知是一个较为耗时的操作,需要使用线程池异步处理,并且为了异常处理的逻辑不影响我们正常的服务,一定要设置线程池的队列大小和拒绝策略,拒绝策略应该是直接丢弃。
  3. 在发送异常告警需要考虑收敛,否则在某些情况下,邮件或短信可能会爆炸(别问我怎么知道的)。而且邮件算还好,但是短信是要钱的!!!

告警收敛可以根据一定的规则,比如根据告警信息、特定参数或者异常类型作为唯一标识,在时间范围内只告警N次。

总结

以上就可以简单快速且优雅的实现一个异常告警功能啦,对于缺乏基础设施建设,且需要快速上线的项目来说,这样最少可以保证我们项目前期的异常监控,不会等用户、运营、产品、老板都发现服务挂了,作为一个一线开发,你还在那笑嘻嘻的打代码,完全没有意识到,风雨欲来~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值