以下の例は、カスタム・アドバイザーを実装できる方法を示します。

標準アドバイザー

このサンプル・ソース・コードは、標準 Load Balancer HTTP アドバイザーに似ています。 以下のように機能します。

  1. 送信要求 "HEAD/HTTP" コマンドが出されます。
  2. 応答を受け取ります。情報は解析されませんが、応答により getLoad メソッドが終了します。
  3. getLoad メソッドは成功を示す 0 または失敗を示す -1 を戻します。

このアドバイザーは通常モードで操作するので、ロード測定はソケット・オープン、送信、受信、およびクローズ操作を実行するために必要な経過時間 (ミリ秒) に基づきます。

package CustomAdvisors;
import com.ibm.internet.lb.advisors.*;
public class ADV_sample extends ADV_Base implements ADV_MethodInterface {
  static final String ADV_NAME ="Sample";
  static final int ADV_DEF_ADV_ON_PORT = 80;
  static final int ADV_DEF_INTERVAL = 7;
  static final String ADV_SEND_REQUEST = 
    "HEAD / HTTP/1.0¥r¥nAccept: */*¥r¥nUser-Agent: " +
    "IBM_Load_Balancer_HTTP_Advisor¥r¥n¥r¥n";

//--------
// Constructor

  public ADV_sample() {
    super(ADV_NAME, "3.0.0.0-03.31.00", 
          ADV_DEF_ADV_ON_PORT, ADV_DEF_INTERVAL, "",
          false);
    super.setAdvisor( this );
  }

//--------
// ADV_AdvisorInitialize

  public void ADV_AdvisorInitialize() {
    return;                                  // usually an empty routine
  }

//--------
// getLoad

  public int getLoad(int iConnectTime, ADV_Thread caller) {
    int iRc;
    int iLoad = ADV_HOST_INACCESSIBLE;       // initialize to inaccessible

    iRc = caller.send(ADV_SEND_REQUEST);     // send the HTTP request to 
                                             // the server
    if (0 <= iRc) {                          // if the send is successful
      StringBuffer sbReceiveData = new StringBuffer("");   // allocate a buffer 
                                                           // for the response
      iRc = caller.receive(sbReceiveData);   // receive the result

      // parse the result here if you need to

      if (0 <= iRc) {          // if the receive is successful
        iLoad = 0;             // return 0 for success 
      }                        // (advisor's load value is ignored by
    }                          //  base in normal mode)
    return iLoad;
  }
}

サイド・ストリーム・アドバイザー

このサンプルでは、アドバイザー・ベースによる標準ソケットのオープンの抑制を例示しています。その代わりに、このアドバイザーは、サーバーを照会するためにサイド・ストリーム Java™ ソケットをオープンします。このプロシージャーは、通常のクライアント・トラフィックと異なるポートを使用してアドバイザー照会を listen するサーバーのために役立ちます。

この例では、サーバーはポート 11999 上で listen していて、 照会されたときに 16 進 int "4" でロード値を戻します。 このサンプルは置換モードで実行されます。 すなわち、アドバイザー・コンストラクターの最終パラメーターが true に設定されて、アドバイザー基本コードは経過時間ではなく戻されたロード値を使用します。

初期化ルーチンでの supressBaseOpeningSocket() に対する呼び出しに注意してください。データが送信されないときの基本ソケットの抑制は不要です。例えば、アドバイザーがサーバーに接続できることを確認するためにソケットをオープンする場合などです。 この選択を行う前には、アプリケーションの必要性を注意深く調べてください。

package CustomAdvisors;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.Date;
import com.ibm.internet.lb.advisors.*;
import com.ibm.internet.lb.common.*;
import com.ibm.internet.lb.server.SRV_ConfigServer;

public class ADV_sidea extends ADV_Base implements ADV_MethodInterface {
  static final String ADV_NAME = "sidea";
  static final int ADV_DEF_ADV_ON_PORT = 12345;
  static final int ADV_DEF_INTERVAL = 7;

  // create an array of bytes with the load request message
  static final byte[] abHealth = {(byte)0x00, (byte)0x00, (byte)0x00, 
                                  (byte)0x04};

  public ADV_sidea() {
    super(ADV_NAME, "3.0.0.0-03.31.00", ADV_DEF_ADV_ON_PORT,
          ADV_DEF_INTERVAL, "", 
          true);         // replace mode parameter is true
    super.setAdvisor( this );
  }

//--------
// ADV_AdvisorInitialize

