下列範例顯示如何實作自訂顧問。
此範例原始碼類似標準 Load Balancer HTTP 顧問。 其作用如下:
此顧問是在標準模式下操作,因此負載測量是根據執行 socket 開啟、傳送、接收及關閉作業所需的經歷時間(毫秒)。
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"; //-------- // 建構子 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; // 通常是空常式 } //-------- // getLoad public int getLoad(int iConnectTime, ADV_Thread caller) { int iRc; int iLoad = ADV_HOST_INACCESSIBLE; // 起始設定為無法存取 iRc = caller.send(ADV_SEND_REQUEST); // 將 HTTP 要求傳送至 // 伺服器 if (0 <= iRc) { // 如果傳送成功 StringBuffer sbReceiveData = new StringBuffer(""); // 配置緩衝區 // 給回應 iRc = caller.receive(sbReceiveData); // 接收結果 // 如有需要,請在這裡剖析結果 if (0 <= iRc) { // 如果接收成功 iLoad = 0; // 傳回 0 代表成功 } // (顧問的負載值 } // 在標準模式下會被基本程式所忽略) return iLoad; } }
這個範例說明暫停顧問基本程式開啟的標準 socket。 不同的是,這個顧問會開啟端串流 Java socket 來查詢伺服器。對於使用不同於一般用戶端資料流量的埠,來接聽顧問查詢的伺服器,此程序很有幫助。
在這個範例中,伺服器是在埠 11999 接聽,當有查詢時,會傳回十六進位整數 "4" 的負載值。這個範例是在取代模式下執行,也就是說,顧問建構子的最後參數是設為 true,且顧問基本程式碼是使用傳回的負載值而非經歷時間。
請注意起始設定常式中對 supressBaseOpeningSocket() 的呼叫。 若不傳送資料,則不需要暫停基本程式 socket。例如,您想要開啟 socket,以確定顧問可以聯絡到伺服器。 在做這個選擇之前,請仔細檢查應用程式的需求。
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; // 以負載要求訊息建立一個位元組陣列 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); // 取代模式參數為 true super.setAdvisor( this ); } //-------- // ADV_AdvisorInitialize public void ADV_AdvisorInitialize() { suppressBaseOpeningSocket(); // 告訴基本程式碼不要開啟 // 標準 socket return; } //-------- // getLoad public int getLoad(int iConnectTime, ADV_Thread caller) { int iRc; int iLoad = ADV_HOST_INACCESSIBLE; // -1 int iControlPort = 11999; // 與伺服器通訊的埠 String sServer = caller.getCurrentServerId(); // 要查詢的伺服器位址 try { socket soServer = new Socket(sServer, iControlPort); // 開啟 socket 到 // 伺服器 DataInputStream disServer = new DataInputStream( soServer.getInputStream()); DataOutputStream dosServer = new DataOutputStream( soServer.getOutputStream()); int iRecvTimeout = 10000; // 設定逾時(毫秒) // 用以接收資料 soServer.setSoTimeout(iRecvTimeout); dosServer.writeInt(4); // 將訊息傳送至伺服器 dosServer.flush(); iLoad = disServer.readByte(); // 從伺服器接收回應 } catch (exception e) { system.out.println("Caught exception " + e); } return iLoad; // 從伺服器傳回報告的負載 } }
這個自訂顧問範例示範根據伺服器其中一個埠本身的狀態,以及在相同伺服器機器上的另一個埠執行的不同伺服器常駐程式的狀態,來偵測埠的失敗。 比方說,如果埠 80 的 HTTP 常駐程式停止回應,您也會想要停止遞送資料流量到埠 443 的 SSL 常駐程式。
此顧問比標準顧問更積進,因為它會把沒有傳送回應的任何伺服器當成已停止發揮作用,而將它標示為關閉。標準顧問則把無回應的伺服器視為非常慢速的伺服器。 此顧問會根據 HTTP 埠和 SSL 埠其中之一無回應,而同時針對這兩個埠將伺服器標示為關閉。
如果要使用這個自訂顧問,管理者要啟動該顧問的兩個實例:一個在 HTTP 埠上,一個在 SSL 埠上。 顧問將兩個靜態廣域雜湊表實例化,一個給 HTTP,一個給 SSL。每一個顧問會嘗試與其伺服器常駐程式通訊,並將此事件的結果儲存在其雜湊表中。 每一個顧問傳回到基本顧問類別的值,是同時依據與其本身的伺服器常駐程式通訊的能力,及友機顧問與其常駐程式的通訊能力而定。
會使用下列自訂方法。
偵測到下列錯誤狀況。
撰寫這個範例是為了要鏈結 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; //-------- // 定義此自訂顧問使用的雜湊表之表格元素 class ADV_nte implements Cloneable { private String sCluster; private int iPort; private String sServer; private int iLoad; private Date dTimestamp; //-------- // 建構子 public ADV_nte(String sClusterIn, int iPortIn, String sServerIn, int iLoadIn) { sCluster = sClusterIn; iPort = iPortIn; sServer = sServerIn; iLoad = iLoadIn; dTimestamp = new Date(); } //-------- // 檢查此元素為最新的或已過期 public boolean isCurrent(ADV_twop oThis) { boolean bCurrent; int iLifetimeMs = 3 * 1000 * oThis.getInterval(); // 將生命期限設為 // 3 個顧問週期 Date dNow = new Date(); Date dExpires = new Date(dTimestamp.getTime() + iLifetimeMs); if (dNow.after(dExpires)) { bCurrent = false; } else { bCurrent = true; } return bCurrent; } //-------- // 值存取元 public int getLoadValue() { return iLoad; } //-------- // 複製(避免執行緒之間毀損) public synchronized Object Clone() { try { return super.clone(); } catch (cloneNotSupportedException e) { return null; } } } //-------- // 定義自訂顧問 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; //-------- // 定義表格來保存特定埠的歷程資訊 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"; //-------- // 以 SSL client hello 訊息建立位元組陣列 public static final byte[] abClientHello = { (byte)0x80, (byte)0x1c, (byte)0x01, // client hello (byte)0x03, (byte)0x00, // SSL 版本 (byte)0x00, (byte)0x03, // 密碼規格長度(位元組) (byte)0x00, (byte)0x00, // 階段作業 ID 長度(位元組) (byte)0x00, (byte)0x10, // 盤查資料長度(位元組) (byte)0x00, (byte)0x00, (byte)0x03, // 密碼規格 (byte)0x1A, (byte)0xFC, (byte)0xE5, (byte)Ox20, // 盤查資料 (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 }; //-------- // 建構子 public ADV_twop() { super(ADV_TWOP_NAME, VERSION, ADV_TWOP_DEF_ADV_ON_PORT, ADV_TWOP_DEF_INTERVAL, "", false); // false = 負載平衡器計算回應時間 setAdvisor ( this ); } //-------- // ADV_AdvisorInitialize public void ADV_AdvisorInitialize() { return; } //-------- // 雜湊表的已同步 PUT 和 GET 存取常式 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 - 根據伺服器回應來判定 HTTP 負載 int getLoadHTTP(int iConnectTime, ADV_Thread caller) { int iLoad = ADV_HOST_INACCESSIBLE; int iRc = caller.send(ADV_HTTP_REQUEST_STRING); // 將要求訊息傳送 // 至伺服器 if (0 <= iRc) { // 要求是否傳回失敗? StringBuffer sbReceiveData = new StringBuffer("") // 配置緩衝區 // 給回應 iRc = caller.receive(sbReceiveData); // 從伺服器取得回應 if (0 <= iRc) { // 接收是否傳回失敗? if (0 < sbReceiveData.length()) { // 資料在嗎? iLoad = SUCCESS; // 忽略擷取的資料並 // 傳回成功碼 } } } return iLoad; } //-------- // getLoadSSL() - 根據伺服器回應來判定 SSL 負載 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); // 執行接收。 socket.getInputStream().read(); // 如果接收成功,則傳回負載 0。關注重點不是 // 資料的內容,負載是由 ADV_Thread 執行緒計算。 iLoad = 0; } catch (IOException e) { // 發生錯誤時,iLoad 會以此為預設。 } return iLoad; } //-------- // getLoad - 合併 HTTP 和 SSL 方法的結果 public int getLoad(int iConnectTime, ADV_Thread caller) { int iLoadHTTP; int iLoadSSL; int iLoad; int iRc; String sCluster = caller.getCurrentClusterId(); // 現行叢集位址 int iPort = getAdviseOnPort(); String sServer = caller.getCurrentServerId(); String sHashKey = sCluster = ":" + sServer; // 雜湊表索引鍵 if (ADV_TWOP_PORT_HTTP == iPort) { // 處理 HTTP 伺服器 iLoadHTTP = getLoadHTTP(iConnectTime, caller); // 取得 HTTP 的負載 ADV_nte nteHTTP = newADV_nte(sCluster, iPort, sServer, iLoadHTTP); putNte(htTwopHTTP, "HTTP", sHashKey, nteHTTP); // 儲存 HTTP 負載 // 資訊 ADV_nte nteSSL = getNte(htTwopSSL, "SSL", sHashKey); // 取得 SSL // 資訊 if (null != nteSSL) { if (true == nteSSL.isCurrent(this)) { // 檢查時間戳記 if (ADV_HOST_INACCESSIBLE != nteSSL.getLoadValue()) { // SSL // 是否運作? iLoad = iLoadHTTP; } else { // SSL 未運作,因此將 HTTP 伺服器標示為關閉 iLoad= ADV_HOST_INACCESSIBLE; } } else { // SSL 資訊過期,因此將 // HTTP 伺服器標示為關閉 iLoad = ADV_HOST_INACCESSIBLE; } } else { // 沒有關於 SSL 的負載資訊,報告 // getLoadHTTP() 結果 iLoad = iLoadHTTP; } } else if (ADV_TWOP_PORT_SSL == iPort) { // 處理 SSL 伺服器 iLoadSSL = getLoadSSL(iConnectTime, caller); // 取得 SSL 的負載 ADV_nte nteSSL = new ADV_nte(sCluster, iPort, sServer, iLoadSSL); putNte(htTwopSSL, "SSL", sHashKey, nteSSL); // 儲存 SSL 負載資訊。 ADV_nte nteHTTP = getNte(htTwopHTTP, "SSL", sHashKey); // 取得 HTTP // 資訊 if (null != nteHTTP) { if (true == nteHTTP.isCurrent(this)) { // 檢查時間戳記 if (ADV_HOST_INACCESSIBLE != nteHTTP.getLoadValue()) { // HTTP // 是否運作? iLoad = iLoadSSL; } else { // HTTP 伺服器未運作,因此將 SSL 標示為關閉 iLoad = ADV_HOST_INACCESSIBLE; } } else { // HTTP 的資訊過期,因此將 SSL 標示為關閉 iLoad = ADV_HOST_INACCESSIBLE; } } else { // 沒有關於 HTTP 的負載資訊,報告 // getLoadSSL() 結果 iLoad = iLoadSSL; } } //-------- // 錯誤處理常式 else { iLoad = ADV_HOST_INACCESSIBLE; } return iLoad; } }
WebSphere® Application Server 的範例自訂顧問已併入 install_path/servers/samples/CustomAdvisors/ 目錄中。本文件未複製其完整程式碼。
完整的顧問只比該範例稍微複雜一點。它新增一個特殊化剖析常式,它比上述 StringTokenizer 範例更精簡。
範例程式碼較複雜部分是在 Java Servlet 中。 除了其他方法之外,Servlet 還包含 Servlet 規格所需要的兩個方法:init() 和 service(),另外還有 run() 方法,這是 Java.lang.thread 類別所需要的。
Servlet 程式碼的相關片段顯示如下。
... 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; } ...
不論您是對 Application Server 的現有部分使用標準呼叫,或是新增一個新的程式碼片段作為自訂顧問的伺服器端的對應項,您都可以檢查傳回的負載值及變更伺服器行為。 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 規格將回覆碼分類,可彙總如下:
如果您非常確定伺服器可能傳回什麼代碼,則您的代碼不必像這個範例那麼詳細。 不過請記住,限制您偵測的回覆碼可能會限制程式的未來彈性。
下列範例是獨立式 Java 程式,它包含最小 HTTP 用戶端。 此範例呼叫一個簡單的通用剖析器來檢查 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()); } } }