Esempi

Gli esempi riportati di seguito mostrano come implementare gli advisor personalizzati.

Advisor standard

Questo codice di origine di esempio è simile all'advisor HTTP del Load Balancer standard. Funziona nel modo seguente:

  1. Viene emessa una richiesta di invio, un comando "HEAD/HTTP".
  2. Si riceve una risposta. Le informazioni non vengono analizzate, ma la risposta provoca la chiusura del metodo getLoad.
  3. Il metodo getLoad restituisce 0 per indicare la riuscita o -1 per indicare un errore.

Questo advisor opera in modalità normale, quindi la misurazione del carico si basa sul tempo utilizzato, espresso in millisecondi, necessario per eseguire le operazioni di apertura, invio, ricezione e chiusura del 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";

//--------
// Costruttore

  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;                                  // generalmente una routine vuota
  }

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

  public int getLoad(int iConnectTime, ADV_Thread caller) {
    int iRc;
    int iLoad = ADV_HOST_INACCESSIBLE;       // inizializzare sull'inaccessibile

    iRc = caller.send(ADV_SEND_REQUEST);     // inviare la richiesta HTTP al 
                                             // mittente
    if (0 <= iRc) {                          // se l'invio riesce
      StringBuffer sbReceiveData = new StringBuffer("");   // assegnare un buffer 
                                                           // per la risposta
      iRc = caller.receive(sbReceiveData);   // ricevere il risultato

      // se necessario, analizzare ora il risultato

      if (0 <= iRc) {          // se la ricezione è riuscita
        iLoad = 0;             // restituire 0 per indicare la riuscita 
      }                        // (il valore del carico dell'advisor viene ignorato dalla
    }                          //  base in modalità normale)
    return iLoad;
  }
}

Advisor flusso laterale

Questo esempio illustra l'eliminazione del socket standard aperto dall'advisor di base. Al contrario, questo advisor apre un socket Java flusso laterale per interrogare un server. Questa procedura può essere utile per i server che utilizzano una porta differente dal quella utilizzata dal traffico client normale per ricevere una query dell'advisor.

In questo esempio, un server è in ascolto sulla porta 11999 e quando viene interrogato restituisce un valore di carico con un int esadecimale pari a "4". Questo esempio viene eseguito in modalità di sostituzione, vale a dire, l'ultimo parametro del costruttore advisor viene impostato su true e il codice di base dell'advisor utilizza il valore del carico restituito piuttosto che il tempo trascorso.

Si noti la chiamata a supressBaseOpeningSocket() nella routine di inizializzazione. Non è richiesta l'eliminazione del socket di base se non verranno inviati dati. Ad esempio, è possibile che si desideri aprire il socket per verificare che l'advisor possa contattare il server. Esaminare attentamente le esigenze della propria applicazione prima di optare per questa scelta.

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;

  // creare una matrice di byte con il messaggio di richiesta del carico
  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);         // il parametro della modalità di sostituzione è true
    super.setAdvisor( this );
  }

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

  public void ADV_AdvisorInitialize()
  { 
    suppressBaseOpeningSocket();   // indicare al codice di base di non aprire 
                                   // il socket standard 
    return;
  }

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

  public int getLoad(int iConnectTime, ADV_Thread caller) {
    int iRc; 
    int iLoad = ADV_HOST_INACCESSIBLE;    // -1
    int iControlPort = 11999;   // la porta su cui comunicare con il server

    String sServer = caller.getCurrentServerId();   // indirizzo del server su cui eseguire la query 
    try { 
      socket soServer = new Socket(sServer, iControlPort);  // aprire il socket sul 
                                                            // server
      DataInputStream disServer = new DataInputStream(
                                      soServer.getInputStream());
      DataOutputStream dosServer = new DataOutputStream(
                                       soServer.getOutputStream());
      
      int iRecvTimeout = 10000;  // impostare il timeout (in millisecondi)
                                 // per la ricezione dei dati  
      soServer.setSoTimeout(iRecvTimeout);

      dosServer.writeInt(4);     // inviare un messaggio al server
      dosServer.flush();

      iLoad = disServer.readByte();   // ricevere la risposta dal server

    } catch (exception e) {
      system.out.println("Caught exception " + e);
    }
    return iLoad;    // restituire il carico registrato dal server 
  }
}

Advisor a due porte

Questo esempio di advisor personalizzato mostra la capacità di rilevare l'errore di una porta di un server, sia in base al suo stato sia in base allo stato di un daemon server differente in esecuzione su un'altra porta sulla stessa macchina server. Ad esempio, se il daemon HTTP sulla porta 80 arresta la risposta, è possibile che si desideri arrestare il traffico di instradamento per il daemon SSL sulla porta 443.

Questo advisor è più aggressivo rispetto agli advisor standard, poiché considera non operativi tutti i server che non inviano una risposta e li contrassegna come inattivi. Gli advisor standard considerano i server inerti come molto lenti. Questo advisor contrassegna un server come inattivo sia per la porta HTTP sia per la porta SSL, in caso di mancata risposta da entrambe le porte.

