14.3. MicroProfile Fault Tolerance

14.3.1. 概要

MicroProfile Fault Tolerance は、マイクロサービス環境においてサービスの耐障害性と回復性を実現するための機能を提供します。

14.3.2. 設定情報

MicroProfile Fault Tolerance の設定項目は、[ リファレンス > 設定 > マイクロサービスアプリケーション > MicroProfile Fault Tolerance ] を参照してください。

14.3.3. 提供機能

MicroProfile Fault Tolerance は、アプリケーションの実行論理と、実行時のエラーハンドリングを分離するための機能で、次のアノテーションを提供します。
表14.3.3-1
名称 説明 アノテーション名
Timeout タイムアウトの設定、および監視を定義します。 @Timeout
Retry リトライ回数、間隔等のリトライ条件を設定し、再施行します。 @Retry
Fallback メソッドが失敗した場合に実行するクラスを定義します。 @Fallback
Bulkhead サービスに対する同時呼び出しの数を制限します。 @Bulkhead
CircuitBreaker サービスが指定の割合で失敗した場合に、指定期間内は即時例外を返却するように定義します。 @CircuitBreaker
Asynchronous クラスの操作を非同期にします。 @Asynchronous
14.3.3.1. Timeout
タイムアウトの設定、監視を実施します。タイムアウトが発生した場合は、TimeoutExceptionをスローします。@Fallback、@CircuitBreaker、@Asynchronous、@Bulkhead、@Retryアノテーションと同時に指定することができます。
@Asynchronousの指定がない場合は、タイムアウトが発生すると、java.lang.Thread#interrupt()により割込みが発生します。ただし、次のような状況では、割込みは機能しません。
表14.3.3.1-1
パラメータ 説明 既定値
value タイムアウトと判断する時間を指定します。 1000
unit タイムアウト時間の単位を指定します。 ChronoUnit.MILLIS (ミリ秒)
14.3.3.2. Retry
リトライ回数、間隔等の設定から再施行を行います。クラスレベル、メソッドレベルでの指定が可能です。両方指定されている場合は、メソッドレベルのほうが優先されます。
@Fallback、@CircuitBreaker、@Asynchronous、@Bulkhead、@Timeoutアノテーションと同時に指定することができます。@Timeoutと同時に指定する場合、retryOnパラメータには、TimeoutExceptionや、そのスーパークラスを指定する必要があります。
表14.3.3.2-1
パラメータ 説明 既定値
maxRetries 最大リトライ回数を指定します。(-1は無限リトライを意味します) 3
delay 遅延時間を指定します。 0
delayUnit 遅延時間の単位を指定します。 ChronoUnit.MILLIS (ミリ秒)
maxDuration 最大試行時間を指定します。遅延時間より大きな値を指定します。 180000
durationUnit 最大試行時間の単位を指定します。 ChronoUnit.MILLIS (ミリ秒)
jitter ランダムにリトライ遅延時間を変えるゆらぎ値を指定します。−jitterから+jitterの範囲の値を遅延時間に加算します。 200
jitterDelayUnit ゆらぎ値の単位を指定します。 ChronoUnit.MILLIS (ミリ秒)
retryOn リトライを行う例外を指定します。カンマで区切ることで、複数指定が可能です。 Exception.class
abortOn アボートさせる例外を指定します。カンマで区切ることで、複数指定が可能です。 ""
14.3.3.3. Fallback
メソッドが失敗した場合に実行するFallbackHandlerの実装クラスや、メソッドを定義します。両方のパラメータが指定されていた場合や、fallbackMethodパラメータに指定されたメソッドが同一クラスに存在しない場合は、FaultToleranceDefinitionException例外をスローします。
@Retry、@CircuitBreaker、@Asynchronous、@Bulkhead、@Timeoutアノテーションと同時に指定することができます。BulkheadException、CircuitBreakerOpenException、TimeoutException等の例外発生もフォールバックのトリガーとなります。
表14.3.3.3-1
パラメータ 説明 既定値
value FallbackHandlerの実装クラスを指定します。 DEFAULT.class
fallbackMethod 同一クラス内のメソッド名を指定します。 ""
14.3.3.4. Bulkhead
サービスに対する同時呼び出しの数を制限します。
@Asynchronousアノテーションとともに利用した場合は、スレッドプール方式となり、そうでなければ、セマフォ方式となります。それ以外に、@Fallback、@CircuitBreaker、@Timeout、@Retryアノテーションと同時に指定することができます。@Fallbackが指定されていた場合は、BulkheadException例外をスローします。
表14.3.3.4-1
パラメータ 説明 既定値
value 同時に実行できるタスク数を指定します。0より大きい値を指定します。 10
waitingTaskQueue 待機させられるタスク数を指定します。0より大きい値を指定します。@Asynchronousアノテーションと同時に利用する場合にのみ指定できます。 10
CautionSTD
プロセスグループに配備するAP内でBulkheadアノテーションを使用する際は、プロセスグループのスレッド制御設定によるスレッド数上限が優先されます。
プロセスグループの設定値については、「プロセスグループの設定値の説明」を参照してください。
14.3.3.5. CircuitBreaker
システムに過負荷が及ぶことを防止するために、フェイル・ファストを実行する方法を提供します。
@Timeout、@Fallback、@Asynchronous、@Bulkhead、@Retryアノテーションと同時に指定することができます。サーキットブレーカーが開くと、CircuitBreakerOpenException例外がスローされ、@Fallbackが指定されている場合は、そこで定義されたハンドラや、メソッドが実行されます。
表14.3.3.5-1
パラメータ 説明 既定値
failOn 異常とみなす例外やエラー(java.lang.Throwableを継承するクラス)を指定します。カンマで区切ることで、複数指定が可能です。 Throwable.class
delay 遅延時間を指定します。0以上の値を指定します。0は遅延なしを意味します。 5000
delayUnit 遅延時間の単位を指定します。 ChronoUnit.MILLIS
requestVolumeThreshold 異常を監視する要求回数を指定します。1以上の値を指定します。 20
failureRatio ブレーカーを開くトリガーとなる、監視要求回数のうちの異常発生回数の割合を指定します。0から1の間の値を指定します。 0.50
successThreshold ブレーカーを閉じる連続成功回数を指定します。1以上の値を指定します。 1

