Ejemplos

En los ejemplos siguientes se muestra cómo se pueden implementar asesores personalizados.

Asesor estándar

Este código fuente de ejemplo es similar al asesor HTTP estándar de Load Balancer. Funciona de la manera siguiente:

  1. Se emite una solicitud de envío, un mandato "HEAD/HTTP".
  2. Se recibe una respuesta. La información no se analiza, pero la respuesta hace que el método getLoad termine.
  3. El método getLoad devuelve 0 para indicar que la operación se ha realizado satisfactoriamente o -1 para indicar una anomalía.

El asesor funciona en modalidad normal, por lo que la medición de carga se basa en el tiempo transcurrido, en milisegundos, necesario para realizar las operaciones de apertura, envío, recepción y cierre de 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";

//--------
// 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;                                  // normalmente una rutina vacía
  }

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

  public int getLoad(int iConnectTime, ADV_Thread caller) {
    int iRc;
    int iLoad = ADV_HOST_INACCESSIBLE;       // inicializar en inaccesible

    iRc = caller.send(ADV_SEND_REQUEST);     // enviar la solicitud HTTP
                                             // al servidor
    if (0 <= iRc) {                          // si el envío es satisfactorio
      StringBuffer sbReceiveData = new StringBuffer("");   // asignar un almacenamiento intermedio
                                                           // para la respuesta
      iRc = caller.receive(sbReceiveData);   // recibir el resultado

      // analizar el resultado aquí si se necesita hacerlo

      if (0 <= iRc) {          // si la recepción es satisfactoria
        iLoad = 0;             // devolver 0 para correcto
      }                        // (valor de carga del asesor ignorado por
    }                          //  base en modalidad normal)
    return iLoad;
  }
}

Asesor de corriente lateral

Este ejemplo ilustra la supresión del socket estándar abierto por la base de asesor. En su lugar, este asesor abre un socket Java™ de corriente lateral para consultar a un servidor. Este procedimiento puede ser útil para servidores que utilizan un puerto diferente del de tráfico de cliente normal para escuchar una consulta de asesor.

En este ejemplo, un servidor está a la escucha en el puerto 11999 y, cuando se le consulta, devuelve un valor de carga con un entero hexadecimal "4". Este ejemplo se ejecuta en modalidad de sustitución, es decir, el último parámetro del constructor de asesor se establece en true y el código base de asesor utiliza el valor de carga devuelto en lugar del tiempo transcurrido.

Observe la llamada a supressBaseOpeningSocket() en la rutina de inicialización. La supresión del socket base cuando no se van a enviar datos no es necesaria. Por ejemplo, es posible que desee abrir el socket para asegurarse de que el asesor puede contactar con el servidor. Examine las necesidades de la aplicación con detenimiento antes de realizar la elección.

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;

  // crear una matriz de bytes con el mensaje de solicitud de carga
  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);         // el parámetro de modalidad de sustitución es true
    super.setAdvisor( this );
  }

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

  public void ADV_AdvisorInitialize()
  { 
    suppressBaseOpeningSocket();   // indicar al código base que no abra el
                                   // socket estándar
    return;
  }

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

  public int getLoad(int iConnectTime, ADV_Thread caller) {
    int iRc; 
    int iLoad = ADV_HOST_INACCESSIBLE;  // -1
    int iControlPort = 11999;   // puerto en el que comunicarse con el servidor

    String sServer = caller.getCurrentServerId();   // dirección de servidor que consultar
    try { 
      socket soServer = new Socket(sServer, iControlPort);  // abrir socket en
                                                            // servidor
      DataInputStream disServer = new DataInputStream(
                                      soServer.getInputStream());
      DataOutputStream dosServer = new DataOutputStream(
                                       soServer.getOutputStream());
      
      int iRecvTimeout = 10000;  // establecer tiempo de espera (en milisegundos)
                                 // para recibir datos
      soServer.setSoTimeout(iRecvTimeout);

      dosServer.writeInt(4);     // enviar un mensaje al servidor
      dosServer.flush();

      iLoad = disServer.readByte();   // recibir la respuesta del servidor

    } catch (exception e) {
      system.out.println("Caught exception " + e);
    }
    return iLoad;    // devolver la carga notificada desde el servidor
  }
}

Asesor de dos puertos

Este ejemplo de asesor personalizado demuestra la posibilidad de detectar una anomalía en un puerto de un servidor basándose en su propio estado y en el estado de un daemon de servidor diferente que se ejecute en otro puerto de la misma máquina de servidor. Por ejemplo, si el daemon HTTP en el puerto 80 deja de responder, es aconsejable que también detenga el tráfico de direccionamiento al daemon SSL en el puerto 443.

