2010年08月06日

実践SNMP+Java - Timeout Model

今回はタイムアウトとリトライ数について考えます。
 
サンプルコードを使って動く・動かないを検証するだけであればタイムアウトとリトライはさほど気にすることは無いパラメータですが、実践ではいざという時に重要なパラメータになります。

まずはサンプルコードを見て下さい。

<Javaコード(SNMP Get)>
 
  1.     // target oid
  2.     String sysNameInstance = ".1.3.6.1.2.1.1.5.0";
  3.     // SNMP paramerters
  4.     CommunityTarget comTgt = new CommunityTarget();
  5.     comTgt.setAddress( new UdpAddress("192.168.1.1/161") ); // UDP address
  6.     comTgt.setCommunity( new OctetString("public") ); // SNMP community name
  7.     comTgt.setTimeout(1000); // timeout
  8.     comTgt.setRetries(2); // retries
  9.     // create PDU
  10.     PDU pdu = new PDU();
  11.     pdu.setType(PDU.GET);
  12.     OID oid = new OID(sysNameInstance);
  13.     pdu.add(new VariableBinding(oid));
  14.     // SNMP operation basic object
  15.     Snmp snmp = null;
  16.     // DefaultUdpTransportMapping or DefaultTcpTransportMapping
  17.     DefaultUdpTransportMapping utm = null;
  18.     try {
  19.       utm = new DefaultUdpTransportMapping();
  20.       snmp = new Snmp(utm);
  21.       //snmp.setTimeoutModel(new MyTimeout());
  22.       snmp.listen(); // open port and wait
  23.       // SNMP Get
  24.       long start = System.currentTimeMillis();
  25.       ResponseEvent response = snmp.get(pdu, comTgt);
  26.       long end = System.currentTimeMillis();
  27.       System.out.println("SNMP TIME = "+ (end - start));
  28.       // response PDU
  29.       PDU resPdu = response.getResponse();
  30.       if ( resPdu != null && resPdu.getErrorStatus() == SnmpConstants.SNMP_ERROR_SUCCESS ) {
  31.         // number of variable bindings included in response PDU
  32.         int numVarBinds = resPdu.size();
  33.         for (int i=0; i<numVarBinds; i++) {
  34.           VariableBinding var = resPdu.get(i);
  35.           if ( !var.isException() ) {
  36.             if (var.getSyntax() == SMIConstants.SYNTAX_TIMETICKS) {
  37.               System.out.println(var.getOid() + " -> " + var.getVariable().toInt());
  38.             } else {
  39.               System.out.println(var.getOid() + " -> " + var.getVariable().toString());
  40.             }
  41.           } else {
  42.             System.out.println(var.getOid() + " -> " + "has exception syntax!");
  43.           }
  44.         }
  45.       } else {
  46.         if (resPdu == null) {
  47.           // when response PDU is NULL, SNMP GetRequest timed out.
  48.           System.out.println("request timed out.");
  49.         } else {
  50.           System.out.println(resPdu.getErrorStatus()+"/"+resPdu.getErrorStatusText());
  51.         }
  52.       }
  53.       // close port and release resources
  54.       snmp.close();
  55.     } catch (IOException e) {
  56.       e.printStackTrace();
  57.     }
 
このサンプルコードでは、system.sysName.0をSNMP Getで取得しています。

これまでに説明したGetリクエストと変わりはありませんが、24~27行目で、snmp.get(pdu, comTgt)の所要時間を計測しています。
 
試すときには、192.168.1.1の変わりにネットワーク上に存在しないIPアドレスを設定して下さい。

もちろん、タイムアウトが発生します。

 
<実行結果(SNMP Get)>

SNMP TIME = 3000
request timed out.
 
このコードでは、タイムアウトを1000ミリ秒、リトライ数を2回に設定していますから、初回リクエスト(1000ミリ秒)+リトライ(1000ミリ秒 * 2)で合計3000ミリ秒になります。

 
<タイムアウトの発生例>
 