  public void ADV_AdvisorInitialize()
  { 
    suppressBaseOpeningSocket();   // tell base code not to open the 
                                   // standard socket 
        return;
  }

//--------
// getLoad

  public int getLoad(int iConnectTime, ADV_Thread caller) {
    int iRc; 
    int iLoad = ADV_HOST_INACCESSIBLE;    // -1
    int iControlPort = 11999;   // port on which to communicate with the server

    String sServer = caller.getCurrentServerId();   // address of server to query 
    try { 
      socket soServer = new Socket(sServer, iControlPort);  // open socket to 
                                                            // server
      DataInputStream disServer = new DataInputStream(
                                      soServer.getInputStream());
      DataOutputStream dosServer = new DataOutputStream(
                                       soServer.getOutputStream());
      
      int iRecvTimeout = 10000;  // set timeout (in milliseconds)
                                 // for receiving data  
      soServer.setSoTimeout(iRecvTimeout);

      dosServer.writeInt(4);     // send a message to the server
      dosServer.flush();

      iLoad = disServer.readByte();   // receive the response from the server

    } catch (exception e) {
      system.out.println("Caught exception " + e);
    }
    return iLoad;    // return the load reported from the server 
  }
}

2 つのポート・アドバイザー

このカスタム・アドバイザー・サンプルは、サーバーの 1 つのポートに対する失敗を検出する機能を説明しています。 これは、そのポートの状況と、同一サーバー・マシン上にある別のポート上で実行されている 異なるサーバー・デーモンの状況の両方に基づいています。 例えば、ポート 80 の HTTP デーモンが応答を停止する場合には、ポート 443 の SSL デーモンへのルーティング・トラフィックも停止することができます。

このアドバイザーは、応答を送信しないサーバーは機能を停止したと見なして、ダウンのマークを付けるので、 標準アドバイザーよりも積極的です。標準アドバイザーは応答のないサーバーを非常に低速であると見なします。このアドバイザーは HTTP ポートおよび SSL ポートのいずれかの応答がないと、両方のポートがダウンしたことを示す マークをサーバーに付けます。

このカスタム・アドバイザーを使用するには、アドバイザーの HTTP ポート上にある インスタンスと SSL ポート上にあるインスタンスを管理者が開始します。 アドバイザーは HTTP 用と SSL 用の 2 つの静的グローバル・ハッシュ・テーブルを検証します。各アドバイザーは そのサーバー・デーモンとの通信を試行し、そのハッシュ・テーブルにこのイベントの結果を保管します。 各アドバイザーが基本アドバイザークラスに戻す値は、 その固有のサーバー・デーモンと通信する能力およびそのデーモンと通信するパートナー・アドバイザーの能力によって異なります。

以下のカスタム・メソッドが使用されます。

次のエラー条件が検出されます。

このサンプルは HTTP 用のポート 80 および SSL 用の 443 をリンクするように書かれていますが、ポートの組み合わせは任意に調整できます。

package CustomAdvisors;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.Date;
import com.ibm.internet.lb.advisors.*;
import com.ibm.internet.lb.common.*;
import com.ibm.internet.lb.manager.*;
import com.ibm.internet.lb.server.SRV_ConfigServer;

//--------
// Define the table element for the hash tables used in this custom advisor

class ADV_nte implements Cloneable {
  private String  sCluster;
  private int     iPort;
  private String  sServer;
  private int     iLoad;
  private Date    dTimestamp;

//--------
// constructor

  public ADV_nte(String sClusterIn, int iPortIn, String sServerIn, 
                 int iLoadIn) {
    sCluster = sClusterIn;
    iPort = iPortIn;
    sServer = sServerIn;
    iLoad = iLoadIn;
    dTimestamp = new Date();
  }

//--------
// check whether this element is current or expired
  public boolean isCurrent(ADV_twop oThis) {
    boolean bCurrent;
    int iLifetimeMs = 3 * 1000 * oThis.getInterval();   // set lifetime as 
                                                        // 3 advisor cycles
    Date dNow = new Date();
    Date dExpires = new Date(dTimestamp.getTime() + iLifetimeMs);

    if (dNow.after(dExpires)) {
      bCurrent = false;
    } else {
      bCurrent = true;
    }
    return bCurrent;
  }

//--------
// value accessor(s)

  public int getLoadValue() { return iLoad; }
  
//--------
// clone (avoids corruption between threads)

  public synchronized Object Clone() {
    try { 
      return super.clone();
    } catch (cloneNotSupportedException e) {
      return null;
    }
  }

}

//--------
// define the custom advisor