サーキットブレーカには、Closed、Open、Half-openの3つの状態があり、パラメータに指定した値に従って下記の図の様に遷移します。
サーキットブレーカーの状態
図14.3.3.5-1

既定値の場合、20回中10回のリクエスト送信で異常が発生した場合にブレーカーを開き、5000ミリ秒後に半開状態となってリクエスト送信を再開し、1回成功すればブレーカーを閉じます。 半開状態でリクエスト送信に失敗した場合は、再びブレーカーを開きます。
14.3.3.6. Asynchronous
クラスの操作を非同期に設定します。
アノテーションを指定するメソッドの返却値は、java.util.concurrent.Futureオブジェクトである必要があります。@Timeout、@Fallback、@Bulkhead、@Retryアノテーションと同時に指定することができます。
表14.3.3.6-1
パラメータ 説明 既定値
(パラメータ無し)
14.3.3.7. ChronoUnit
MicroProfile Fault Toleranceのアノテーション用ChronoUnit定義です。
表14.3.3.7-1
単位
YEARS
MONTHS
WEEKS
DAYS
HOURS
MINUTES
SECONDS
MILLIS ミリ秒
NANOS ナノ秒
14.3.3.8. メトリクス情報
MicroProfile Fault Tolerance が提供するメトリクス情報です。
[name] には該当メソッドの完全修飾名を設定します。
表14.3.3.8-1 共通
名前 タイプ 単位 説明
ft.[name].invocations.total Counter なし メソッドが呼び出された回数。
ft.[name].invocations.failed.total Counter なし メソッドが呼び出され、全てのFault Tolerance 処理を完了後、例外をスローした回数。

表14.3.3.8-2 Retryアノテーション
名前 タイプ 単位 説明
ft.[name].retry.callsSucceededNotRetried.total Counter なし メソッドが呼び出され、リトライせずに成功した回数。
ft.[name].retry.callsSucceededRetried.total Counter なし メソッドが呼び出され、少なくとも1回リトライした後に成功した回数。
ft.[name].retry.callsFailed.total Counter なし メソッドが呼び出され、リトライした後に最終的に失敗した回数。
ft.[name].retry.retries.total Counter なし メソッドがリトライした回数。

表14.3.3.8-3 Timeoutアノテーション
名前 タイプ 単位 説明
ft.[name].timeout.executionDuration Histogram ナノ秒 メソッド実行時間のヒストグラム。
ft.[name].timeout.callsTimedOut.total Counter なし メソッドがタイムアウトした回数。
ft.[name].timeout.callsNotTimedOut.total Counter なし メソッドがタイムアウトせずに完了した回数。

