/**
* personium.io
* Copyright 2014 FUJITSU LIMITED
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fujitsu.dc.common.es.impl;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.NoShardAvailableActionException;
import org.elasticsearch.client.transport.NoNodeAvailableException;
import org.elasticsearch.cluster.block.ClusterBlockException;
import org.elasticsearch.common.util.concurrent.UncategorizedExecutionException;
import org.elasticsearch.transport.NodeDisconnectedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fujitsu.dc.common.es.response.EsClientException;
/**
* Elasticsearchへリトライ可能な処理を記述する抽象クラス.
* リクエストの実際の実装は、抽象メソッド {@link #doProcess()} に記述すること.
* 呼出し元は、#doRequest()メソッドを呼び出すこと。
* @param <T> 正常終了時のレスポンスの型. void の場合は Void
*/
abstract class AbstractRetryableEsRequest<T> {
static Logger log = LoggerFactory.getLogger(AbstractRetryableEsRequest.class);
int retryCount = 0;
long retryInterval = 0;
String description;
// 初回呼び出し時のみ true. リトライに入っている間は falseとなる。
boolean firstAttempt = true;
/**
* コンストラクタ.
* @param argRetryCount リトライ回数
* @param argRetryInterval リトライ間隔
* @param requestDesc ログ出力時に利用されるメソッド記述.
*/
public AbstractRetryableEsRequest(int argRetryCount, long argRetryInterval, String requestDesc) {
retryCount = argRetryCount;
retryInterval = argRetryInterval;
description = requestDesc;
}
/**
* #onParticularError()メソッド処理後に、リトライを継続させるために投げるオブジェクト.
*/
@SuppressWarnings("serial")
public static class ContinueRetry extends RuntimeException {
/**
* コンストラクタ.
*/
public ContinueRetry() {
super();
}
}
/**
* ESへのリクエストを実行する.
* リクエスト初回に以下の4種の例外のいずれかが発生した場合、リトライ処理を行う.
* <ul>
* <li>NodeDisconnectedException</li>
* <li>NoNodeAvailableException</li>
* <li>NoShardAvailableActionException</li>
* <li>ClusterBlockException</li>
* </ul>
* @return レスポンスオブジェクト
*/
public T doRequest() {
firstAttempt = true;
boolean continueRetry = false;
try {
return doProcess();
} catch (ElasticsearchException e) {
if (isParticularError(e)) {
// 検出された例外を特別扱いする場合の処理呼び出し
try {
return onParticularError(e);
} catch (ContinueRetry e2) {
// リトライ処理へ移行する.
continueRetry = true;
} catch (ElasticsearchException e2) {
// #onParticulorError()内で適切に対処されなかった ElasticsearchExceptionは
// EsClientExceptionラップして投げる.
throw new EsClientException(description + " failed", e);
}
}
// translogのRead時のポインタ位置不正による例外(ES1.2.1のバグ)の場合には、flushを実行しリトライする
// UncategorizedExecutionExceptionはtranslog読込以外の例外の場合にもスローされてくる可能性があるが、
// 判別できないため、それらの場合にも本ルートに乗せる
if (e instanceof UncategorizedExecutionException) {
flushTransLog();
}
log.info(e.getClass().getName() + " : " + e.getMessage());
// 以下の例外の場合はリトライをする。
if (continueRetry
|| e instanceof NodeDisconnectedException || e instanceof NoNodeAvailableException
|| e instanceof NoShardAvailableActionException || e instanceof ClusterBlockException
|| e instanceof UncategorizedExecutionException) {
log.info("Proceed to retry loop.");
continueRetry = false; // 念のため
return retryRequest();
}
// 上記以外の場合、リトライの意味はないため、EsClientExceptionにラップしてそのまま投げる。
throw new EsClientException(description + " failed", e);
}
}
/**
* Elasticsearchへのリクエストを実装するための抽象メソッド.
* 利用者はこのメソッドをオーバーライドすること.
* @return レスポンス
*/
abstract T doProcess();
/**
* リトライ時、引数に指定された例外を特別扱いする場合、trueを返すようにオーバーライドすること.
* これにより、#onParticularErrorメソッドが呼び出される.
* 標準実装では, 常に falseを返す.
* @param e 検査対象の例外
* @return true: 正常終了として扱う場合, false: 左記以外の場合
*/
boolean isParticularError(ElasticsearchException e) {
return false;
}
/**
* 特定の例外が発生した場合に、正常終了で復帰させる等、特定の処理を行うためにはこのメソッドをオーバーライドすること.
* 本メソッドは、#isParticularError() が trueを返した時のみ呼び出される.
* <ul>
* <li>本メソッドから AbstractRetryableEsRequest.ContinueRetry例外が投げられた場合、引き続きリトライ処理が行われる。</li>
* <li>本メソッドから ElasticsearchExceptionが投げられた場合、#doRequest()の呼び出し元には、EsClientExceptionに ラップされた例外が返される。</li>
* <li>例外を返さずに何らかの復帰値を返した場合は、呼び出し元にはその値が返される。</li>
* </ul>
* 標準実装では引数に与えられた例外をそのまま投げ返す.
* @param e 特定例外
* @return レスポンス
*/
T onParticularError(ElasticsearchException e) {
throw e;
}
/**
* Elasticsearchへのリクエストをリトライする.
* 以下の4つの例外が発生した場合のみリトライし、それ以外は、EsClientExceptionを投げて中断する。
* <ul>
* <li>NodeDisconnectedException</li>
* <li>NoNodeAvailableException</li>
* <li>NoShardAvailableActionException</li>
* <li>ClusterBlockException</li>
* </ul>
* @return レスポンス
*/
private T retryRequest() {
firstAttempt = false;
Exception lastError = null;
for (int i = 0; i < retryCount; i++) {
log.info(description + ": retry " + (i + 1));
try {
// 少し待機
Thread.sleep(retryInterval);
// 再度リクエストを実行する。
return doProcess();
} catch (ElasticsearchException e) {
lastError = e;
if (isParticularError(e)) {
// 検出された例外を特別扱いする場合の処理呼び出し
try {
return onParticularError(e);
} catch (ContinueRetry e2) {
continue;
} catch (ElasticsearchException e2) {
// #onParticulorError()内で適切に対処されなかった ElasticsearchExceptionは
// EsClientExceptionラップして投げる.
throw new EsClientException(description + " failed", e);
}
} else if (e instanceof UncategorizedExecutionException) {
// translogのRead時のポインタ位置不正による例外(ES1.2.1のバグ)の場合には、flushを実行しリトライする
// UncategorizedExecutionExceptionはtranslog読込以外の例外の場合にもスローされてくる可能性があるが、
// 判別できないため、それらの場合にも本ルートに乗せる
flushTransLog();
continue;
} else if (e instanceof NodeDisconnectedException || e instanceof NoNodeAvailableException
|| e instanceof NoShardAvailableActionException || e instanceof ClusterBlockException) {
// これらの例外の場合、ESの状態が不正か通信エラー等の原因が考えられるため、リトライを継続。
continue;
}
// 上記以外の例外は、明確なエラー発生と考えられるため、例外を返す。
throw new EsClientException(description + " failed", e);
} catch (InterruptedException e) {
// #sleep()中の例外。外部から中断された場合などが想定される。
throw new EsClientException(description + " failed", e);
}
}
// リトライ回数を超えた場合、最後のエラーを返却する。
throw new EsClientException.EsNoResponseException(description + " failed", lastError);
}
abstract EsTranslogHandler getEsTranslogHandler();
/**
* translogをflushする.
*/
protected void flushTransLog() {
getEsTranslogHandler().flushTranslog();
}
}