前回エントリで、FailSafe
によるリトライ方法の制御をいくつか紹介しました。
今回のエントリでは、処理失敗時やリトライ処理実行時のハンドリングなどを紹介します。
基本となるリトライポリシーとリトライ処理を以下とします
リトライポリシー
RetryPolicy retryPolicy = new RetryPolicy() .retryOn(BusinessLogicException.class) // リトライ対象とする例外クラス .withDelay(2, TimeUnit.SECONDS) // リトライ時の間隔 .withMaxRetries(5); // 最大リトライ回数
リトライ処理
Failsafe.with(retryPolicy) .onSuccess((o, ctx) -> logger.info("bussiness logic success!!!! [execCount: {}]]", ctx.getExecutions())) .onFailure(throwable -> logger.error("bussiness logic failed.", throwable)) .run(() -> BusinessLogic.execute(time));
onSuccess
により、処理が成功した場合は成功時のログを出力しています。
onFailure
により、最大リトライ回数まで失敗した場合にエラーログを出力するようにしています。
(ログ出力は、slf4j
などで実行しています)
戻り値を返すようにする
これまでのエントリでは、基本的に戻り値を返さないメソッドをFailSafe
でのリトライ対象としていました。
当然ですが、戻り値を返したい場合も多々あります。
この場合は、run
メソッドではなくget
メソッドの中で対象の処理を実行するようにします。
例えばリトライ対象の処理が大雑把に書くと以下のような処理だとして、戻り値にResultというenumを返すものとします。
final class BusinessLogic { static Result execute(long time) { logger.info(">>BusinessLogic call execute [param: {}]", time); if ((System.currentTimeMillis() - time) < 20000) { throw new BusinessLogicException("failed"); } logger.info(">>BusinessLogic execute success."); return Result.SUCCESS; } } enum Result { UNKNOWN, FAILED, SUCCESS }
この場合、FailSafe
で以下のように実行します。
Result ret = Failsafe.with(retryPolicy) .onSuccess((o, ctx) -> logger.info("bussiness logic success!!!! [execCount: {}]]", ctx.getExecutions())) .onFailure(throwable -> logger.error("bussiness logic failed.", throwable)) .get(() -> BusinessLogic.execute(time));
これで、対象の処理の戻り値を受け取ることができます。
リトライ最大回数まで失敗したら特定の戻り値を返す
最大回数まで処理が失敗した場合に返す戻り値を返す場合は、withFallback
で指定することが可能です。
以下例では、処理が全部失敗した場合は Result.FAILED
という値を返すようにしています。
(BusinessLogic#execute
は先に記載したコードのままとします)
Result ret = Failsafe.with(retryPolicy) .withFallback(Result.FAILED) .onSuccess((o, ctx) -> logger.info("bussiness logic success!!!! [execCount: {}]]", ctx.getExecutions())) .onFailure(throwable -> logger.error("bussiness logic failed.", throwable)) .get(() -> BusinessLogic.execute(time)); // 返された戻り値をログ出力してみる logger.info("execute result -> [{}]", ret);
これを実行してみると
処理が途中で成功した場合
20:43:30.915 [main] INFO net.yyuki.retry2.RetrySample2 - >>BusinessLogic call execute [param: 1536493395812] 20:43:32.927 [main] INFO net.yyuki.retry2.RetrySample2 - >>BusinessLogic call execute [param: 1536493395812] 20:43:34.929 [main] INFO net.yyuki.retry2.RetrySample2 - >>BusinessLogic call execute [param: 1536493395812] 20:43:36.933 [main] INFO net.yyuki.retry2.RetrySample2 - >>BusinessLogic call execute [param: 1536493395812] 20:43:36.933 [main] INFO net.yyuki.retry2.RetrySample2 - >>BusinessLogic execute success. 20:43:36.933 [main] INFO net.yyuki.retry2.RetrySample2 - bussiness logic success!!!! [execCount: 4]] 20:43:36.933 [main] INFO net.yyuki.retry2.RetrySample2 - execute result -> [SUCCESS]
戻り値として Result.SUCCESS
が返されています
リトライ含めて処理が全部失敗した場合
20:39:49.916 [main] INFO net.yyuki.retry2.RetrySample2 - >>BusinessLogic call execute [param: 1536493189830] 20:39:51.926 [main] INFO net.yyuki.retry2.RetrySample2 - >>BusinessLogic call execute [param: 1536493189830] 20:39:53.932 [main] INFO net.yyuki.retry2.RetrySample2 - >>BusinessLogic call execute [param: 1536493189830] 20:39:55.934 [main] INFO net.yyuki.retry2.RetrySample2 - >>BusinessLogic call execute [param: 1536493189830] 20:39:57.935 [main] INFO net.yyuki.retry2.RetrySample2 - >>BusinessLogic call execute [param: 1536493189830] 20:39:59.939 [main] INFO net.yyuki.retry2.RetrySample2 - >>BusinessLogic call execute [param: 1536493189830] 20:39:59.942 [main] ERROR net.yyuki.retry2.RetrySample2 - bussiness logic failed. net.yyuki.retry2.RetrySample2$BusinessLogicException: failed at net.yyuki.retry2.RetrySample2$BusinessLogic.execute(RetrySample2.java:51) at net.yyuki.retry2.RetrySample2.lambda$retryExecute$2(RetrySample2.java:38) at net.jodah.failsafe.SyncFailsafe.call(SyncFailsafe.java:145) at net.jodah.failsafe.SyncFailsafe.get(SyncFailsafe.java:56) at net.yyuki.retry2.RetrySample2.retryExecute(RetrySample2.java:38) at net.yyuki.retry2.RetrySample2.main(RetrySample2.java:24) 20:39:59.942 [main] INFO net.yyuki.retry2.RetrySample2 - execute result -> [FAILED]
戻り値として Result.FAILED
が返されています。
リトライ最大回数まで失敗したら例外をスローする
withFallback
では戻り値を返すだけではなく、例外をスローすることも可能です。
例外をスローしたい場合は以下のように記述します。
Result ret = Failsafe.with(retryPolicy) .withFallback(() -> {throw new FaitalException("All processing failed");}) .onSuccess((o, ctx) -> logger.info("bussiness logic success!!!! [execCount: {}]]", ctx.getExecutions())) .onFailure(throwable -> logger.error("bussiness logic failed.", throwable)) .get(() -> BusinessLogic.execute(time));
処理失敗時・リトライ時にハンドリングする
リトライする時に、その度にログを出力したいというケースがあります。
その場合、onRetry
, onFailedAttempt
というメソッドでハンドリング可能です。
Result ret = Failsafe.with(retryPolicy) .withFallback(Result.FAILED) .onSuccess((o, ctx) -> logger.info("bussiness logic success!!!! [execCount: {}]]", ctx.getExecutions())) .onFailure(throwable -> logger.error("bussiness logic failed.", throwable)) .onRetry((o, th, ctx) -> logger.warn("onRetry [tryCount: {}]", ctx.getExecutions())) .onFailedAttempt((o, th, ctx) -> logger.warn("onFailedAttempt [tryCount: {}]", ctx.getExecutions())) .get(() -> BusinessLogic.execute(time));
仮にリトライが全部失敗した場合、以下のような結果になります。
実行結果
20:57:23.457 [main] INFO net.yyuki.retry2.RetrySample2 - >>BusinessLogic call execute [param: 1536494243378] 20:57:23.468 [main] WARN net.yyuki.retry2.RetrySample2 - onFailedAttempt [tryCount: 1] 20:57:25.473 [main] WARN net.yyuki.retry2.RetrySample2 - onRetry [tryCount: 1] 20:57:25.473 [main] INFO net.yyuki.retry2.RetrySample2 - >>BusinessLogic call execute [param: 1536494243378] 20:57:25.474 [main] WARN net.yyuki.retry2.RetrySample2 - onFailedAttempt [tryCount: 2] 20:57:27.478 [main] WARN net.yyuki.retry2.RetrySample2 - onRetry [tryCount: 2] 20:57:27.478 [main] INFO net.yyuki.retry2.RetrySample2 - >>BusinessLogic call execute [param: 1536494243378] 20:57:27.478 [main] WARN net.yyuki.retry2.RetrySample2 - onFailedAttempt [tryCount: 3] 20:57:29.483 [main] WARN net.yyuki.retry2.RetrySample2 - onRetry [tryCount: 3] 20:57:29.483 [main] INFO net.yyuki.retry2.RetrySample2 - >>BusinessLogic call execute [param: 1536494243378] 20:57:29.483 [main] WARN net.yyuki.retry2.RetrySample2 - onFailedAttempt [tryCount: 4] 20:57:31.488 [main] WARN net.yyuki.retry2.RetrySample2 - onRetry [tryCount: 4] 20:57:31.488 [main] INFO net.yyuki.retry2.RetrySample2 - >>BusinessLogic call execute [param: 1536494243378] 20:57:31.488 [main] WARN net.yyuki.retry2.RetrySample2 - onFailedAttempt [tryCount: 5] 20:57:33.489 [main] WARN net.yyuki.retry2.RetrySample2 - onRetry [tryCount: 5] 20:57:33.489 [main] INFO net.yyuki.retry2.RetrySample2 - >>BusinessLogic call execute [param: 1536494243378] 20:57:33.489 [main] WARN net.yyuki.retry2.RetrySample2 - onFailedAttempt [tryCount: 6] 20:57:33.492 [main] ERROR net.yyuki.retry2.RetrySample2 - bussiness logic failed. net.yyuki.retry2.RetrySample2$BusinessLogicException: failed at net.yyuki.retry2.RetrySample2$BusinessLogic.execute(RetrySample2.java:53) at net.yyuki.retry2.RetrySample2.lambda$retryExecute$4(RetrySample2.java:40) at net.jodah.failsafe.SyncFailsafe.call(SyncFailsafe.java:145) at net.jodah.failsafe.SyncFailsafe.get(SyncFailsafe.java:56) at net.yyuki.retry2.RetrySample2.retryExecute(RetrySample2.java:40) at net.yyuki.retry2.RetrySample2.main(RetrySample2.java:24) 20:57:33.492 [main] INFO net.yyuki.retry2.RetrySample2 - execute result -> [FAILED]
この結果からもわかりますが、以下の順に処理が実行されます
対象の処理実行 → (処理失敗) → onFailedAttempt
→ 遅延(delay) → onRetry
→リトライにより対象処理実行 → (処理失敗) → onFailedAttempt
→ ...
onFailedAttempt
が処理失敗に反応して実行されるのに対して、onRetry
がリトライ処理が実行される直前に実行されます。
リトライが最大回数まで失敗した場合の通知を受け取る
リトライが最大回数を超えた場合、onRetriesExceeded
で通知を受け取ることが可能です。
onFailure
との使い分けが正確によくわかっていませんが、onFailure
よりも先にonRetriesExceeded
への通知が行われます。
Result ret = Failsafe.with(retryPolicy) .withFallback(Result.FAILED) .onSuccess((o, ctx) -> logger.info("bussiness logic success!!!! [execCount: {}]]", ctx.getExecutions())) .onRetriesExceeded(ctx -> logger.warn("Failed to execute. Max retries exceeded.")) .onFailure(throwable -> logger.error("bussiness logic failed.", throwable)) .onRetry((o, th, ctx) -> logger.warn("onRetry [tryCount: {}]", ctx.getExecutions())) .onFailedAttempt((o, th, ctx) -> logger.warn("onFailedAttempt [tryCount: {}]", ctx.getExecutions())) .get(() -> BusinessLogic.execute(time));
上記の指定の場合に全部処理が失敗した場合以下のような結果になります
実行結果
21:18:36.937 [main] INFO net.yyuki.retry2.RetrySample3 - >>BusinessLogic call execute [param: 1536495516836] 21:18:36.943 [main] WARN net.yyuki.retry2.RetrySample3 - onFailedAttempt [tryCount: 1] 21:18:38.948 [main] WARN net.yyuki.retry2.RetrySample3 - onRetry [tryCount: 1] 21:18:38.948 [main] INFO net.yyuki.retry2.RetrySample3 - >>BusinessLogic call execute [param: 1536495516836] 21:18:38.948 [main] WARN net.yyuki.retry2.RetrySample3 - onFailedAttempt [tryCount: 2] 21:18:40.949 [main] WARN net.yyuki.retry2.RetrySample3 - onRetry [tryCount: 2] 21:18:40.949 [main] INFO net.yyuki.retry2.RetrySample3 - >>BusinessLogic call execute [param: 1536495516836] 21:18:40.949 [main] WARN net.yyuki.retry2.RetrySample3 - onFailedAttempt [tryCount: 3] 21:18:42.955 [main] WARN net.yyuki.retry2.RetrySample3 - onRetry [tryCount: 3] 21:18:42.955 [main] INFO net.yyuki.retry2.RetrySample3 - >>BusinessLogic call execute [param: 1536495516836] 21:18:42.955 [main] WARN net.yyuki.retry2.RetrySample3 - onFailedAttempt [tryCount: 4] 21:18:42.955 [main] WARN net.yyuki.retry2.RetrySample3 - Failed to execute. Max retries exceeded. 21:18:42.957 [main] ERROR net.yyuki.retry2.RetrySample3 - bussiness logic failed. net.yyuki.retry2.RetrySample3$BusinessLogicException: failed at net.yyuki.retry2.RetrySample3$BusinessLogic.execute(RetrySample3.java:54) at net.yyuki.retry2.RetrySample3.lambda$retryExecute$5(RetrySample3.java:41) at net.jodah.failsafe.SyncFailsafe.call(SyncFailsafe.java:145) at net.jodah.failsafe.SyncFailsafe.get(SyncFailsafe.java:56) at net.yyuki.retry2.RetrySample3.retryExecute(RetrySample3.java:41) at net.yyuki.retry2.RetrySample3.main(RetrySample3.java:24)
リトライが最大回数を超えてしまったタイミングで、onRetriesExceeded
への通知が行われていることがわかります。
まとめ
処理失敗時、リトライ時の状況を細かにハンドリングできることができました。
他にもメソッドや機能が存在していますが、ぜひ試してみてください。