Exemplos

Os exemplos a seguir mostram como os orientadores customizados podem ser implementados.

Orientador Padrão

Este código de origem de amostra é semelhante ao orientador padrão de HTTP do Load Balancer. Ele funciona da seguinte forma:

  1. Um pedido de envio, um comando "HEAD/HTTP", é emitido.
  2. A resposta é recebida. As informações não são analisadas, mas a resposta faz com que o método getLoad seja terminado.
  3. O método getLoad retorna 0 para indicar sucesso ou -1 para indicar uma falha.

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;
  }
}

Orientador de Fluxo Lateral

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 
  }
}

Orientador de Duas Portas

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;
  }
}

Orientador do WebSphere Application Server

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;
  }

  ...

Usando Dados Retornados dos Orientadores

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());
    }
  }
}