2010年08月06日
実践SNMP+Java - Timeout Model
今回はタイムアウトとリトライ数について考えます。
サンプルコードを使って動く・動かないを検証するだけであればタイムアウトとリトライはさほど気にすることは無いパラメータですが、実践ではいざという時に重要なパラメータになります。
まずはサンプルコードを見て下さい。
<Javaコード(SNMP Get)>
- // target oid
- String sysNameInstance = ".1.3.6.1.2.1.1.5.0";
- // SNMP paramerters
- CommunityTarget comTgt = new CommunityTarget();
- comTgt.setAddress( new UdpAddress("192.168.1.1/161") ); // UDP address
- comTgt.setCommunity( new OctetString("public") ); // SNMP community name
- comTgt.setTimeout(1000); // timeout
- comTgt.setRetries(2); // retries
- // create PDU
- PDU pdu = new PDU();
- pdu.setType(PDU.GET);
- OID oid = new OID(sysNameInstance);
- pdu.add(new VariableBinding(oid));
- // SNMP operation basic object
- Snmp snmp = null;
- // DefaultUdpTransportMapping or DefaultTcpTransportMapping
- DefaultUdpTransportMapping utm = null;
- try {
- utm = new DefaultUdpTransportMapping();
- snmp = new Snmp(utm);
- //snmp.setTimeoutModel(new MyTimeout());
- snmp.listen(); // open port and wait
- // SNMP Get
- long start = System.currentTimeMillis();
- ResponseEvent response = snmp.get(pdu, comTgt);
- long end = System.currentTimeMillis();
- System.out.println("SNMP TIME = "+ (end - start));
- // response PDU
- PDU resPdu = response.getResponse();
- if ( resPdu != null && resPdu.getErrorStatus() == SnmpConstants.SNMP_ERROR_SUCCESS ) {
- // number of variable bindings included in response PDU
- int numVarBinds = resPdu.size();
- for (int i=0; i<numVarBinds; i++) {
- VariableBinding var = resPdu.get(i);
- if ( !var.isException() ) {
- if (var.getSyntax() == SMIConstants.SYNTAX_TIMETICKS) {
- System.out.println(var.getOid() + " -> " + var.getVariable().toInt());
- } else {
- System.out.println(var.getOid() + " -> " + var.getVariable().toString());
- }
- } else {
- System.out.println(var.getOid() + " -> " + "has exception syntax!");
- }
- }
- } else {
- if (resPdu == null) {
- // when response PDU is NULL, SNMP GetRequest timed out.
- System.out.println("request timed out.");
- } else {
- System.out.println(resPdu.getErrorStatus()+"/"+resPdu.getErrorStatusText());
- }
- }
- // close port and release resources
- snmp.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
このサンプルコードでは、system.sysName.0をSNMP Getで取得しています。
試すときには、192.168.1.1の変わりにネットワーク上に存在しないIPアドレスを設定して下さい。
もちろん、タイムアウトが発生します。
<実行結果(SNMP Get)>
SNMP TIME = 3000request timed out.
このコードでは、タイムアウトを1000ミリ秒、リトライ数を2回に設定していますから、初回リクエスト(1000ミリ秒)+リトライ(1000ミリ秒 * 2)で合計3000ミリ秒になります。
<タイムアウトの発生例>
これはあるネットワーク機器から1秒間隔でMIBテーブルの値をGetNextでポーリングしていた際の通信ログ(tcpdump)を見やすく整形した表です。
GetNextリクエスト/レスポンス毎に1行で記載しています。
GetNextリクエスト/レスポンス毎に1行で記載しています。
この時には1秒間隔で、1ポーリングサイクル毎に約200行分のオブジェクトを取得していました。
まず、19時26分17.3秒ごろ(id=12)からタイムアウトが発生し始めます。
この時のSNMPパラメータはタイムアウトが500ミリ秒、リトライ数が2回なので、delta time(current/previous)を見るとその通りに動作しています。
各リトライ、タイムアウトのセット毎にブルー、グリーン、オレンジで塗りつぶしてあります。
ここでブルーの部分に注目すると、id 12~14のPDUはアプリケーション上、タイムアウトしていますが、id 21~23でレスポンスが戻ってきています。
(しかし、アプリケーションでは無視されます)
この際のリクエスト/レスポンス間の経過時間を見ると、送信してから4~5秒を要しています。
通常、2ミリ秒程度でレスポンスを受信できることを考えると、異常な値です。
しかし、実践では起こり得るのです。
この際には相手側機器(エージェント)であるCISCO社製ルータのCPU使用率が高くなっておりSNMPサービスの優先度が下がっていたと考えられます。
この状態はその後も数秒間続き、id 20のリクエストが約0.3秒で戻ってからは、正常な状態に復旧します。
これは短時間ではありますが輻輳と呼ばれる現象で、ネットワーク・リソースが不足している状況でリトライによりさらにリソースを消費し状況の悪化を招いています。
今回のデータだけ見ると単純にタイムアウト時間を大きくしたくなりますが、もし、本当にネットワーク機器がダウンしている場合には、余計な時間を消費することになり、迅速な障害検知との間にトレードオフが生まれます。
このような状態ではリトライ回数に応じてタイムアウト時間を増加方向に変動させることでそのトレードオフを最小限にするのがベスト。
では実際に実装してみましょう。
SNMP4Jでは、TimeoutModelインタフェースを実装したクラスを用意することで独自のタイムアウト時間変動を実装することが出来ます。
<Javaコード(MyTimeout)>
- public class MyTimeout implements TimeoutModel {
- @Override
- public long getRequestTimeout(int totalNumberOfRetries, long targetTimeout) {
- // blog:
- // タイムアウト時間の総量を戻します。
- // 総量はリトライ数とタイムアウト初期値から求めます。
- /*
- * Gets the timeout for the specified retry.
- * (a zero value for retryCount specifies the first request)
- *
- * Parameters:
- * totalNumberOfRetries - the total number of retries configured for the target.
- * targetTimeout - the timeout as specified for the target in milliseconds.
- *
- * Returns:
- * the time in milliseconds when the request will be timed out finally.
- *
- */
- // total timeout in milliseconds.
- long totalTimeoutInMillis = 0;
- for (int retryCount=0; retryCount<=totalNumberOfRetries; retryCount++) {
- // add current timeout in milliseconds
- totalTimeoutInMillis += getRetryTimeout(retryCount, totalNumberOfRetries, targetTimeout);
- }
- return totalTimeoutInMillis;
- }
- @Override
- public long getRetryTimeout(int retryCount, int totalNumberOfRetries, long targetTimeout) {
- // blog: リトライ毎に設定するタイムアウト時間(ミリ秒)を戻します。
- /*
- * <javadoc>
- * Gets the timeout for all retries, which is defined as the sum of
- * getRetryTimeout(int retryCount, int totalNumberOfRetries,
- * long targetTimeout) for all retryCount in 0 <= retryCount < totalNumberOfRetries.
- *
- * Parameters:
- * retryCount - the number of retries already performed for the target.
- * totalNumberOfRetries - the total number of retries configured for the target.
- * targetTimeout - the timeout as specified for the target in milliseconds.
- *
- * Returns:
- * long the timeout duration in milliseconds for the supplied retry.
- */
- // 初期値タイムアウト値に+(リトライ回数 * 500)ミリ秒します。
- long currentTimeoutInMillis = targetTimeout + retryCount * 500;
- return currentTimeoutInMillis;
- }
- }
<説明(MyTimeout)>
getRequestTimeout(全タイムアウト時間)、getRetryTimeout(リトライ毎のタイムアウト時間)メソッドを実装します。
今回は、リトライ回数に応じてリトライ数*500ミリ秒を初期値タイムアウト値に加算するロジックを実装しています。
初期値が1秒で、リトライ数が1回であれば、リトライ1回目では1.5秒待ちます。ちなみに僕が実践で使う場合にはもう少し変わった実装をします。
<結果(MyTimeout)>
SNMP Getで説明したコードの21行目のコメントを外すとMyTimeoutモデルが反映されます。
SNMP TIME = 4516request timed out.
1000ミリ秒 + 1500ミリ秒 + 2000ミリ秒で計4500秒になっています。
さすがSNMP4Jの開発者は実践を知ってますねぇ。
ダグラス・R. マウロ ケビン・J. シュミット
オライリー・ジャパン
売り上げランキング: 257,278
オライリー・ジャパン
売り上げランキング: 257,278
Java ネットワーク プログラミング 基礎からわかる 完全入門
posted with amazlet at 13.01.12
永嶋 浩
技術評論社
売り上げランキング: 228,941
技術評論社
売り上げランキング: 228,941
Posted by netbuffalo at 19:49│TrackBack(0)│
│実践SNMP+Java