Previous article introduced how to create an HTTP client with only annotations, but in the actual HTTP client it is necessary to retry. there is.
In fact, there may be many projects that create an HTTP client class that has RestTemplate
and perform retry processing there.
Let's realize this retry process with ʻOpen Feign`.
ʻOpenFeign` retry processing can be realized by implementing two interfaces.
-ErrorDecoder interface -Retryer interface
ErrorDecoder
ʻErrorDecoder is an interface for deciding what kind of ʻException
is thrown when a response other than the 200s is returned in an HTTP request.
As an example, let's implement a class that returns ʻIllegalArgumentExceptionwhen
400`.
class IllegalArgumentExceptionOn404Decoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
if (response.status() == 400) {
throw new IllegalArgumentException("It's 400.");
}
return new ErrorDecoder.Default().decode(methodKey, response);
}
}
Basically, I think that you can change the behavior by looking at response.status ()
.
If you throw RetryableException inside decode
, the followingRetryer.continueOrPropagate
Is called.
Retryer
Retryer
is called when RetryableException
is thrown, and is an interface for managing the retry interval and the number of times.
The default Retryer
is Retryer.Default tries 5 times in 100ms It is supposed to be done.
If you only want to change the retry interval and the number of times, I think Retryer.Default
is enough.
Specify the execution class of the above two interfaces in ʻapplication.yml`.
feign:
client:
config:
{{yourFeignName}}:
errorDecode: com.example.feign.MyErrorDecoder
retryer: com.example.feign.MyRetryer
Set yourFeignName
to the one specified in FeignClient.name
.
If you separate FeignClient.name
, you can separate the retry process for each name
.
Let's add a retry process to the previous Weather sample.
MyErrorDecoder.java
In this sample, it is tried to retry when 503
and 504
.
package com.example.ofc.feign;
import org.springframework.web.client.RestClientException;
import feign.Response;
import feign.RetryableException;
import feign.codec.ErrorDecoder;
public class MyErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
RestClientException cause = new RestClientException(response.toString());
final int status = response.status();
if (status == 503 || status == 504) {
return new RetryableException(methodKey, cause, null);
}
return cause;
}
}
MyRetryer.java I just modified Retryer.Default a little.
package com.example.ofc.feign;
import static java.util.concurrent.TimeUnit.SECONDS;
import feign.RetryableException;
import feign.Retryer;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class MyRetryer implements Retryer {
private final int maxAttempts;
private final long period;
private final long maxPeriod;
int attempt;
long sleptForMillis;
public MyRetryer() {
this(100, SECONDS.toMillis(1), 3);
}
public MyRetryer(long period, long maxPeriod, int maxAttempts) {
this.period = period;
this.maxPeriod = maxPeriod;
this.maxAttempts = maxAttempts;
this.attempt = 1;
}
// visible for testing;
protected long currentTimeMillis() {
return System.currentTimeMillis();
}
@Override
public void continueOrPropagate(RetryableException e) {
log.info("Retry processing");
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();
throw e;
}
sleptForMillis += interval;
}
/**
* Calculates the time interval to a retry attempt. <br>
* The interval increases exponentially
* with each attempt, at a rate of nextInterval *= 1.5 (where 1.5 is the
* backoff factor), to the
* maximum interval.
*
* @return time in nanoseconds from now until the next attempt.
*/
long nextMaxInterval() {
long interval = (long) (period * Math.pow(1.5, attempt - 1));
return interval > maxPeriod ? maxPeriod : interval;
}
@Override
public Retryer clone() {
return new MyRetryer(period, maxPeriod, maxAttempts);
}
}
application.yml Specify the above two implementation classes.
server:
port: 8088
application:
name: open-feign-client
feign:
client:
config:
weather:
errorDecoder: com.example.ofc.feign.MyErrorDecoder
retryer: com.example.ofc.feign.MyRetryer
I tried to realize retry processing with ʻOpenFeign`. It can be realized without touching the API client itself, so it seems easy to repair.
Click here for this sample https://github.com/totto357/open-feign-client-example