Per utilizzare questo advisor personalizzato, l'amministratore avvia due istanze dell'advisor: una sulla porta HTTP e una sulla porta SSL. L'advisor crea le istanze di due tabelle hash globali statiche, una per HTTP e una per SSL. Ciascun advisor tenta di comunicare con il relativo daemon server e memorizza i risultati di questo evento nella tabella hash correlata. Il valore restituito da ciascun advisor alla classe dell'advisor di base dipende sia dalla capacità di comunicare con il server daemon sia dalla capacità dell'advisor partner di comunicare con il proprio daemon.

Vengono utilizzati i seguenti metodi personalizzati.

Vengono rilevate le seguenti condizioni di errore.

Questo esempio viene scritto per collegare la porta 80 per HTTP e la porta 443 per SSL, ma può essere adattato per qualsiasi combinazione di porte.

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;

//--------
// Defin. l'elemento tab. per le tab. hash utilizzate in questo advisor personalizzato

class ADV_nte implements Cloneable {
  private String  sCluster;
  private int     iPort;
  private String  sServer;
  private int     iLoad;
  private Date    dTimestamp;

//--------
// costruttore

  public ADV_nte(String sClusterIn, int iPortIn, String sServerIn, 
                 int iLoadIn) {
    sCluster = sClusterIn;
    iPort = iPortIn;
    sServer = sServerIn;
    iLoad = iLoadIn;
    dTimestamp = new Date();
  }

//--------
// verificare se questo elemento è corrente o scaduto
  public boolean isCurrent(ADV_twop oThis) {
    boolean bCurrent;
    int iLifetimeMs = 3 * 1000 * oThis.getInterval();   // impostare la durata come 
                                                        // 3 cicli di advisor
    Date dNow = new Date();
    Date dExpires = new Date(dTimestamp.getTime() + iLifetimeMs);

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

//--------
// accessori valore

  public int getLoadValue() { return iLoad; }
  
//--------
// clone (evita la corruzione tra i thread)

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

}

//--------
// definire l'advisor personalizzato

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;

  //--------
  // definire le tab. per conservare le info cronologiche specifiche della 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";

  //--------
  // creare una matrice di byte con il messaggio hello del client SSL
  
  public static final byte[] abClientHello = {
    (byte)0x80, (byte)0x1c,
    (byte)0x01,               // client hello
    (byte)0x03, (byte)0x00,   // versione SSL
    (byte)0x00, (byte)0x03,   // lunghezza specifica di codifica (byte)
    (byte)0x00, (byte)0x00,   // lunghezza ID di sessione (byte)
    (byte)0x00, (byte)0x10,   // lunghezza dati richiesta (byte)
    (byte)0x00, (byte)0x00, (byte)0x03,   // specifica di codifica
    (byte)0x1A, (byte)0xFC, (byte)0xE5, (byte)Ox20,  // dati richiesta
    (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 };

  //--------
  // costruttore

  public ADV_twop() {
    super(ADV_TWOP_NAME, VERSION, ADV_TWOP_DEF_ADV_ON_PORT, 
          ADV_TWOP_DEF_INTERVAL, "", 
          false);    // false = load balancer programma la risposta
    setAdvisor ( this );
  }

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

  public void ADV_AdvisorInitialize() {
    return;
  }

  //--------
  // routine di accesso PUT e GET sincronizzate per le tabelle 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 - determinare il carico HTTP in base alla risposta del server

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

    int iRc = caller.send(ADV_HTTP_REQUEST_STRING);  // inviare il msg di richiesta 
                                                     // al server   
    if (0 <= iRc) {           // la richiesta ha restituito un errore? 
      StringBuffer sbReceiveData = new StringBuffer("")    // assegnare un buffer 
                                                           // per la risposta
      iRc = caller.receive(sbReceiveData);    // ricevere la risposta dal server

      if (0 <= iRc) {             // la ricezione ha restituito un errore? 
        if (0 < sbReceiveData.length()) {      // vi sono dati? 
          iLoad = SUCCESS;        // ignorare i dati richiamati e  
                                  // restituire il codice di riuscita
        }
      }
    }
    return iLoad;
  }