public class ADV_twop extends ADV_Base 
   implements ADV_MethodInterface, ADV_AdvisorVersionInterface {
 
  static final int ADV_TWOP_PORT_HTTP = 80;
  static final int ADV_TWOP_PORT_SSL = 443;

  //--------
  // define tables to hold port-specific history information

  static HashTable htTwopHTTP = new Hashtable();
  static HashTable htTwopSSL = new Hashtable();

  static final String ADV_TWOP_NAME = "twop";
  static final int ADV_TWOP_DEF_ADV_ON_PORT = 80;
  static final int ADV_TWOP_DEF_INTERVAL = 7;
  static final String ADV_HTTP_REQUEST_STRING = 
    "HEAD / HTTP/1.0¥r¥nAccept: */*¥r¥nUser-Agent: " +
    "IBM_LB_Custom_Advisor¥r¥n¥r¥n";

  //--------
  // create byte array with SSL client hello message
  
  public static final byte[] abClientHello = {
    (byte)0x80, (byte)0x1c,
    (byte)0x01,               // client hello
    (byte)0x03, (byte)0x00,   // SSL version
    (byte)0x00, (byte)0x03,   // cipher spec len (bytes)
    (byte)0x00, (byte)0x00,   // session ID len (bytes)
    (byte)0x00, (byte)0x10,   // challenge data len (bytes)
    (byte)0x00, (byte)0x00, (byte)0x03,   // cipher spec
    (byte)0x1A, (byte)0xFC, (byte)0xE5, (byte)Ox20,  // challenge data
    (byte)0xFD, (byte)0x3A, (byte)0x3C, (byte)0x18,  
    (byte)0xAB, (byte)0x67, (byte)0xB0, (byte)0x52, 
    (byte)0xB1, (byte)0x1D, (byte)0x55, (byte)0x44, (byte)0x0D, (byte)0x0A };

  //--------
  // constructor

  public ADV_twop() {
    super(ADV_TWOP_NAME, VERSION, ADV_TWOP_DEF_ADV_ON_PORT, 
          ADV_TWOP_DEF_INTERVAL, "", 
          false);    // false = load balancer times the response
    setAdvisor ( this );
  }

  //--------
  // ADV_AdvisorInitialize

  public void ADV_AdvisorInitialize() {
        return;
  }

  //--------
  // synchronized PUT and GET access routines for the hash tables

  synchronized ADV_nte getNte(Hashtable ht, String sName, String sHashKey) {
    ADV_nte nte = (ADV_nte)(ht.get(sHashKey));
    if (null != nte) {
      nte = (ADV_nte)nte.clone();
    }
    return nte;
  }
 synchronized void putNte(Hashtable ht, String sName, String sHashKey, 
                          ADV_nte nte) {
   ht.put(sHashKey,nte);
   return;
 }

  //--------
  // getLoadHTTP - determine HTTP load based on server response

  int getLoadHTTP(int iConnectTime, ADV_Thread caller) {
    int iLoad = ADV_HOST_INACCESSIBLE;

    int iRc = caller.send(ADV_HTTP_REQUEST_STRING);  // send request message 
                                                     // to server   
    if (0 <= iRc) {           // did the request return a failure? 
      StringBuffer sbReceiveData = new StringBuffer("")    // allocate a buffer 
                                                           // for the response
      iRc = caller.receive(sbReceiveData);    // get response from server

      if (0 <= iRc) {             // did the receive return a failure? 
        if (0 < sbReceiveData.length()) {      // is data there? 
          iLoad = SUCCESS;        // ignore retrieved data and  
                                  // return success code
        }
      }
    }
    return iLoad;
  }

  //--------
  // getLoadSSL() - determine SSL load based on server response

  int getLoadSSL(int iConnectTime, ASV_Thread caller) {
    int iLoad = ADV_HOST_INACCESSIBLE;
    int iRc;

    CMNByteArrayWrapper cbawClientHello = new CMNByteArrayWrapper(
                                                  abClientHello);
    Socket socket = caller.getSocket();

    try {
        socket.getOutputStream().write(abClientHello);
        // Perform a receive.
        socket.getInputStream().read();
        // If receive is successful, return load of 0.  We are not concerned with
        // data's contents, and the load is calculated by the ADV_Thread thread.
        iLoad = 0;
    } catch (IOException e) {
        // Upon error, iLoad will default to it.
    }
    return iLoad;
  }

  //--------
  // getLoad - merge results from the HTTP and SSL methods

  public int getLoad(int iConnectTime, ADV_Thread caller) {
    int iLoadHTTP;
    int iLoadSSL;
    int iLoad;
    int iRc;

    String sCluster = caller.getCurrentClusterId();   // current cluster address
    int iPort = getAdviseOnPort();
    String sServer = caller.getCurrentServerId();
    String sHashKey = sCluster = ":" + sServer;     // hash table key

    if (ADV_TWOP_PORT_HTTP == iPort) {              // handle an HTTP server
      iLoadHTTP = getLoadHTTP(iConnectTime, caller);  // get the load for HTTP

      ADV_nte nteHTTP = newADV_nte(sCluster, iPort, sServer, iLoadHTTP);
      putNte(htTwopHTTP, "HTTP", sHashKey, nteHTTP);  // save HTTP load 
                                                      // information
      ADV_nte nteSSL = getNte(htTwopSSL, "SSL", sHashKey);  // get SSL 
                                                               // information
      if (null != nteSSL) { 
        if (true == nteSSL.isCurrent(this)) {         // check the time stamp
          if (ADV_HOST_INACCESSIBLE != nteSSL.getLoadValue()) {    // is SSL 
                                                                   // working?
            iLoad = iLoadHTTP;
          } else {    // SSL is not working, so mark the HTTP server down
            iLoad= ADV_HOST_INACCESSIBLE;
          }
        } else {      // SSL information is expired, so mark the 
                      // HTTP server down
      iLoad = ADV_HOST_INACCESSIBLE;
        }
      } else {        // no load information about SSL, report 
                      // getLoadHTTP() results
        iLoad = iLoadHTTP;
      }
    }
    else if (ADV_TWOP_PORT_SSL == iPort) {           // handle an SSL server
      iLoadSSL = getLoadSSL(iConnectTime, caller);   // get load for SSL

      ADV_nte nteSSL = new ADV_nte(sCluster, iPort, sServer, iLoadSSL);
      putNte(htTwopSSL, "SSL", sHashKey, nteSSL);   // save SSL load info.

      ADV_nte nteHTTP = getNte(htTwopHTTP, "SSL", sHashKey);   // get HTTP 
                                                               // information
      if (null != nteHTTP) {
        if (true == nteHTTP.isCurrent(this)) {       // check the timestamp
          if (ADV_HOST_INACCESSIBLE != nteHTTP.getLoadValue()) {  // is HTTP 
                                                                  // working?
            iLoad = iLoadSSL; 
          } else {   // HTTP server is not working, so mark SSL down
      iLoad = ADV_HOST_INACCESSIBLE;
          }
        } else {     // expired information from HTTP, so mark SSL down
      iLoad = ADV_HOST_INACCESSIBLE;
        }
      } else {       // no load information about HTTP, report 
                     // getLoadSSL() results
        iLoad = iLoadSSL;
      }
    }

  //--------
  // error handler

    else { 
      iLoad = ADV_HOST_INACCESSIBLE;
    }
    return iLoad;
  }
}

