以下の例は、カスタム・アドバイザーを実装できる方法を示します。
このサンプル・ソース・コードは、標準 Load Balancer HTTP アドバイザーに似ています。 以下のように機能します。
このアドバイザーは通常モードで操作するので、ロード測定はソケット・オープン、送信、受信、およびクローズ操作を実行するために必要な経過時間 (ミリ秒) に基づきます。
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 } }
このカスタム・アドバイザー・サンプルは、サーバーの 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 用のカスタム・アドバイザーのサンプル は 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()); } } }