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()により割込みが発生します。ただし、次のような状況では、割込みは機能しません。
- スレッドがブロッキングI/O(DBやファイルの読み書き)でブロックされている場合や、NIOチャネルの例外発生のみを待ち受けている場合
- スレッドが待ち合わせを行っていない場合
- スレッドがInterruptedExceptionをキャッチし、割込みを無視している場合
表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())を呼び出した際、処理は呼び出し元とは別スレッドで非同期に実行されます。