表14.3.3.8-4 CircuitBreakerアノテーション
名前 タイプ 単位 説明
ft.[name].circuitbreaker.callsSucceeded.total Counter なし CircuitBreakerによって実行が許可され、その後成功した回数。
ft.[name].circuitbreaker.callsFailed.total Counter なし CircuitBreakerによって実行が許可され、その後失敗した回数。
ft.[name].circuitbreaker.callsPrevented.total Counter なし CircuitBreakerによって実行が遮断された回数。
ft.[name].circuitbreaker.open.total Gauge ナノ秒 CircuitBreakerがOpen状態で費やした時間。
ft.[name].circuitbreaker.halfOpen.total Gauge ナノ秒 CircuitBreakerがHalf-open状態で費やした時間。
ft.[name].circuitbreaker.closed.total Gauge ナノ秒 CircuitBreakerがClosed状態で費やした時間。
ft.[name].circuitbreaker.opened.total Counter なし CircuitBreakerがClosed状態からOpen状態に移行した回数。

表14.3.3.8-5 Bulkheadアノテーション
名前 タイプ 単位 説明
ft.[name].bulkhead.concurrentExecutions Gauge なし 現在同時実行中の数。
ft.[name].bulkhead.callsAccepted.total Counter なし Bulkheadにより受理された呼び出し回数。
ft.[name].bulkhead.callsRejected.total Counter なし Bulkheadにより拒否された呼び出し回数。
ft.[name].bulkhead.executionDuration Histogram ナノ秒 メソッド実行時間のヒストグラム。キューでの待機時間は含まない。
ft.[name].bulkhead.waitingQueue.population* Gauge なし 現在キューで待機している数。
ft.[name].bulkhead.waiting.duration* Histogram ナノ秒 キューでの待機に費やした時間のヒストグラム。

表14.3.3.8-6 Fallbackアノテーション
名前 タイプ 単位 説明
ft.[name].fallback.calls.total Counter なし Fallbackハンドラーまたはメソッドが呼び出された回数。

14.3.4. 記述例


@Timeoutを指定した場合の記述例を下記に示します。
(サンプルプロジェクト Jaxrs20SampleJSON_with_MFT.zip)
package sample;

import java.util.Map;

import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.eclipse.microprofile.faulttolerance.Timeout;

import sample.resource.Customer;
import sample.util.Util;

@ApplicationScoped
@Path("/timeout")
public class CustomerResource1 {

    @Path("{id}")
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Timeout(500)
    public Customer getCustomerList(@PathParam("id") String id) throws Exception {
        // load data
        Map<String, Customer> map = Util.loadData("/sample/data/customerlist.txt");
        Customer customer = map.get(id);
        return customer;
    }

    public Customer fallbackService(@PathParam("id") String id) {
        // load sorry data
        Map<String, Customer> map = Util.loadData("/sample/data/sorrylist.txt");
        Customer customer = map.get("0001");
        return customer;
    }
}      
  上記メソッド(getCustomerList())を呼び出した際、処理が500ミリ秒で終了しない場合はTimeoutExceptionをスローします。
  

@Retryを指定した場合の記述例を以下に示します。
(サンプルプロジェクト Jaxrs20SampleJSON_with_MFT.zip)
package sample;

import java.util.Map;

import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.eclipse.microprofile.faulttolerance.Retry;
import org.eclipse.microprofile.faulttolerance.Timeout;

import sample.resource.Customer;
import sample.util.Util;

@ApplicationScoped
@Path("/retry")
public class CustomerResource2 {

    @Path("{id}")
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Timeout(500)
    @Retry(maxRetries=1)
    public Customer getCustomerList(@PathParam("id") String id) throws Exception {
    	// load data
        Map<String, Customer> map = Util.loadData("/sample/data/customerlist.txt");
        Customer customer = map.get(id);
        return customer;
    }

    public Customer fallbackService(@PathParam("id") String id) {
        // load sorry data
        Map<String, Customer> map = Util.loadData("/sample/data/sorrylist.txt");
        Customer customer = map.get("0001");
        return customer;
    }
}
  上記メソッド(getCustomerList())の呼び出しにおいて、最大1回まで処理をリトライします。
  

@Fallbackを指定した場合の記述例を下記に示します。
(サンプルプロジェクト Jaxrs20SampleJSON_with_MFT.zip)
package sample;

import java.util.Map;

import javax.enterprise.context.RequestScoped;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.eclipse.microprofile.faulttolerance.Fallback;
import org.eclipse.microprofile.faulttolerance.Timeout;

