En los ejemplos siguientes se muestra cómo se pueden implementar asesores personalizados.
Este código fuente de ejemplo es similar al asesor HTTP estándar de Load Balancer. Funciona de la manera siguiente:
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;
}
}
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
}
}
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;
}
}
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;
}
...
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());
}
}
}