Este asesor es más agresivo que los asesores estándares, porque considera que cualquier servidor que no envía una respuesta ha dejado de funcionar y lo marca como inactivo. Los asesores estándar consideran que los servidores que no responden son muy lento. Este asesor marca un servidor como inactivo para el puerto HTTP y para el puerto SSL basándose en una falta de respuesta de cualquiera de los puertos.

Para utilizar este asesor personalizado, el administrador inicia dos instancias del asesor: una en el puerto HTTP y otra en el puerto SSL. El asesor crea instancias de dos tablas hash globales estáticas, una para HTTP y otra para SSL. Cada asesor intenta comunicarse con el daemon de servidor y almacena los resultados de este suceso en la tabla hash. El valor que cada asesor devuelve a la clase de asesor base depende de la posibilidad de comunicarse con su propio daemon de servidor y la posibilidad del asesor asociado para comunicarse con el daemon.

Se utilizan los siguientes métodos personalizados.

Se detectan las siguientes condiciones de error.

Este ejemplo se ha escrito para enlazar los puertos 80 para HTTP y 443 para SSL, pero se puede adaptar a cualquier combinación de puertos.

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;

//--------
// Definir el elemento de tabla para las tablas hash utilizadas en este asesor personalizado

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

//--------
// comprobar si este elemento es actual o está caducado
  public boolean isCurrent(ADV_twop oThis) {
    boolean bCurrent;
    int iLifetimeMs = 3 * 1000 * oThis.getInterval();   // establecer tiempo de vida como
                                                        // 3 ciclos de asesor
    Date dNow = new Date();
    Date dExpires = new Date(dTimestamp.getTime() + iLifetimeMs);

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

//--------
// valor descriptor(es) de acceso

  public int getLoadValue() { return iLoad; }
  
//--------
// clonar (evita la corrupción entre hebras)

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

}

//--------
// definir el asesor personalizado

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 tablas para mantener información histórica específica de puerto

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

  //--------
  // crear matriz de bytes con mensaje de saludo de cliente SSL
  
  public static final byte[] abClientHello = {
    (byte)0x80, (byte)0x1c,
    (byte)0x01,               // saludo de cliente
    (byte)0x03, (byte)0x00,   // versión SSL
    (byte)0x00, (byte)0x03,   // longitud de especificación de cifrado (bytes)
    (byte)0x00, (byte)0x00,   // longitud de ID de sesión (bytes)
    (byte)0x00, (byte)0x10,   // longitud de datos de reto (bytes)
    (byte)0x00, (byte)0x00, (byte)0x03,   // especificación de cifrado
    (byte)0x1A, (byte)0xFC, (byte)0xE5, (byte)Ox20,  // datos de reto
    (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 temporiza la respuesta
    setAdvisor ( this );
  }

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

  public void ADV_AdvisorInitialize() {
    return;
  }

  //--------
  // rutinas de acceso PUT y GET sincronizadas para las tablas hash

  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 carga HTTP basándose en la respuesta de servidor

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

    int iRc = caller.send(ADV_HTTP_REQUEST_STRING);  // enviar mensaje de solicitud
                                                     // al servidor
    if (0 <= iRc) {           // ¿Ha devuelto la solicitud una anomalía?
      StringBuffer sbReceiveData = new StringBuffer("")    // asignar un almacenamiento intermedio
                                                           // para la respuesta
      iRc = caller.receive(sbReceiveData);    // obtener respuesta de servidor

      if (0 <= iRc) {             // ¿ha devuelto la recepción una anomalía?
        if (0 < sbReceiveData.length()) {      // ¿hay datos allí?
          iLoad = SUCCESS;        // ignorar datos recuperados y
                                  // devolver código de realización satisfactoria
        }
      }
    }
    return iLoad;
  }

  //--------
  // getLoadSSL() - determinar carga SSL basándose en respuesta de 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);
        // Realizar una recepción.
        socket.getInputStream().read();
        // si la recepción es satisfactoria, devuelve la carga de 0. No ocuparse con
        // el contenido de los datos y la hebra ADV_Thread calcula la carga.
        iLoad = 0;
    } catch (IOException e) {
        // Al producirse un error, iLoad lo tomará de manera predeterminada.
    }
    return iLoad;
  }

  //--------
  // getLoad - fusionar resultados de los métodos HTTP y SSL

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

    String sCluster = caller.getCurrentClusterId();   // dirección de clúster actual
    int iPort = getAdviseOnPort();
    String sServer = caller.getCurrentServerId();
    String sHashKey = sCluster = ":" + sServer;     // clave de tabla hash

    if (ADV_TWOP_PORT_HTTP == iPort) {              // manejar un servidor HTTP
      iLoadHTTP = getLoadHTTP(iConnectTime, caller);  // obtener la carga para HTTP

      ADV_nte nteHTTP = newADV_nte(sCluster, iPort, sServer, iLoadHTTP);
      putNte(htTwopHTTP, "HTTP", sHashKey, nteHTTP);  // guardar información de
                                                      // carga HTTP
      ADV_nte nteSSL = getNte(htTwopSSL, "SSL", sHashKey);  // obtener
                                                            // información SSL
      if (null != nteSSL) { 
        if (true == nteSSL.isCurrent(this)) {         // comprobar indicación de fecha y hora
          if (ADV_HOST_INACCESSIBLE != nteSSL.getLoadValue()) {    // ¿funciona
                                                                   // SSL?
        iLoad = iLoadHTTP;
          } else {    // SSL no funciona, de modo que marcar el servidor HTTP como inactivo
            iLoad= ADV_HOST_INACCESSIBLE;
          }
        } else {      // La información SSL ha caducado, de modo que marcar el
                      // servidor HTTP como inactivo
          iLoad = ADV_HOST_INACCESSIBLE; 
        }
      } else {        // no hay información de carga sobre SSL, informar
                      // resultados de getLoadHTTP()
        iLoad = iLoadHTTP;
      }
    }
    else if (ADV_TWOP_PORT_SSL == iPort) {           // manejar un servidor SSL
      iLoadSSL = getLoadSSL(iConnectTime, caller);   // obtener carga para SSL

      ADV_nte nteSSL = new ADV_nte(sCluster, iPort, sServer, iLoadSSL);
      putNte(htTwopSSL, "SSL", sHashKey, nteSSL);   // guardar información de carga SSL.

      ADV_nte nteHTTP = getNte(htTwopHTTP, "SSL", sHashKey);   // obtener
                                                               // información HTTP
      if (null != nteHTTP) {
        if (true == nteHTTP.isCurrent(this)) {       // comprobar la indicación de fecha y hora
          if (ADV_HOST_INACCESSIBLE != nteHTTP.getLoadValue()) {  // ¿funciona
                                                                  // HTTP?
            iLoad = iLoadSSL; 
          } else {   // el servidor HTTP no funciona, por lo tanto marcar SSL como inactivo
          iLoad = ADV_HOST_INACCESSIBLE; 
          }
        } else {     // información caducada de HTTP, por lo tanto marcar SSL como inactivo
          iLoad = ADV_HOST_INACCESSIBLE; 
        }
      } else {       // no hay información de carga sobre HTTP, informar
                     // de resultados de getLoadSSL()
        iLoad = iLoadSSL;
      }
    }

  //--------
  // manejador de errores

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

