Feign超过失败重试后不知道怎么告警?

简介: 今天同事问我,使用Feign进行Http请求,当出现网络问题进行重试,假如超过了重试次数后想要发起一个告警要怎么做?

哈喽,大家好,我是强哥。


今天同事问我,使用Feign进行Http请求,当出现网络问题进行重试,假如超过了重试次数后想要发起一个告警要怎么做?


强哥被问到的时候,也突然懵了一下,之前使用Feign配置Retryer的时候,都是使用Feign的默认实现Retryer.Default,配置好重试次数和时间之后,就不管了。也没遇到要处理超过重试次数如何发起告警的问题。


那么,针对这个问题我们要怎么解决呢?


简单的在网上找了下,呵呵,全是关于怎么配置重试次数的,重试失败后的额外操作一个也没有多说。


没法,看着同事含情脉脉的眼神,不给她解决下有点不好意思。那要怎么搞的?看源码呗。


Feign触发请求调用的核心代码在SynchronousMethodHandler下的invoke方法:


@Override
  public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        return executeAndDecode(template);
      } catch (RetryableException e) {
        retryer.continueOrPropagate(e);
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }


这里我们可以看到,executeAndDecode方法是核心,进去看看(代码有点长,强哥这里就挑有用的展示):


Object executeAndDecode(RequestTemplate template) throws Throwable {
    //根据Feign配置的请求拦截器进行请求构建
    Request request = targetRequest(template);
    Response response;
    try {
      //发送请求获取结果
      response = client.execute(request, options);
    } catch (IOException e) {
      //网络异常走这里
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
      }
      //执行Retryer
      throw errorExecuting(request, e);
    }
    boolean shouldClose = true;
    try {
      //省略部分代码
      ……
      //调用接口后返回结果
      if (response.status() >= 200 && response.status() < 300) {
        if (void.class == metadata.returnType()) {
          return null;
        } else {
          return decode(response);
        }
      } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
        return decode(response);
      } else {
        throw errorDecoder.decode(metadata.configKey(), response);
      }
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
      }
      throw errorReading(request, response, e);
    } finally {
      if (shouldClose) {
        ensureClosed(response.body());
      }
    }
  }


先讲讲上面代码的执行流程:


  • 拼接Request
  • 发起请求,获取结果
  • 异常判断:1、如果是网络问题,则进入重试;2、如果发起请求后有获取到响应码,则根据响应码进行对应的处理。


那么,Feign什么时候请求失败会走Retryer呢?没错,就是throw errorExecuting(request, e);这句代码:


static FeignException errorExecuting(Request request, IOException cause) {
    return new RetryableException(
        format("%s executing %s %s", cause.getMessage(), request.method(), request.url()), cause,
        null);
  }


在抛出RetryableException异常后,有细看代码的小伙伴应该发现了,这个异常会被最开头的invoke方法捕获,然后通过retryer.continueOrPropagate(e);进入Retryer的continueOrPropagate方法。


那么重点就在continueOrPropagate方法里了(这里直接给出Retryer.Default的实现代码)。


public void continueOrPropagate(RetryableException e) {
      if (attempt++ >= maxAttempts) {
        throw e;
      }
      long interval;
      if (e.retryAfter() != null) {
        interval = e.retryAfter().getTime() - currentTimeMillis();
        if (interval > maxPeriod) {
          interval = maxPeriod;
        }
        if (interval < 0) {
          return;
        }
      } else {
        interval = nextMaxInterval();
      }
      try {
        Thread.sleep(interval);
      } catch (InterruptedException ignored) {
        Thread.currentThread().interrupt();
      }
      sleptForMillis += interval;
    }


代码逻辑很简单,就是判断当前重试次数是否大于最大重试次数,不是就等待一会然后再到最开始贴出的invoke方法的循环里再次发起请求(invoke方法里有一个while(true)的循环来重复发起请求);如果超过了重试次数,就直接抛异常了:


if (attempt++ >= maxAttempts) {
    throw e;
}


哈哈,那么重点也就是这个超过重试次数,要抛出异常的地方了。从上面的代码可以看出,抛出的是RetryableException类型的异常。


也就意味着,如果Feign在发起请求后,重试次数达到了最大重试次数还是失败的话,就会抛出RetryableException异常。


