Os exemplos a seguir mostram como os orientadores customizados podem ser implementados.
Este código de origem de amostra é semelhante ao orientador padrão de HTTP do Load Balancer. Ele funciona da seguinte forma:
O orientador opera no modo normal de modo que a medida de carregamento seja baseada no tempo decorrido em milissegundos necessário para executar as operações de abertura, de envio, de recebimento e de fechamento de soquete.
pacote 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"; //-------- // Construtor 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; // geralmente um rotina vazia } //-------- // getLoad public int getLoad(int iConnectTime, ADV_Thread caller) { int iRc; int iLoad = ADV_HOST_INACCESSIBLE; // inicializar como inacessível iRc = caller.send(ADV_SEND_REQUEST); // enviar o pedido de HTTP para // o servidor if (0 <= iRc) { // se o envio for bem-sucedido StringBuffer sbReceiveData = new StringBuffer(""); // alocar um buffer // para a resposta iRc = caller.receive(sbReceiveData); // receber o resultado // analisar o resultado aqui, se precisar if (0 <= iRc) { // se o recebimento for bem-sucedido iLoad = 0; // retornar 0 para obter êxito } // (valor de carregamento do orientador é ignorado pela } // base no modo normal) return iLoad; } }
Essa amostra ilustra a supressão do soquete padrão aberto pelo orientador base. Em vez disso, esse orientador abre um soquete Java de fluxo lateral para consultar um servidor. Esse procedimento pode ser útil para servidores que usam uma porta diferente do tráfego normal do cliente para atender a uma consulta do orientador.
Nesse exemplo, um servidor está atendendo na porta 11999 e quando consultado, retorna um valor de carregamento com um número inteiro hexadecimal "4". Essa amostra é executada no modo de substituição, isto é, o último parâmetro do construtor do orientador é configurado como true e o código base do orientador usa o valor de carregamento retornado, em vez do tempo decorrido.
Note a chamada de supressBaseOpeningSocket() na rotina de inicialização. Suprimir o soquete base quando nenhum dado for enviado não é necessário. Por exemplo, você pode querer abrir o soquete para assegurar que o orientador pode entrar em contato com o servidor. Examine as necessidades de seu aplicativo cuidadosamente, antes de fazer esta opção.
pacote 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; // criar uma matriz de bytes com a mensagem de pedido de carregamento 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); // o parâmetro de modo de substituição é verdadeiro super.setAdvisor( this ); } //-------- // ADV_AdvisorInitialize public void ADV_AdvisorInitialize() { suppressBaseOpeningSocket(); // dizer ao código base para não abrir o // soquete padrão return; } //-------- // getLoad public int getLoad(int iConnectTime, ADV_Thread caller) { int iRc; int iLoad = ADV_HOST_INACCESSIBLE; // -1 int iControlPort = 11999; // porta na qual se comunicar com o servidor String sServer = caller.getCurrentServerId(); // endereço do servidor a ser consultado try { socket soServer = new Socket(sServer, iControlPort); // o soquete aberto para o // servidor DataInputStream disServer = new DataInputStream( soServer.getInputStream()); DataOutputStream dosServer = new DataOutputStream( soServer.getOutputStream()); int iRecvTimeout = 10000; // configurar tempo limite (em milissegundos) // para dados de recebimento soServer.setSoTimeout(iRecvTimeout); dosServer.writeInt(4); // enviar uma mensagem ao servidor dosServer.flush(); iLoad = disServer.readByte(); // receber a resposta do servidor } catch (exception e) { system.out.println("Caught exception " + e); } return iLoad; // retornar o carregamento relatado do servidor } }
Esta amostra de orientador customizado demonstra a capacidade de detectar falha para uma porta de um servidor baseado em seu próprio status e no status de um daemon do servidor diferente, que está em execução em outra porta na mesma máquina servidor. Por exemplo, se o daemon HTTP na porta 80 parar de responder, você também deverá querer parar o tráfego de roteamento para o daemon SSL na porta 443.
Esse orientador é mais agressivo do que os orientadores padrão, porque ele considera que qualquer servidor que não envia uma resposta, tenha parado de funcionar e marca-o como inativo. Os orientadores padrão consideram os servidores não responsivos como sendo muito lentos. Esse orientador marca um servidor como inativo para a porta HTTP e para a porta SSL com base em uma falta de resposta de uma das portas.
Para usar esse orientador customizado, o administrador inicia duas instâncias do orientador: uma na porta HTTP e outra na porta SSL. O orientador instancia duas hashtable globais estáticas, uma para HTTP e outra para o SSL. Cada orientador tenta se comunicar com o daemon do servidor e armazena os resultados desse evento na hashtable. O valor que cada orientador retorna para a classe do orientador base depende da possibilidade de se comunicar com seu próprio daemon do servidor e da possibilidade de o orientador parceiro se comunicar com seu daemon.
Os seguintes métodos customizados são usados.
As seguintes condições de erro são detectadas.
Esta amostra é gravada para vincular as portas 80 para HTTP e 443 para SSL, mas pode ser padronizada para qualquer combinação de portas.
pacote 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; //-------- // Definir o elemento de tabela para as hashtables usadas neste orientador customizado class ADV_nte implements Cloneable { private String sCluster; private int iPort; private String sServer; private int iLoad; private Date dTimestamp; //-------- // construtor public ADV_nte(String sClusterIn, int iPortIn, String sServerIn, int iLoadIn) { sCluster = sClusterIn; iPort = iPortIn; sServer = sServerIn; iLoad = iLoadIn; dTimestamp = new Date(); } //-------- // verificar se este elemento é atual ou expirado public boolean isCurrent(ADV_twop oThis) { boolean bCurrent; int iLifetimeMs = 3 * 1000 * oThis.getInterval(); // configurar o tempo de vida como // 3 ciclos do orientador Date dNow = new Date(); Date dExpires = new Date(dTimestamp.getTime() + iLifetimeMs); if (dNow.after(dExpires)) { bCurrent = false; } else { bCurrent = true; } return bCurrent; } //-------- // acessador(es) de valor public int getLoadValue() { return iLoad; } //-------- // clone (evita distorção entre encadeamentos) public synchronized Object Clone() { try { return super.clone(); } catch (cloneNotSupportedException e) { return null; } } } //-------- // definir o orientador customizado 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; //-------- // definir tabelas para manter informações de histórico específico à porta 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"; //-------- // criar matriz de byte com mensagem de apresentação do cliente SSL public static final byte[] abClientHello = { (byte)0x80, (byte)0x1c, (byte)0x01, // apresentação do cliente (byte)0x03, (byte)0x00, // versão de SSL (byte)0x00, (byte)0x03, // compr. espec. ao código (bytes) (byte)0x00, (byte)0x00, // compr. do ID de sessão (bytes) (byte)0x00, (byte)0x10, // compr. de dados de desafio (bytes) (byte)0x00, (byte)0x00, (byte)0x03, // espec. ao código (byte)0x1A, (byte)0xFC, (byte)0xE5, (byte)Ox20, // dados de desafio (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 }; //-------- // construtor public ADV_twop() { super(ADV_TWOP_NAME, VERSION, ADV_TWOP_DEF_ADV_ON_PORT, ADV_TWOP_DEF_INTERVAL, "", false); // false = o balanceador de carga determina o tempo da resposta setAdvisor ( this ); } //-------- // ADV_AdvisorInitialize public void ADV_AdvisorInitialize() { return; } //-------- // rotinas de acesso PUT e GET sincronizadas para as hashtables 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 - determinar carregamento de HTTP baseado na resposta do servidor int getLoadHTTP(int iConnectTime, ADV_Thread caller) { int iLoad = ADV_HOST_INACCESSIBLE; int iRc = caller.send(ADV_HTTP_REQUEST_STRING); // enviar mensagem de pedido // ao servidor if (0 <= iRc) { // o pedido retornou uma falha? StringBuffer sbReceiveData = new StringBuffer("") // alocar um buffer // para a resposta iRc = caller.receive(sbReceiveData); // obter resposta do servidor if (0 <= iRc) { // o recebimento retornou uma falha? if (0 < sbReceiveData.length()) { // os dados estão lá? iLoad = SUCCESS; // ignorar dados recuperados e // retornar código com êxito } } } return iLoad; } //-------- // getLoadSSL() - determinar carregamento de SSL baseado na resposta do servidor 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); // Executar um recebimento. socket.getInputStream().read(); // Se o recebimento for bem-sucedido, retorne o carregamento de 0. Não estamos preocupados com o // conteúdo dos dados, e o carregamento é calculado pelo encadeamento ADV_Thread. iLoad = 0; } catch (IOException e) { // Sob erro, iLoad será padronizado para ele. } return iLoad; } //-------- // getLoad - mesclar resultados dos métodos de HTTP e SSL public int getLoad(int iConnectTime, ADV_Thread caller) { int iLoadHTTP; int iLoadSSL; int iLoad; int iRc; String sCluster = caller.getCurrentClusterId(); // endereço do cluster atual int iPort = getAdviseOnPort(); String sServer = caller.getCurrentServerId(); String sHashKey = sCluster = ":" + sServer; // chave da hashtable if (ADV_TWOP_PORT_HTTP == iPort) { // manipular um servidor HTTP iLoadHTTP = getLoadHTTP(iConnectTime, caller); // obter o carregamento para HTTP ADV_nte nteHTTP = newADV_nte(sCluster, iPort, sServer, iLoadHTTP); putNte(htTwopHTTP, "HTTP", sHashKey, nteHTTP); // salvar informações de carregamento // de HTTP ADV_nte nteSSL = getNte(htTwopSSL, "SSL", sHashKey); // obter informações // de SSL if (null != nteSSL) { if (true == nteSSL.isCurrent(this)) { // verificar o registro de data e hora if (ADV_HOST_INACCESSIBLE != nteSSL.getLoadValue()) { // o SSL está // funcionando? iLoad = iLoadHTTP; } else { // O SSL não está funcionando, portanto, marque o servidor HTTP como inativo iLoad= ADV_HOST_INACCESSIBLE; } } else { // As informações de SSL estão expiradas, portanto marque o // servidor HTTP como inativo iLoad = ADV_HOST_INACCESSIBLE; } } else { // nenhuma informações de carregamento sobre SSL, resultados do // getLoadHTTP() relatório iLoad = iLoadHTTP; } } else if (ADV_TWOP_PORT_SSL == iPort) { // manipular um servidor SSL iLoadSSL = getLoadSSL(iConnectTime, caller); // obter carregamento para SSL ADV_nte nteSSL = new ADV_nte(sCluster, iPort, sServer, iLoadSSL); putNte(htTwopSSL, "SSL", sHashKey, nteSSL); // salvar informação de carregamento de SSL. ADV_nte nteHTTP = getNte(htTwopHTTP, "SSL", sHashKey); // obter informações // de HTTP if (null != nteHTTP) { if (true == nteHTTP.isCurrent(this)) { // verificar o registro de data e hora if (ADV_HOST_INACCESSIBLE != nteHTTP.getLoadValue()) { // o HTTP está // funcionando? iLoad = iLoadSSL; } else { // o servidor HTTP não está funcionando, portanto para SSL como inativo iLoad = ADV_HOST_INACCESSIBLE; } } else { // informações expiradas de HTTP, portanto marque SSL como inativo iLoad = ADV_HOST_INACCESSIBLE; } } else { // nenhuma informações de carregamento sobre HTTP, resultados do // getLoadSSL() relatório iLoad = iLoadSSL; } } //-------- // error handler else { iLoad = ADV_HOST_INACCESSIBLE; } return iLoad; } }
Um orientador customizado de amostra do WebSphere Application Server está incluído no diretório install_path/servers/samples/CustomAdvisors/. O código completo não está duplicado neste documento.
O orientador completo é um pouco mais complexo que o orientador de amostra. Ele inclui uma rotina de análise especializada que é mais compacta do que o exemplo de StringTokenizer mostrado anteriormente.
A parte mais complexa do código de amostra está no servlet Java. Entre outros métodos, o servlet contém dois métodos necessários para a especificação do servlet: init() e service(), e um método, run(), que é necessário para a classe Java.lang.thread.
Os fragmentos relevantes do código do servlet são exibidos a seguir.
... 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; } ...
Se você usar uma chamada padrão para uma parte existente do servidor de aplicativos ou incluir um novo pedaço de código para ser a contraparte do lado do servidor do orientador customizado, possivelmente, desejará examinar os valores de carregamento retornados e alterar o comportamento do servidor. A classe StringTokenizer Java e seus métodos associados facilitam essa investigação.
O conteúdo de um comando HTTP típico poderá ser GET /index.html HTTP/1.0
Uma resposta típica para esse comando poderá ser a seguinte.
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>
Os itens de interesse estão contidos na primeira linha, especialmente o código de retorno HTTP.
A especificação HTTP classifica os códigos de retorno que podem ser resumidos da seguinte forma:
Se você souber muito precisamente quais códigos o servidor pode possivelmente retornar, o código talvez não precise ser tão detalhado quanto esse exemplo. Entretanto, lembre-se de que limitar os códigos de retorno que você detecta pode limitar a flexibilidade futura do seu programa.
O exemplo a seguir é um programa Java independente que contém um cliente HTTP mínimo. O exemplo chama um analisador de propósito geral simples para examinar as respostas 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("Nada foi retornado"); 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()); } } }