Asesor de WebSphere Application Server

Se incluye un asesor personalizado de ejemplo para WebSphere Application Server en el directorio vía_acceso_instalación/servers/samples/CustomAdvisors/. En este documento no se duplica el código completo.

El asesor completo sólo es ligeramente más complejo que el del ejemplo. Añade una rutina de análisis especializada que es más compacta que la del ejemplo de StringTokenizer mostrado anteriormente.

La parte más compleja del código de ejemplo está en el servlet Java. Entre otros métodos, el servlet contiene dos métodos necesarios para la especificación de servlet: init() y service(), y un método, run(), que es necesario para la clase Java.lang.thread.

Más abajo se muestran los fragmentos pertinentes del código de 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;
  }

  ...

Utilización de los datos devueltos por los asesores

Si utiliza una llamada estándar a una parte existente del servidor de aplicaciones o añade un nuevo fragmento de código para que sea el equivalente del del asesor personalizado del lado del servidor, es aconsejable que examine los valores de carga devueltos y cambie el funcionamiento del servidor. La clase Java StringTokenizer, y los métodos asociados, hacen que esta investigación sea fácil.

El contenido de un mandato HTTP típico puede ser GET /index.html HTTP/1.0

Una respuesta típica a este mandato puede ser la siguiente.

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>

Los elementos de interés están contenidos en la primera línea, específicamente el código de retorno HTTP.

La especificación HTTP clasifica los códigos de retorno que se pueden resumir de la manera siguiente:

Si conoce con mucha exactitud qué códigos puede devolver posiblemente el servidor, puede que el código no tenga que ser tan detallado como en este ejemplo. No obstante, tenga en cuenta que la limitación de los códigos de retorno que detecte también puede limitar la futura flexibilidad del programa.

El ejemplo siguiente es un programa Java autónomo que contiene un cliente HTTP mínimo. El ejemplo invoca un analizador de uso general simple para examinar las respuestas 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());
    }
  }
}