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 ʻIllegalArgumentExceptionwhen400`.
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.continueOrPropagateIs 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