如何实现Feign超过重试次数后的告警操作
今天同事问我,使用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)来捕获异常,在异常捕获得地方打上断点就能知道具体是什么类型的异常啦:
好啦,所以整个问题,其实用一个try catch就解决啦。
OK,今天就水到这里。对于我们遇到的陌生的问题,其实,如果网上找不到答案,最快的办法就是自己打断点走源码来获取解决办法啦。
共有 0 条评论