这里强哥重点强调是为了让小伙伴们明白:我们其实只要在自己的业务代码使用Feign发起请求的地方,前后添加上try catch相关的代码捕获这个异常就可以了。


给出一个强哥的解决方式:


try {
    //发起Feign请求
    Object feignResult = feignService.getUserId(json);
}catch (HystrixRuntimeException e) {
    if (e.getCause() instanceof RetryableException) {
        //告警代码
        weChatNoticeUtil.sendWxNoticeMsg("请求重试出错啦,看看是不是服务再重启或者断网咯");
    }else {
        log.warn("正常访问出错,看看是不是服务地址变更啦", e);
    }
}


捕获异常后在catch中进行对应的告警操作就可以啦。


这里catch捕获的是HystrixRuntimeException类型的异常,且在catch的处理代码中,又对请求异常的类型进行了判断,这是为什么呢?


对请求类型的判断是因为:前面有说过,并不是所有的请求都会走Retryer发起重试,如果请求能正常发起,并获取到返回码不管成功失败都是不会走Retryer的,比如请求404错误的话就不会走重试机制。


一般都是网络有问题才会走Retryer。而从上面源码的分析我们可以看出Retryer的报错类型是RetryableException,所以专门针对它进行了特殊处理。


至于catch捕获的是HystrixRuntimeException类型,其实是框架对应实现抛出来的,具体怎么知道是HystrixRuntimeException。其实只要先进行try catch(Exception e)来捕获异常,在异常捕获的地方打上断点就能知道具体是什么类型的异常啦:


15.png


好啦,所以整个问题,其实用一个try catch就解决啦。


OK,今天就水到这里。对于我们遇到的陌生的问题,其实,如果网上找不到答案,最快的办法就是自己打断点走源码来获取解决办法啦。

相关文章
ly~
|
23天前
|
消息中间件 存储 监控
如何查看 RocketMQ 消息的重试次数和时间间隔?
RocketMQ消息重试次数和时间间隔可通过查看消费者和Broker日志、使用管理控制台的监控页面和消息查询功能,或通过分析消费者代码和RocketMQ客户端库代码等方式获取。日志中常有消费失败重试的明确记录,控制台可监控消费情况推断重试状态,代码分析则适合技术用户深入了解。
ly~
97 3
|
4月前
|
监控 中间件 Java
中间件失败重试机制
【7月更文挑战第21天】
56 7
|
3月前
|
消息中间件 负载均衡 调度
异步任务处理系统问题之自动重试是如何在Level 3的系统中实现的问题如何解决
异步任务处理系统问题之自动重试是如何在Level 3的系统中实现的问题如何解决
|
6月前
|
存储 缓存 Java
nacos常见问题之超时异常如何解决
Nacos是阿里云开源的服务发现和配置管理平台,用于构建动态微服务应用架构;本汇总针对Nacos在实际应用中用户常遇到的问题进行了归纳和解答,旨在帮助开发者和运维人员高效解决使用Nacos时的各类疑难杂症。
1079 0
|
安全 Java
Hystrix超时机制为服务接口调用超时提供安全保护
Hystrix超时机制为服务接口调用超时提供安全保护
112 1
|
消息中间件 存储 资源调度
订单超时怎么处理?我们用这种方案
在电商业务下,许多订单超时场景都在24小时以上,对于超时精度没有那么敏感,并且有海量订单需要批处理,推荐使用基于定时任务的跑批解决方案。
1550 3
订单超时怎么处理?我们用这种方案
|
消息中间件 监控 Java
消息重试|学习笔记
快速学习消息重试,消息重试。为什么会有消息重试机制?消息重试目的是保证消息一定会被消费掉。 消息重试分为两个方面:一是对于顺序消息的重试;二是无序消息的重试。
消息重试|学习笔记
|
负载均衡 Java 测试技术
聊聊openfeign的超时和重试
聊聊openfeign的超时和重试
1748 0
|
存储 JSON 负载均衡
服务网格 实现超时和重试
服务网格 实现超时和重试
317 0
|
Dubbo 应用服务中间件 数据库
重试次数|学习笔记
快速学习重试次数
132 0