0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Microprofile Fault Toleranceの機能と独自に用意したInterceptor間の実行順序を制御する

Posted at

環境

  • Java 8
  • CDI 2.0
  • Microprofile Fault Tolerance 2.0

事象

Microprofile Fault Tolerance 2.0 Circuit Breakerのアノテーションと、独自に用意した FooInterceptor を実行するアノテーションを併用した場合、サーキットブレーカが意図せず発動する事象に遭遇しました。

FooInterceptor
@Interceptor
@Dependent
@Foo
@Priority(Interceptor.Priority.APPLICATION)
public class FooInterceptor {
  
  @AroundInvoke
  public Object invoke(InvocationContext ic) throws Exception {
    try {
      return ic.proceed();

    } catch (SomeException e) {
      throw someCondition
        ? new WrappedException(e)
        : e;
    }
  }
}
FooInterceptorを呼び出すためのアノテーション
@Inherited
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Foo {}
サーキットブレーカとFooInterceptorを利用するクラス
@ApplicationScoped
public class SomeClass {

/**
 *  何かをする
 * @throws SomeException エラーが発生した
 * @throws WrappedException someConditionがtrueのときにエラーが発生した
 * @throws CircuitBreakerOpenException サーキットブレーカ発動中
 */
  @CircuitBreaker(
      successThreshold = 1,
      requestVolumeThreshold = 100,
      failureRatio = 1,
      delay = 600_000,
      failOn = {
        SomeException.class,
      })
  @Foo
  public void doSomeThing() {
    // 何かをする
  }
}

この実装では、someConditionがtrueの場合にはサーキットブレーカが発動しないことを期待していました。
SomeClass#doSomeThing にてthrowされた例外がまず FooInterceptor により WrappedException に変換される。WrappedExceptionfailOn にて定義していないため、サーキットブレーカは発動しないという想定です。しかし実際には、 SomeClass#doSomeThing が一定回数 WrappedException を返したところサーキットブレーカが発動し、その後は CircuitBreakerOpenException を返すようになりました。

解決策

以下の通り、FooInterceptorの優先順位を 5000 とすることで、someConditionがtrueの場合のサーキットブレーカ発動を回避することができました。

独自に用意したInterceptor
@Interceptor
@Dependent
@Foo
// @Priority(Interceptor.Priority.APPLICATION)
@Priority(5000)
public class FooInterceptor {
  // 省略
}

原因

本事象は、@CircuitBreaker が呼び出すInterceptorのPriorityよりも、 FooInterceptor のPriorityが高かったために発生しました。
実行されるべきInterceptorが複数存在するときの実行順序は、それらが持つPriorityにより決まりますFooInterceptor のPriorityがより高い場合には、まず FooInterceptor#invoke 、 次に @CircuitBreakerが呼び出すInterceptor#invoke 、最後に SomeClass#doSomething という順序でコールスタックが積まれていくことになります。このとき、 SomeClass#doSomething で発生した例外は @CircuitBreakerが呼び出すInterceptor#invoke が最初に受け取ることになるため、 FooInterceptor#invoke で実施している変換処理が呼ばれる前にサーキットブレーカ発動条件の評価が行われてしまいます。

これを避けるためには、 FooInterceptor のPriorityを、 @CircuitBreaker が呼び出すInterceptorのそれよりも低く設定する必要があります。そうすることで、コールスタックの順序が入れ替わり、FooInterceptor#invokeSomeClass#doSomething で発生した例外を先にcatchすることができるようになります。では、 @CircuitBreaker のPriorityはいくつに設定されているのでしょうか?

Microprofile Fault Tolerance 2.0の仕様では、以下のように記述されており、Microprofile Fault Toleranceの各Interceptorは Priority.PLATFORM_AFTER+10 (4010)から Priority.PLATFORM_AFTER+50 (4050)までの範囲にPriorityを定義するよう定めています。

The base priority of the lowest priority Fault Tolerance interceptor is Priority.PLATFORM_AFTER+10, which is 4010. If more than one Fault Tolerance interceptor is provided by an implementation, the priority number taken by Fault Tolerance interceptor(s) should be in the range of [base, base+40].

FooInterceptor はこれよりもPriorityを低くする、つまりPriorityの値を大きくする必要があります。もともと設定していた Priority.APPLICATION は2000ですので、全然足りていませんでした。4050 よりも大きい値にPriorityを設定することで、事象解消が可能になりました。

おわり。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?