前回の記事ではアノテーションだけでHTTPクライアントを作成する方法を紹介しましたが、実際のHTTPクライアントではリトライをする必要が出てくる場面があります。
実際RestTemplate
を持つようなHTTPクライアントクラスを作成してそこでリトライ処理をしているプロジェクトなども多いのではないでしょうか
このリトライ処理をOpenFeign
でも実現してみます。
仕組み
OpenFeign
のリトライ処理は2つのインターフェースを実装することで実現できます。
ErrorDecoder
ErrorDecoder
はHTTPリクエストで200番台以外のレスポンスが返ってきた際にどのようなException
を投げるのか決めるためのインターフェースです。
例として400
の時にIllegalArgumentException
を返すクラスを実装してみます。
class IllegalArgumentExceptionOn404Decoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
if (response.status() == 400) {
throw new IllegalArgumentException("400だよー。");
}
return new ErrorDecoder.Default().decode(methodKey, response);
}
}
基本的にはresponse.status()
をみて動作を変えるようになるかと思います。
decode
の内部でRetryableExceptionを投げると、以下のRetryer.continueOrPropagate
が呼ばれます。
Retryer
Retryer
はRetryableException
が投げられた際に呼ばれ、リトライ間隔や回数などを管理するためのインターフェースです。
デフォルトのRetryer
がRetryer.Defaultは100msで5回試行するものになっています。
リトライ間隔、回数だけを変えるだけであればRetryer.Default
で十分かと思います。
使い方
上記の2つのインターフェースの実行クラスをapplication.yml
に指定します。
feign:
client:
config:
{{yourFeignName}}:
errorDecode: com.example.feign.MyErrorDecoder
retryer: com.example.feign.MyRetryer
yourFeignName
にはFeignClient.name
で指定したものを設定します。
FeignClient.name
を分けるとname
毎にリトライ処理を分けることができます。
サンプル
前回のお天気サンプルにリトライ処理を追加してみます。
MyErrorDecoder.java
このサンプルでは503
, 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
Retryer.Defaultを少し改変しただけです。
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("リトライ処理");
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
上記2つの実装クラスを指定します。
server:
port: 8088
application:
name: open-feign-client
feign:
client:
config:
weather:
errorDecoder: com.example.ofc.feign.MyErrorDecoder
retryer: com.example.ofc.feign.MyRetryer
まとめ
OpenFeign
でリトライ処理を実現してみました。
APIクライアント自体には手を入れずに実現できるので、改修もしやすそうですね。
今回のサンプルもこちら
https://github.com/totto357/open-feign-client-example