WebSphere Application Server アドバイザー

WebSphere® Application Server 用のカスタム・アドバイザーのサンプル は install_path/servers/samples/CustomAdvisors/ ディレクトリーに入っています。完全なコードはこの資料では掲載していません。

完全なアドバイザーはサンプルよりわずかに複雑です。上記の StringTokenizer の例よりコンパクトな特殊化された構文解析ルーチンを追加します。

サンプル・コードのより複雑な部分は Java サーブレットにあります。 このサーブレットには、他のメソッドと共に、サーブレット仕様が必要とする init() および service() という 2 つのメソッドと、 Java.lang.thread クラスが必要とする run() というメソッドが含まれています。

サーブレット・コードの関連フラグメントは、以下のようになります。

...

  public void init(ServletConfig config) throws ServletException {
    super.init(config);
    ...
    _checker = new Thread(this);
    _checker.start();
  }
  
  public void run() {
    setStatus(GOOD);

    while (true) {
      if (!getKeepRunning()) 
        return;
      setStatus(figureLoad());
      setLastUpdate(new java.util.Date());

      try {
        _checker.sleep(_interval * 1000);
      } catch (Exception ignore) { ; }
    }
  }

  public void service(HttpServletRequest req, HttpServletResponse res)
                      throws ServletException, IOException {

    ServletOutputStream out = null;
    try {
      out = res.getOutputStream();
    } catch (Exception e) { ... }
    ...
    res.setContentType("text/x-application-LBAdvisor");
    out.println(getStatusString());
    out.println(getLastUpdate().toString());
    out.flush();
        return;
  }

  ...