import sample.resource.Customer;
import sample.util.Util;

@RequestScoped
@Path("/fallback")
public class CustomerResource3 {

    @Path("{id}")
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Timeout(500)
    @Fallback(fallbackMethod="fallbackService")
    public Customer getCustomerList(@PathParam("id") String id) throws Exception {
        // load data
    	throw new Exception("test!");
    }

    public Customer fallbackService(@PathParam("id") String id) {
        // load sorry data
        Map<String, Customer> map = Util.loadData("/sample/data/sorrylist.txt");
        Customer customer = map.get("0001");
        return customer;
    }
}
  上記メソッド(getCustomerList())の呼び出しにおいて例外が検知された場合、指定された代替メソッド(fallbackService())が実行され、代替メソッドの結果が返されます。

@Bulkheadを指定した場合の記述例を下記に示します。
(サンプルプロジェクト Jaxrs20SampleJSON_with_MFT.zip)
package sample;

import java.util.Map;
import java.util.logging.Logger;

import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.eclipse.microprofile.faulttolerance.Bulkhead;

import com.nec.webotx.webservice.xml.ws.util.Log;

import sample.resource.Customer;
import sample.util.Util;

@ApplicationScoped
@Path("/bulkhead")
public class CustomerResource5 {

	Logger log = Log.getOTXLogger();

    @Path("{id}")
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Bulkhead(value=1)
    public Customer getCustomerList(@PathParam("id") String id) throws Exception {
        // load data
        Map<String, Customer> map = Util.loadData("/sample/data/customerlist.txt");
        Customer customer = map.get(id);
        return customer;
    }

    public Customer fallbackService(@PathParam("id") String id) {
        // load sorry data
        Map<String, Customer> map = Util.loadData("/sample/data/sorrylist.txt");
        Customer customer = map.get("0001");
        return customer;
    }
}
  上記メソッド(getCustomerList())の呼び出しにおいて、同時呼び出し数が1件に制限されます。
1件を超える呼び出しを行った場合BulkheadExceptionをスローします。

@CircuitBreakerを指定した場合の記述例を下記に示します。
(サンプルプロジェクト Jaxrs20SampleJSON_with_MFT.zip)
 
package sample;

import java.util.Map;

import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.eclipse.microprofile.faulttolerance.CircuitBreaker;
import org.eclipse.microprofile.faulttolerance.Timeout;

import sample.resource.Customer;
import sample.util.Util;

@ApplicationScoped
@Path("/circuitbreaker")
public class CustomerResource4 {

    @Path("{id}")
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Timeout(500)
    @CircuitBreaker(requestVolumeThreshold=2, failureRatio=0.5)
    public Customer getCustomerList(@PathParam("id") String id) throws Exception {
        // load data
        Map<String, Customer> map = Util.loadData("/sample/data/customerlist.txt");
        Customer customer = map.get(id);
        return customer;
    }

    public Customer fallbackService(@PathParam("id") String id) {
        // load sorry data
        Map<String, Customer> map = Util.loadData("/sample/data/sorrylist.txt");
        Customer customer = map.get("0001");
        return customer;
    }
}
  上記メソッド(getCustomerList())の呼び出しにおいて、呼び出し回数2回中、1回以上失敗した場合はサーキットブレーカーを開きます。
サーキットブレーカーが開いている状態で呼び出しを行った場合はCircuitBreakerOpenExceptionをスローします。

@Asynchronousを指定した場合の記述例を下記に示します。
(サンプルプロジェクト Jaxrs20SampleJSON_with_MFT.zip)
package sample;

import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;

import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.eclipse.microprofile.faulttolerance.Asynchronous;

import sample.resource.Customer;
import sample.util.Util;

@ApplicationScoped

@Path("/async")
public class CustomerResource6 {

    @Path("{id}")
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Asynchronous
    public Future<Customer> getCustomerList(@PathParam("id") String id) throws Exception {
        // load data
        Map<String, Customer> map = Util.loadData("/sample/data/customerlist.txt");
        Customer customer = map.get(id);
        return CompletableFuture.completedFuture(customer);
    }

    public Future<Customer> fallbackService(@PathParam("id") String id) {
        // load sorry data
        Map<String, Customer> map = Util.loadData("/sample/data/sorrylist.txt");
        Customer customer = map.get("0001");
        return CompletableFuture.completedFuture(customer);
    }
}
  上記メソッド(getCustomerList())を呼び出した際、処理は呼び出し元とは別スレッドで非同期に実行されます。