  //--------
  // getLoadSSL() - determinare il carico SSL in base alla risposta del server

  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);
        // Eseguire una ricezione.
        socket.getInputStream().read();
        // Se la ricezione riesce correttamente, restituire un carico pari a 0. Non preoccuparsi del
        // contenuto dei dati, il carico è calcolato dal thread ADV_Thread.
        iLoad = 0;
    } catch (IOException e) {
        // In seguito a un errore, iLoad punterà ad esso.
    }
    return iLoad;
  }

  //--------
  // getLoad - associare i risultati dei metodi HTTP e SSL

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

    String sCluster = caller.getCurrentClusterId();   // indirizzo cluster corrente
    int iPort = getAdviseOnPort();
    String sServer = caller.getCurrentServerId();
    String sHashKey = sCluster = ":" + sServer;     // chiave tabella hash

    if (ADV_TWOP_PORT_HTTP == iPort) {              // gestire un server HTTP
      iLoadHTTP = getLoadHTTP(iConnectTime, caller);  // richiamare carico per HTTP

      ADV_nte nteHTTP = newADV_nte(sCluster, iPort, sServer, iLoadHTTP);
      putNte(htTwopHTTP, "HTTP", sHashKey, nteHTTP);  // salvare le info sul carico 
                                                      // su SSL
      ADV_nte nteSSL = getNte(htTwopSSL, "SSL", sHashKey);  // richiamare le info 
                                                            // su SSL 
      if (null != nteSSL) { 
        if (true == nteSSL.isCurrent(this)) {         // controllare data e ora
          if (ADV_HOST_INACCESSIBLE != nteSSL.getLoadValue()) {    // l'SSL è 
                                                                   // in funzione?
            iLoad = iLoadHTTP;
          } else {    // SSL non funziona, quindi contras. srvr HTTP come inattivo
            iLoad= ADV_HOST_INACCESSIBLE;
          }
        } else {      // le informazioni su SSL sono scadute, quindi contrassegnare 
                      // il server HTTP come inattivo
          iLoad = ADV_HOST_INACCESSIBLE; 
        }
      } else {        // nessuna informazione sul carico relativa all'SSL, registrare 
                      // i risultati di getLoadHTTP()
        iLoad = iLoadHTTP;
      }
    }
    else if (ADV_TWOP_PORT_SSL == iPort) {           // gestire un server SSL
      iLoadSSL = getLoadSSL(iConnectTime, caller);   // ricevere il carico per SSL

      ADV_nte nteSSL = new ADV_nte(sCluster, iPort, sServer, iLoadSSL);
      putNte(htTwopSSL, "SSL", sHashKey, nteSSL);   // salvare info sul carico SSL.

      ADV_nte nteHTTP = getNte(htTwopHTTP, "SSL", sHashKey);   // ottenere info su 
                                                               // su SSL
      if (null != nteHTTP) {
        if (true == nteHTTP.isCurrent(this)) {       // controllare data e ora
          if (ADV_HOST_INACCESSIBLE != nteHTTP.getLoadValue()) {  // HTTP è 
                                                                  // in funzione?
            iLoad = iLoadSSL; 
          } else {   // server HTTP non è in funzione, quindi contrassegnare SSL come inattivo
            iLoad = ADV_HOST_INACCESSIBLE; 
          }
        } else {     // info da HTTP scadute, quindi contrassegnare SSL come inattivo
          iLoad = ADV_HOST_INACCESSIBLE; 
        }
      } else {       // nessuna informazione su HTTP, registrare 
                     // i risultati di getLoadSSL()
        iLoad = iLoadSSL;
      }
    }

  //--------
  // handler dell'errore

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

Advisor di WebSphere Application Server

Un advisor personalizzato di esempio per WebSphere Application Server è incluso nella directory percorso_installazione/servers/samples/CustomAdvisors/. Il codice completo non viene duplicato in questo documento.

L'advisor completo è leggermente più complesso dell'esempio. Esso aggiunge una routine di analisi specializzata più compatta rispetto all'esempio StringTokenizer mostrato in precedenza.

La parte più complessa del codice di esempio è rappresentata dal servlet Java. Tra gli altri metodi, il servlet contiene due metodi richiesti dalla specifica del servlet: init() e service(), nonché un metodo, run(), richiesto dalla classe Java.lang.thread.

Di seguito vengono illustrati i frammenti rilevanti del codice 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;
  }

  ...

Utilizzo dei dati restituiti dagli advisor

Nel caso in cui si utilizzi una chiamata standard a una parte esistente del server delle applicazioni o si aggiunga una nuova parte di codice alla controparte lato server del proprio advisor personalizzato, è possibile che si desideri esaminare i valori del carico restituiti e modificare il funzionamento del server. La classe Java StringTokenizer e i relativi metodi, rendono più semplice questo controllo.

Il contenuto di un comando HTTP tipico potrebbe essere GET /index.html HTTP/1.0

Una risposta tipica a questo comando potrebbe essere la seguente.

HTTP/1.1 200 OK
Date: Mon, 20 November 2000 14:09:57 GMT
Server: Apache/1.3.12 (Linux e 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>Pagina testo</TITLE></HEAD>
<BODY><H1>Server Apache</H1>
<HR>
<P><P>Questo server Web esegue Apache 1.3.12.
<P><HR>
<P><IMG SRC="apache_pb.gif" ALT="">
</BODY></HTML>

Gli elementi di interesse sono contenuti nella prima riga, in particolare il codice di ritorno HTTP.

La specifica HTTP classifica i codici di ritorno riepilogati nel modo seguente:

Se si conoscono perfettamente quali sono codici che il server può restituire, non è necessario che il codice sia così dettagliato come mostrato in questo esempio. Tuttavia, tenere presente che limitando i codici di ritorno che possono essere rilevati si potrebbe limitare la flessibilità futura del programma.

L'esempio riportato di seguito è un programma Java autonomo che contiene un client HTTP minimo. L'esempio richiama un programma di analisi semplice e generico per l'esame delle risposte 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());
    }
  }
}