アドバイザーから戻されるデータの使用

アプリケーション・サーバーの既存パーツに対する標準呼び出しを使用したり、 またはカスタム・アドバイザーのサーバー側で相対するコードの新規部分を追加して、 戻されたロード値を調べてサーバー動作を変更することができます。 Java StringTokenizer クラスおよびその関連メソッド は、この調査を簡単にします。

通常の HTTP コマンドのコンテンツは GET /index.html HTTP/1.0 です。

このコマンドに対する通常の応答は、以下のようになります。

HTTP/1.1 200 OK
Date: Mon, 20 November 2000 14:09:57 GMT
Server: Apache/1.3.12 (Linux and UNIX)
Content-Location: index.html.en
Vary: negotiate
TCN: choice
Last-Modified: Fri, 20 Oct 2000 15:58:35 GMT
ETag: "14f3e5-1a8-39f06bab;39f06a02"
Accept-Ranges: bytes
Content-Length: 424
Connection: close
Content-Type: text/html
Content-Language: en

<!DOCTYPE HTML PUBLIC "-//w3c//DTD HTML 3.2 Final//EN">
<HTML><HEAD><TITLE>Test Page</TITLE></HEAD>
<BODY><H1>Apache server</H1>
<HR>
<P><P>This Web server is running Apache 1.3.12.
<P><HR>
<P><IMG SRC="apache_pb.gif" ALT="">
</BODY></HTML>

関心のある項目 (特に HTTP 戻りコード) は先頭行に入っています。

HTTP 仕様は戻りコードを分類し、以下のように要約できます。

サーバーが戻す可能性のあるコードが正確に分かる場合は、コードをこの例ほど詳細にする必要はありません。ただし、検出する戻りコードを制限すると、プログラムの将来の柔軟性を制限することになる場合があります。

以下の例は、最小限の HTTP クライアントが含まれている、スタンドアロン Java プログラムです。この例は HTTP 応答を調べるための単純な汎用パーサーを起動します。

import java.io.*;
import java.util.*;
import java.net.*;

public class ParseTest {
  static final int iPort = 80;
  static final String sServer = "www.ibm.com";
  static final String sQuery = "GET /index.html HTTP/1.0¥r¥n¥r¥n";
  static final String sHTTP10 = "HTTP/1.0";
  static final String sHTTP11 = "HTTP/1.1";

  public static void main(String[] Arg) {
    String sHTTPVersion = null;
    String sHTTPReturnCode = null;
    String sResponse = null;
    int iRc = 0;
    BufferedReader brIn = null;
    PrintWriter psOut = null;
    Socket soServer= null;
    StringBuffer sbText = new StringBuffer(40);

    try {
      soServer = new Socket(sServer, iPort);
      brIn = new BufferedReader(new InputStreamReader(
                                    soServer.getInputStream()));
      psOut = new PrintWriter(soServer.getOutputStream());
      psOut.println(sQuery);
      psOut.flush();
      sResponse = brIn.readLine();
      try {
        soServer.close();
      } catch (Exception sc) {;}
    }  catch (Exception swr) {;}
    
    StringTokenizer st = new StringTokenizer(sResponse, " ");
    if (true == st.hasMoreTokens()) {
      sHTTPVersion = st.nextToken();
      if (sHTTPVersion.equals(sHTTP110) || sHTTPVersion.equals(sHTTP11)) {
        System.out.println("HTTP Version: " + sHTTPVersion);
    } else {
        System.out.println("Invalid HTTP Version: " + sHTTPVersion);
      }
    } else {
      System.out.println("Nothing was returned");
      return;
    }

    if (true == st.hasMoreTokens()) {
      sHTTPReturnCode = st.nextToken();
      try {
        iRc = Integer.parseInt(sHTTPReturnCode);
      } catch (NumberFormatException ne) {;}

      switch (iRc) {
      case(200): 
        System.out.println("HTTP Response code: OK, " + iRc);
        break;
      case(400): case(401): case(402): case(403): case(404): 
        System.out.println("HTTP Response code: Client Error, " + iRc);
        break;
      case(500): case(501): case(502): case(503):
        System.out.println("HTTP Response code: Server Error, " + iRc);
        break;
      default: 
        System.out.println("HTTP Response code: Unknown, " + iRc);
        break;
      }
    }

    if (true == st.hasMoreTokens()) {
      while (true == st.hasMoreTokens()) {
        sbText.append(st.nextToken());
        sbText.append("  ");
        }
      System.out.println("HTTP Response phrase: " + sbText.toString());
    }
  }
}