timeout_model1左の表をご覧下さい。
これはあるネットワーク機器から1秒間隔でMIBテーブルの値をGetNextでポーリングしていた際の通信ログ(tcpdump)を見やすく整形した表です。
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)>
 
  1. public class MyTimeout implements TimeoutModel {
  2.   @Override
  3.   public long getRequestTimeout(int totalNumberOfRetries, long targetTimeout) {
  4.     // blog:
  5.     // タイムアウト時間の総量を戻します。
  6.     // 総量はリトライ数とタイムアウト初期値から求めます。
  7.     /*
  8.      * Gets the timeout for the specified retry.
  9.      * (a zero value for retryCount specifies the first request)
  10.      *
  11.      * Parameters:
  12.      *  totalNumberOfRetries - the total number of retries configured for the target.
  13.      *  targetTimeout - the timeout as specified for the target in milliseconds.
  14.      *
  15.      * Returns:
  16.      *  the time in milliseconds when the request will be timed out finally.
  17.      *
  18.      */
  19.     // total timeout in milliseconds.
  20.     long totalTimeoutInMillis = 0;
  21.     for (int retryCount=0; retryCount<=totalNumberOfRetries; retryCount++) {
  22.       // add current timeout in milliseconds
  23.       totalTimeoutInMillis += getRetryTimeout(retryCount, totalNumberOfRetries, targetTimeout);
  24.     }
  25.     return totalTimeoutInMillis;
  26.   }
  27.   @Override
  28.   public long getRetryTimeout(int retryCount, int totalNumberOfRetries, long targetTimeout) {
  29.     // blog: リトライ毎に設定するタイムアウト時間(ミリ秒)を戻します。
  30.     /*
  31.      * <javadoc>
  32.      * Gets the timeout for all retries, which is defined as the sum of
  33.      * getRetryTimeout(int retryCount, int totalNumberOfRetries,
  34.      * long targetTimeout) for all retryCount in 0 <= retryCount < totalNumberOfRetries.
  35.      *
  36.      * Parameters:
  37.      *  retryCount - the number of retries already performed for the target.
  38.      *  totalNumberOfRetries - the total number of retries configured for the target.
  39.      *  targetTimeout - the timeout as specified for the target in milliseconds.
  40.      *
  41.      * Returns:
  42.      *  long the timeout duration in milliseconds for the supplied retry.
  43.      */
  44.     // 初期値タイムアウト値に+(リトライ回数 * 500)ミリ秒します。
  45.     long currentTimeoutInMillis = targetTimeout + retryCount * 500;
  46.     return currentTimeoutInMillis;
  47.   }
  48. }

 
<説明(MyTimeout)>
getRequestTimeout(全タイムアウト時間)、getRetryTimeout(リトライ毎のタイムアウト時間)メソッドを実装します。
今回は、リトライ回数に応じてリトライ数*500ミリ秒を初期値タイムアウト値に加算するロジックを実装しています。 
初期値が1秒で、リトライ数が1回であれば、リトライ1回目では1.5秒待ちます。ちなみに僕が実践で使う場合にはもう少し変わった実装をします。

 
<結果(MyTimeout)>
 
SNMP Getで説明したコードの21行目のコメントを外すとMyTimeoutモデルが反映されます。
 
SNMP TIME = 4516
request timed out.
 
1000ミリ秒 + 1500ミリ秒 + 2000ミリ秒で計4500秒になっています。
 
さすがSNMP4Jの開発者は実践を知ってますねぇ。


入門SNMP
入門SNMP
posted with amazlet at 13.01.12
ダグラス・R. マウロ ケビン・J. シュミット
オライリー・ジャパン
売り上げランキング: 257,278

Java ネットワーク プログラミング 基礎からわかる 完全入門
永嶋 浩
技術評論社
売り上げランキング: 228,941

Posted by netbuffalo at 19:49│TrackBack(0) 実践SNMP+Java 


この記事へのトラックバックURL